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