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 }