001/* 002 * Copyright 2016 The AppAuth for Android Authors. All Rights Reserved. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the 010 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 011 * express or implied. See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014 015package net.openid.appauth.browser; 016 017import androidx.annotation.NonNull; 018 019/** 020 * Represents a delimited version number for an application. This can parse common version number 021 * formats, treating any sequence of non-numeric characters as a delimiter, and discards these 022 * to retain just the numeric content for comparison. Trailing zeroes in a version number 023 * are discarded to produce a compact, canonical representation. Empty versions are equivalent to 024 * "0". Each numeric part is expected to fit within a 64-bit integer. 025 */ 026public class DelimitedVersion implements Comparable<DelimitedVersion> { 027 028 // See: http://stackoverflow.com/a/2816747 029 private static final int PRIME_HASH_FACTOR = 92821; 030 031 private static final long BIT_MASK_32 = 0xFFFFFFFF; 032 033 private final long[] mNumericParts; 034 035 /** 036 * Creates a version with the specified parts, ordered from major to minor. 037 */ 038 public DelimitedVersion(long[] numericParts) { 039 mNumericParts = numericParts; 040 } 041 042 @Override 043 public String toString() { 044 if (mNumericParts.length == 0) { 045 return "0"; 046 } 047 048 StringBuilder builder = new StringBuilder(); 049 builder.append(mNumericParts[0]); 050 051 int index = 1; 052 while (index < mNumericParts.length) { 053 builder.append('.'); 054 builder.append(mNumericParts[index]); 055 index++; 056 } 057 058 return builder.toString(); 059 } 060 061 @Override 062 public boolean equals(Object obj) { 063 if (this == obj) { 064 return true; 065 } 066 067 if (obj == null || !(obj instanceof DelimitedVersion)) { 068 return false; 069 } 070 071 return this.compareTo((DelimitedVersion) obj) == 0; 072 } 073 074 @Override 075 public int hashCode() { 076 int result = 0; 077 078 for (long numericPart : mNumericParts) { 079 result = result * PRIME_HASH_FACTOR + (int)(numericPart & BIT_MASK_32); 080 } 081 082 return result; 083 } 084 085 @Override 086 public int compareTo(@NonNull DelimitedVersion other) { 087 int index = 0; 088 089 while (index < this.mNumericParts.length && index < other.mNumericParts.length) { 090 int currentPartOrder = 091 compareLongs(this.mNumericParts[index], other.mNumericParts[index]); 092 if (currentPartOrder != 0) { 093 return currentPartOrder; 094 } 095 index++; 096 } 097 098 return compareLongs(this.mNumericParts.length, other.mNumericParts.length); 099 } 100 101 private int compareLongs(long l1, long l2) { 102 if (l1 < l2) { 103 return -1; 104 } else if (l1 > l2) { 105 return 1; 106 } 107 return 0; 108 } 109 110 /** 111 * Parses a delimited version number from the provided string. 112 */ 113 public static DelimitedVersion parse(String versionString) { 114 115 if (versionString == null) { 116 return new DelimitedVersion(new long[0]); 117 } 118 119 String[] stringParts = versionString.split("[^0-9]+"); 120 121 long[] parsedParts = new long[stringParts.length]; 122 int index = 0; 123 for (String numericPart : stringParts) { 124 if (numericPart.isEmpty()) { 125 continue; 126 } 127 128 parsedParts[index] = Long.parseLong(numericPart); 129 index++; 130 } 131 132 // discard all trailing zeroes 133 index--; 134 while (index >= 0) { 135 if (parsedParts[index] > 0) { 136 break; 137 } 138 index--; 139 } 140 141 int length = index + 1; 142 long[] onlyParsedParts = new long[length]; 143 System.arraycopy(parsedParts, 0, onlyParsedParts, 0, length); 144 return new DelimitedVersion(onlyParsedParts); 145 } 146}