View Javadoc
1   /*
2    * This file is part of dependency-check-core.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2013 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.utils;
19  
20  import java.util.ArrayList;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.regex.Matcher;
24  import java.util.regex.Pattern;
25  import org.apache.commons.lang3.StringUtils;
26  
27  /**
28   * <p>
29   * Simple object to track the parts of a version number. The parts are contained
30   * in a List such that version 1.2.3 will be stored as:  <code>versionParts[0] = 1;
31   * versionParts[1] = 2;
32   * versionParts[2] = 3;
33   * </code></p>
34   * <p>
35   * Note, the parser contained in this class expects the version numbers to be
36   * separated by periods. If a different separator is used the parser will likely
37   * fail.</p>
38   *
39   * @author Jeremy Long
40   */
41  public class DependencyVersion implements Iterable<String>, Comparable<DependencyVersion> {
42  
43      /**
44       * Constructor for a empty DependencyVersion.
45       */
46      public DependencyVersion() {
47      }
48  
49      /**
50       * Constructor for a DependencyVersion that will parse a version string.
51       * <b>Note</b>, this should only be used when the version passed in is
52       * already known to be a well formatted version number. Otherwise,
53       * DependencyVersionUtil.parseVersion() should be used instead.
54       *
55       * @param version the well formatted version number to parse
56       */
57      public DependencyVersion(String version) {
58          parseVersion(version);
59      }
60  
61      /**
62       * Parses a version string into its sub parts: major, minor, revision,
63       * build, etc. <b>Note</b>, this should only be used to parse something that
64       * is already known to be a version number.
65       *
66       * @param version the version string to parse
67       */
68      public final void parseVersion(String version) {
69          versionParts = new ArrayList<String>();
70          if (version != null) {
71              final Pattern rx = Pattern.compile("(\\d+[a-z]{1,3}$|[a-z]+\\d+|\\d+|(release|beta|alpha)$)");
72              final Matcher matcher = rx.matcher(version.toLowerCase());
73              while (matcher.find()) {
74                  versionParts.add(matcher.group());
75              }
76              if (versionParts.isEmpty()) {
77                  versionParts.add(version);
78              }
79          }
80      }
81      /**
82       * A list of the version parts.
83       */
84      private List<String> versionParts;
85  
86      /**
87       * Get the value of versionParts.
88       *
89       * @return the value of versionParts
90       */
91      public List<String> getVersionParts() {
92          return versionParts;
93      }
94  
95      /**
96       * Set the value of versionParts.
97       *
98       * @param versionParts new value of versionParts
99       */
100     public void setVersionParts(List<String> versionParts) {
101         this.versionParts = versionParts;
102     }
103 
104     /**
105      * Retrieves an iterator for the version parts.
106      *
107      * @return an iterator for the version parts
108      */
109     @Override
110     public Iterator<String> iterator() {
111         return versionParts.iterator();
112     }
113 
114     /**
115      * Reconstructs the version string from the split version parts.
116      *
117      * @return a string representing the version.
118      */
119     @Override
120     public String toString() {
121         return StringUtils.join(versionParts, '.');
122     }
123 
124     /**
125      * Compares the equality of this object to the one passed in as a parameter.
126      *
127      * @param obj the object to compare equality
128      * @return returns true only if the two objects are equal, otherwise false
129      */
130     @Override
131     public boolean equals(Object obj) {
132         if (obj == null) {
133             return false;
134         }
135         if (getClass() != obj.getClass()) {
136             return false;
137         }
138         final DependencyVersion other = (DependencyVersion) obj;
139         final int minVersionMatchLength = (this.versionParts.size() < other.versionParts.size())
140                 ? this.versionParts.size() : other.versionParts.size();
141         final int maxVersionMatchLength = (this.versionParts.size() > other.versionParts.size())
142                 ? this.versionParts.size() : other.versionParts.size();
143 
144         if (minVersionMatchLength == 1 && maxVersionMatchLength >= 3) {
145             return false;
146         }
147 
148         //TODO steal better version of code from compareTo
149         for (int i = 0; i < minVersionMatchLength; i++) {
150             final String thisPart = this.versionParts.get(i);
151             final String otherPart = other.versionParts.get(i);
152             if (!thisPart.equals(otherPart)) {
153                 return false;
154             }
155         }
156         if (this.versionParts.size() > minVersionMatchLength) {
157             for (int i = minVersionMatchLength; i < this.versionParts.size(); i++) {
158                 if (!"0".equals(this.versionParts.get(i))) {
159                     return false;
160                 }
161             }
162         }
163 
164         if (other.versionParts.size() > minVersionMatchLength) {
165             for (int i = minVersionMatchLength; i < other.versionParts.size(); i++) {
166                 if (!"0".equals(other.versionParts.get(i))) {
167                     return false;
168                 }
169             }
170         }
171 
172         /*
173          *  if (this.versionParts != other.versionParts && (this.versionParts == null || !this.versionParts.equals(other.versionParts))) {
174          *      return false;
175          *  }
176          */
177         return true;
178     }
179 
180     /**
181      * Calculates the hashCode for this object.
182      *
183      * @return the hashCode
184      */
185     @Override
186     public int hashCode() {
187         int hash = 5;
188         hash = 71 * hash + (this.versionParts != null ? this.versionParts.hashCode() : 0);
189         return hash;
190     }
191 
192     /**
193      * Determines if the three most major major version parts are identical. For
194      * instances, if version 1.2.3.4 was compared to 1.2.3 this function would
195      * return true.
196      *
197      * @param version the version number to compare
198      * @return true if the first three major parts of the version are identical
199      */
200     public boolean matchesAtLeastThreeLevels(DependencyVersion version) {
201         if (version == null) {
202             return false;
203         }
204         if (Math.abs(this.versionParts.size() - version.versionParts.size()) >= 3) {
205             return false;
206         }
207 
208         final int max = (this.versionParts.size() < version.versionParts.size())
209                 ? this.versionParts.size() : version.versionParts.size();
210 
211         boolean ret = true;
212         for (int i = 0; i < max; i++) {
213             final String thisVersion = this.versionParts.get(i);
214             final String otherVersion = version.getVersionParts().get(i);
215             if (i >= 3) {
216                 if (thisVersion.compareToIgnoreCase(otherVersion) >= 0) {
217                     ret = false;
218                     break;
219                 }
220             } else if (!thisVersion.equals(otherVersion)) {
221                 ret = false;
222                 break;
223             }
224         }
225 
226         return ret;
227     }
228 
229     @Override
230     public int compareTo(DependencyVersion version) {
231         if (version == null) {
232             return 1;
233         }
234         final List<String> left = this.getVersionParts();
235         final List<String> right = version.getVersionParts();
236         final int max = left.size() < right.size() ? left.size() : right.size();
237 
238         for (int i = 0; i < max; i++) {
239             final String lStr = left.get(i);
240             final String rStr = right.get(i);
241             if (lStr.equals(rStr)) {
242                 continue;
243             }
244             try {
245                 final int l = Integer.parseInt(lStr);
246                 final int r = Integer.parseInt(rStr);
247                 if (l < r) {
248                     return -1;
249                 } else if (l > r) {
250                     return 1;
251                 }
252             } catch (NumberFormatException ex) {
253                 final int comp = left.get(i).compareTo(right.get(i));
254                 if (comp < 0) {
255                     return -1;
256                 } else if (comp > 0) {
257                     return 1;
258                 }
259             }
260         }
261         if (left.size() < right.size()) {
262             return -1;
263         } else if (left.size() > right.size()) {
264             return 1;
265         } else {
266             return 0;
267         }
268     }
269 }