1 /* 2 * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 package jnlp.converter.parser; 25 26 import java.util.ArrayList; 27 import java.util.Arrays; 28 29 /** 30 * VersionID contains a JNLP version ID. 31 * 32 * The VersionID also contains a prefix indicator that can 33 * be used when stored with a VersionString 34 * 35 */ 36 public class VersionID implements Comparable<VersionID> { 37 private final String[] _tuple; // Array of Integer or String objects 38 private final boolean _usePrefixMatch; // star (*) prefix 39 private final boolean _useGreaterThan; // plus (+) greather-than 40 private final boolean _isCompound; // and (&) operator 41 private final VersionID _rest; // remaining part after the & 42 43 /** 44 * Creates a VersionID object from a given <code>String</code>. 45 * @param str version string to parse 46 */ 47 public VersionID(String str) { 48 if (str == null || str.length() == 0) { 49 _tuple = new String[0]; 50 _useGreaterThan = false; 51 _usePrefixMatch = false; 52 _isCompound = false; 53 _rest = null; 54 return; 55 } 56 57 // Check for compound 58 int amp = str.indexOf("&"); 59 if (amp >= 0) { 60 _isCompound = true; 61 VersionID firstPart = new VersionID(str.substring(0, amp)); 62 _rest = new VersionID(str.substring(amp+1)); 63 _tuple = firstPart._tuple; 64 _usePrefixMatch = firstPart._usePrefixMatch; 65 _useGreaterThan = firstPart._useGreaterThan; 66 } else { 67 _isCompound = false; 68 _rest = null; 69 // Check for postfix 70 if (str.endsWith("+")) { 71 _useGreaterThan = true; 72 _usePrefixMatch = false; 73 str = str.substring(0, str.length() - 1); 74 } else if (str.endsWith("*")) { 75 _useGreaterThan = false; 76 _usePrefixMatch = true; 77 str = str.substring(0, str.length() - 1); 78 } else { 79 _useGreaterThan = false; 80 _usePrefixMatch = false; 81 } 82 83 ArrayList<String> list = new ArrayList<>(); 84 int start = 0; 85 for (int i = 0; i < str.length(); i++) { 86 // Split at each separator character 87 if (".-_".indexOf(str.charAt(i)) != -1) { 88 if (start < i) { 89 String value = str.substring(start, i); 90 list.add(value); 91 } 92 start = i + 1; 93 } 94 } 95 if (start < str.length()) { 96 list.add(str.substring(start, str.length())); 97 } 98 _tuple = list.toArray(new String[0]); 99 } 100 } 101 102 /** @return true if no flags are set */ 103 public boolean isSimpleVersion() { 104 return !_useGreaterThan && !_usePrefixMatch && !_isCompound; 105 } 106 107 /** Match 'this' versionID against vid. 108 * The _usePrefixMatch/_useGreaterThan flag is used to determine if a 109 * prefix match of an exact match should be performed 110 * if _isCompound, must match _rest also. 111 */ 112 public boolean match(VersionID vid) { 113 if (_isCompound) { 114 if (!_rest.match(vid)) { 115 return false; 116 } 117 } 118 return (_usePrefixMatch) ? this.isPrefixMatchTuple(vid) : 119 (_useGreaterThan) ? vid.isGreaterThanOrEqualTuple(this) : 120 matchTuple(vid); 121 } 122 123 /** Compares if two version IDs are equal */ 124 @Override 125 public boolean equals(Object o) { 126 if (matchTuple(o)) { 127 VersionID ov = (VersionID) o; 128 if (_rest == null || _rest.equals(ov._rest)) { 129 if ((_useGreaterThan == ov._useGreaterThan) && 130 (_usePrefixMatch == ov._usePrefixMatch)) { 131 return true; 132 } 133 } 134 } 135 return false; 136 } 137 138 /** Computes a hash code for a VersionID */ 139 @Override 140 public int hashCode() { 141 boolean first = true; 142 int hashCode = 0; 143 for (String tuple : _tuple) { 144 if (first) { 145 first = false; 146 hashCode = tuple.hashCode(); 147 } else { 148 hashCode = hashCode ^ tuple.hashCode(); 149 } 150 } 151 return hashCode; 152 } 153 154 /** Compares if two version IDs are equal */ 155 private boolean matchTuple(Object o) { 156 // Check for null and type 157 if (o == null || !(o instanceof VersionID)) { 158 return false; 159 } 160 VersionID vid = (VersionID) o; 161 162 // Normalize arrays 163 String[] t1 = normalize(_tuple, vid._tuple.length); 164 String[] t2 = normalize(vid._tuple, _tuple.length); 165 166 // Check contents 167 for (int i = 0; i < t1.length; i++) { 168 Object o1 = getValueAsObject(t1[i]); 169 Object o2 = getValueAsObject(t2[i]); 170 if (!o1.equals(o2)) { 171 return false; 172 } 173 } 174 175 return true; 176 } 177 178 private Object getValueAsObject(String value) { 179 if (value.length() > 0 && value.charAt(0) != '-') { 180 try { 181 return Integer.valueOf(value); 182 } catch (NumberFormatException nfe) { 183 /* fall through */ 184 } 185 } 186 return value; 187 } 188 189 public boolean isGreaterThan(VersionID vid) { 190 if (vid == null) { 191 return false; 192 } 193 return isGreaterThanOrEqualHelper(vid, false, true); 194 } 195 196 public boolean isGreaterThanOrEqual(VersionID vid) { 197 if (vid == null) { 198 return false; 199 } 200 return isGreaterThanOrEqualHelper(vid, true, true); 201 } 202 203 boolean isGreaterThanOrEqualTuple(VersionID vid) { 204 return isGreaterThanOrEqualHelper(vid, true, false); 205 } 206 207 /** Compares if 'this' is greater than vid */ 208 private boolean isGreaterThanOrEqualHelper(VersionID vid, 209 boolean allowEqual, boolean useRest) { 210 211 if (useRest && _isCompound) { 212 if (!_rest.isGreaterThanOrEqualHelper(vid, allowEqual, true)) { 213 return false; 214 } 215 } 216 // Normalize the two strings 217 String[] t1 = normalize(_tuple, vid._tuple.length); 218 String[] t2 = normalize(vid._tuple, _tuple.length); 219 220 for (int i = 0; i < t1.length; i++) { 221 // Compare current element 222 Object e1 = getValueAsObject(t1[i]); 223 Object e2 = getValueAsObject(t2[i]); 224 if (e1.equals(e2)) { 225 // So far so good 226 } else { 227 if (e1 instanceof Integer && e2 instanceof Integer) { 228 // if both can be parsed as ints, compare ints 229 return ((Integer)e1).intValue() > ((Integer)e2).intValue(); 230 } else { 231 if (e1 instanceof Integer) { 232 return false; // e1 (numeric) < e2 (non-numeric) 233 } else if (e2 instanceof Integer) { 234 return true; // e1 (non-numeric) > e2 (numeric) 235 } 236 237 String s1 = t1[i]; 238 String s2 = t2[i]; 239 240 return s1.compareTo(s2) > 0; 241 } 242 243 } 244 } 245 // If we get here, they are equal 246 return allowEqual; 247 } 248 249 /** Checks if 'this' is a prefix of vid */ 250 private boolean isPrefixMatchTuple(VersionID vid) { 251 252 // Make sure that vid is at least as long as the prefix 253 String[] t2 = normalize(vid._tuple, _tuple.length); 254 255 for (int i = 0; i < _tuple.length; i++) { 256 Object e1 = _tuple[i]; 257 Object e2 = t2[i]; 258 if (e1.equals(e2)) { 259 // So far so good 260 } else { 261 // Not a prefix 262 return false; 263 } 264 } 265 return true; 266 } 267 268 /** Normalize an array to a certain lengh */ 269 private String[] normalize(String[] list, int minlength) { 270 if (list.length < minlength) { 271 // Need to do padding 272 String[] newlist = new String[minlength]; 273 System.arraycopy(list, 0, newlist, 0, list.length); 274 Arrays.fill(newlist, list.length, newlist.length, "0"); 275 return newlist; 276 } else { 277 return list; 278 } 279 } 280 281 @Override 282 public int compareTo(VersionID o) { 283 if (o == null || !(o instanceof VersionID)) { 284 return -1; 285 } 286 VersionID vid = o; 287 return equals(vid) ? 0 : (isGreaterThanOrEqual(vid) ? 1 : -1); 288 } 289 290 /** Show it as a string */ 291 @Override 292 public String toString() { 293 StringBuilder sb = new StringBuilder(); 294 for (int i = 0; i < _tuple.length - 1; i++) { 295 sb.append(_tuple[i]); 296 sb.append('.'); 297 } 298 if (_tuple.length > 0) { 299 sb.append(_tuple[_tuple.length - 1]); 300 } 301 if (_useGreaterThan) { 302 sb.append('+'); 303 } 304 if (_usePrefixMatch) { 305 sb.append('*'); 306 } 307 if (_isCompound) { 308 sb.append("&"); 309 sb.append(_rest); 310 } 311 return sb.toString(); 312 } 313 }