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 }