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