1 /*
2 * #%L
3 * Version.java - mongodb-async-driver - Allanbank Consulting, Inc.
4 * %%
5 * Copyright (C) 2011 - 2014 Allanbank Consulting, Inc.
6 * %%
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * #L%
19 */
20 package com.allanbank.mongodb;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.Serializable;
25 import java.util.Arrays;
26 import java.util.List;
27 import java.util.Properties;
28
29 import com.allanbank.mongodb.bson.NumericElement;
30 import com.allanbank.mongodb.util.IOUtils;
31 import com.allanbank.mongodb.util.log.Log;
32 import com.allanbank.mongodb.util.log.LogFactory;
33
34 /**
35 * Version provides a class to handle version numbers and provide the version of
36 * the driver in use.
37 *
38 * @copyright 2013, Allanbank Consulting, Inc., All Rights Reserved
39 */
40 public class Version implements Serializable, Comparable<Version> {
41
42 /**
43 * A version to use when we don't know the version. This version will always
44 * compare greater than (higher, more recent) than all other versions.
45 */
46 public static final Version UNKNOWN;
47
48 /** The driver's version. */
49 public static final Version VERSION;
50
51 /** The "zero" version which should be before all other versions. */
52 public static final Version VERSION_0 = Version.parse("0");
53
54 /** Version 2.0 */
55 public static final Version VERSION_2_0 = Version.parse("2.0");
56
57 /** Version 2.2 */
58 public static final Version VERSION_2_2 = Version.parse("2.2");
59
60 /** Version 2.4 */
61 public static final Version VERSION_2_4 = Version.parse("2.4");
62
63 /** Version 2.6 */
64 public static final Version VERSION_2_6 = Version.parse("2.6");
65
66 /** Version 2.5.2 - Wire Protocol version = 1. */
67 protected static final Version VERSION_2_5_2 = Version.parse("2.5.2");
68
69 /** Version 2.5.4 - Wire Protocol version = 2. */
70 protected static final Version VERSION_2_5_4 = Version.parse("2.5.4");
71
72 /** The logger for the {@link Version}. */
73 private static final Log LOG = LogFactory.getLog(Version.class);
74
75 /** The serialization version for the class. */
76 private static final long serialVersionUID = 4726973040107711788L;
77
78 static {
79 // Load the version from the maven pom.properties file.
80 final Properties props = new Properties();
81 InputStream in = null;
82 try {
83 in = Version.class
84 .getResourceAsStream("/META-INF/maven/com.allanbank/"
85 + "mongodb-async-driver/pom.properties");
86 if (in != null) {
87 props.load(in);
88 }
89 }
90 catch (final IOException error) {
91 LOG.info("Could not read the version information for the driver.");
92 }
93 finally {
94 IOUtils.close(in);
95 }
96
97 VERSION = parse(props.getProperty("version", "0-DEVELOPMENT"));
98 UNKNOWN = new Version(new int[0], "UNKNOWN");
99 }
100
101 /**
102 * Returns the earlier of the two versions. If either version is
103 * {@code null} then the other version is returned. Only if both version are
104 * {@code null} will {@code null} be returned.
105 *
106 * @param lhs
107 * The first version to compare.
108 * @param rhs
109 * The second version to compare.
110 * @return The earlier (lesser) version of the two.
111 */
112 public static Version earlier(final Version lhs, final Version rhs) {
113 if ((lhs == null) || ((rhs != null) && (lhs.compareTo(rhs) > 0))) {
114 return rhs;
115 }
116
117 return lhs;
118 }
119
120 /**
121 * Returns the best guess at the version of the server based on the wire
122 * protocol version number. Returns the first version of the server to
123 * support the wire protocol version.
124 *
125 * @param wireVersion
126 * Wire protocol version.
127 * @return The best guess at the version of the server based on the wire
128 * protocol version number. Returns <code>null</code> if the version
129 * cannot be determined.
130 */
131 public static Version forWireVersion(final int wireVersion) {
132 Version result = null;
133 if (wireVersion >= 2) {
134 result = VERSION_2_5_4;
135 }
136 else if (wireVersion == 1) {
137 result = VERSION_2_5_2;
138 }
139 else if (wireVersion == 0) {
140 result = VERSION_2_4;
141 }
142
143 return result;
144 }
145
146 /**
147 * Returns the later of the two versions. If either version is {@code null}
148 * then the other version is returned. Only if both version are {@code null}
149 * will {@code null} be returned.
150 *
151 * @param lhs
152 * The first version to compare.
153 * @param rhs
154 * The second version to compare.
155 * @return The later (greater) version of the two.
156 */
157 public static Version later(final Version lhs, final Version rhs) {
158 if ((lhs == null) || ((rhs != null) && (lhs.compareTo(rhs) < 0))) {
159 return rhs;
160 }
161 return lhs;
162 }
163
164 /**
165 * Parses a version from a version array. The values in the array are
166 * assumed to be {@link NumericElement}s.
167 *
168 * @param versionArray
169 * The version array to parse.
170 * @return The version.
171 */
172 public static Version parse(final List<NumericElement> versionArray) {
173
174 final int[] version = new int[versionArray.size()];
175 for (int i = 0; i < version.length; ++i) {
176 version[i] = versionArray.get(i).getIntValue();
177 }
178
179 return new Version(version, null);
180 }
181
182 /**
183 * Parses a version of the general format 'int.int.int-suffix'.
184 *
185 * @param version
186 * The version string to parse.
187 * @return The version.
188 */
189 public static Version parse(final String version) {
190
191 final String[] tokens = version.split("\\.");
192 final int[] versions = new int[tokens.length];
193 String suffix = "";
194 for (int i = 0; i < tokens.length; ++i) {
195 String token = tokens[i];
196
197 // Check for a suffix on the last token.
198 if (i == (tokens.length - 1)) {
199 final int dashIndex = token.indexOf('-');
200 if (dashIndex >= 0) {
201 suffix = token.substring(dashIndex + 1);
202 token = token.substring(0, dashIndex);
203 }
204 }
205
206 try {
207 versions[i] = Integer.parseInt(token);
208 }
209 catch (final NumberFormatException nfe) {
210 LOG.debug(
211 "Could not parse version string token ('{}') from version '{}'.",
212 token, version);
213 }
214 }
215
216 return new Version(versions, suffix);
217 }
218
219 /** The suffix for the version like 'SNAPSHOT'. May be the empty string. */
220 private final String mySuffix;
221
222 /** The "values" for the version number. */
223 private final int[] myVersion;
224
225 /**
226 * Creates a new Version.
227 *
228 * @param version
229 * The "values" for the version number.
230 * @param suffix
231 * The suffix for the version like 'SNAPSHOT'. May be the empty
232 * string.
233 */
234 private Version(final int[] version, final String suffix) {
235 myVersion = version.clone();
236 mySuffix = (suffix != null) ? suffix : "";
237 }
238
239 /**
240 * {@inheritDoc}
241 * <p>
242 * Overridden to compare the two versions.
243 * </p>
244 */
245 @Override
246 public int compareTo(final Version other) {
247
248 int compare = 0;
249
250 // Check to make sure UNKNOWN is the highest version.
251 if (UNKNOWN.equals(this)) {
252 compare = UNKNOWN.equals(other) ? 0 : 1;
253 }
254 else if (UNKNOWN.equals(other)) {
255 compare = -1;
256 }
257
258 final int fields = Math.min(myVersion.length, other.myVersion.length);
259 for (int i = 0; (compare == 0) && (i < fields); ++i) {
260 compare = compare(myVersion[i], other.myVersion[i]);
261 }
262
263 if (compare == 0) {
264 compare = compare(myVersion.length, other.myVersion.length);
265 if (compare == 0) {
266 compare = mySuffix.compareTo(other.mySuffix);
267 }
268 }
269
270 return compare;
271 }
272
273 /**
274 * Determines if the passed object is of this same type as this object and
275 * if so that its fields are equal.
276 *
277 * @param object
278 * The object to compare to.
279 *
280 * @see java.lang.Object#equals(java.lang.Object)
281 */
282 @Override
283 public boolean equals(final Object object) {
284 boolean result = false;
285 if (this == object) {
286 result = true;
287 }
288 else if ((object != null) && (getClass() == object.getClass())) {
289 final Version other = (Version) object;
290
291 result = mySuffix.equals(other.mySuffix)
292 && Arrays.equals(myVersion, other.myVersion);
293 }
294 return result;
295 }
296
297 /**
298 * Computes a reasonable hash code.
299 *
300 * @return The hash code value.
301 */
302 @Override
303 public int hashCode() {
304 int result = 1;
305 result = (31 * result) + mySuffix.hashCode();
306 result = (31 * result) + Arrays.hashCode(myVersion);
307 return result;
308 }
309
310 /**
311 * {@inheritDoc}
312 * <p>
313 * Overridden to produce a human readable string for the version.
314 * </p>
315 */
316 @Override
317 public String toString() {
318 final StringBuilder builder = new StringBuilder();
319 for (int i = 0; i < myVersion.length; ++i) {
320 if (i != 0) {
321 builder.append('.');
322 }
323 builder.append(String.valueOf(myVersion[i]));
324 }
325 if (!mySuffix.isEmpty()) {
326 if (myVersion.length > 0) {
327 builder.append('-');
328 }
329 builder.append(mySuffix);
330 }
331 return builder.toString();
332 }
333
334 /**
335 * Compares two {@code int} values numerically. The value returned is
336 * identical to what would be returned by:
337 *
338 * <pre>
339 * Integer.valueOf(x).compareTo(Integer.valueOf(y))
340 * </pre>
341 *
342 * @param x
343 * the first {@code int} to compare
344 * @param y
345 * the second {@code int} to compare
346 * @return the value {@code 0} if {@code x == y}; a value less than
347 * {@code 0} if {@code x < y}; and a value greater than {@code 0} if
348 * {@code x > y}
349 * @since 1.7
350 */
351 protected int compare(final int x, final int y) {
352 return (x < y) ? -1 : ((x == y) ? 0 : 1);
353 }
354
355 }