001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2006, Geotools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.util;
017:
018: // J2SE dependencies
019: import java.io.Serializable;
020: import java.util.regex.Pattern;
021:
022: /**
023: * Holds a version number. Versions are often of the form <code>{@linkplain #getMajor
024: * major}.{@linkplain #getMinor minor}.{@linkplain #getRevision revision}</code>, but
025: * are not required to. For example an EPSG database version is {@code "6.11.2"}. The
026: * separator character is the dot.
027: * <p>
028: * This class provides convenience methods for fetching the major, minor and reversion
029: * numbers, and for performing comparaisons.
030: *
031: * @since 2.4
032: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/metadata/src/main/java/org/geotools/util/Version.java $
033: * @version $Id: Version.java 25193 2007-04-18 13:37:38Z desruisseaux $
034: * @author Martin Desruisseaux
035: *
036: * @see org.geotools.factory.GeoTools#getVersion
037: */
038: public class Version implements CharSequence, Comparable, Serializable {
039: /**
040: * For cross-version compatibility.
041: */
042: private static final long serialVersionUID = -6793384507333713770L;
043:
044: /**
045: * The separator character between {@linkplain #getMajor major}, {@linkplain #getMinor minor}
046: * and {@linkplain #getRevision revision} components.
047: */
048: public static final char SEPARATOR = '.';
049:
050: /**
051: * The pattern to use for splitting version numbers.
052: */
053: private static final Pattern PATTERN = Pattern.compile("\\"
054: + SEPARATOR);
055:
056: /**
057: * The version in string form, with leading and trailing spaces removed.
058: */
059: private final String version;
060:
061: /**
062: * The components of the version string. Will be created when first needed.
063: */
064: private transient String[] components;
065:
066: /**
067: * The parsed components of the version string. Will be created when first needed.
068: */
069: private transient Comparable[] parsed;
070:
071: /**
072: * The hash code value. Will be computed when first needed.
073: */
074: private transient int hashCode;
075:
076: /**
077: * Creates a new version object from the supplied string.
078: */
079: public Version(final String version) {
080: this .version = version.trim();
081: }
082:
083: /**
084: * Returns the major version number. This method returns an {@link Integer} if possible,
085: * or a {@link String} otherwise.
086: */
087: public Comparable getMajor() {
088: return getComponent(0);
089: }
090:
091: /**
092: * Returns the minor version number. This method returns an {@link Integer} if possible,
093: * or a {@link String} otherwise. If there is no minor version number, then this method
094: * returns {@code null}.
095: */
096: public Comparable getMinor() {
097: return getComponent(1);
098: }
099:
100: /**
101: * Returns the revision number. This method returns an {@link Integer} if possible,
102: * or a {@link String} otherwise. If there is no revision number, then this method
103: * returns {@code null}.
104: */
105: public Comparable getRevision() {
106: return getComponent(2);
107: }
108:
109: /**
110: * Returns the specified components of this version string. For a version of the
111: * {@code major.minor.revision} form, index 0 stands for the major version number,
112: * 1 stands for the minor version number and 2 stands for the revision number.
113: * <p>
114: * The return value is an {@link Integer} if the component is parsable as an integer,
115: * or a {@link String} otherwise. If there is no component at the specified index,
116: * then this method returns {@code null}.
117: *
118: * @param index The index of the component to fetch.
119: * @return The value at the specified index, or {@code null} if none.
120: * @throws IndexOutOfBoundsException if {@code index} is negative.
121: */
122: public synchronized Comparable getComponent(final int index) {
123: if (parsed == null) {
124: if (components == null) {
125: components = PATTERN.split(version);
126: }
127: parsed = new Comparable[components.length];
128: }
129: if (index >= parsed.length) {
130: return null;
131: }
132: Comparable candidate = parsed[index];
133: if (candidate == null) {
134: final String value = components[index].trim();
135: try {
136: candidate = Integer.valueOf(value);
137: } catch (NumberFormatException e) {
138: candidate = value;
139: }
140: parsed[index] = candidate;
141: }
142: return candidate;
143: }
144:
145: /**
146: * Get the rank of the specified object according this type.
147: * This is for {@link #compareTo(Version, int)} internal only.
148: */
149: private static int getTypeRank(final Object value) {
150: if (value instanceof CharSequence) {
151: return 0;
152: }
153: if (value instanceof Number) {
154: return 1;
155: }
156: throw new IllegalArgumentException(String.valueOf(value));
157: }
158:
159: /**
160: * Compares this version with an other version object, up to the specified limit. A limit
161: * of 1 compares only the {@linkplain #getMajor major} version number. A limit of 2 compares
162: * the major and {@linkplain #getMinor minor} version numbers, <cite>etc</cite>. The
163: * comparaisons are performed as {@link Integer} object if possible, or as {@link String}
164: * otherwise.
165: *
166: * @param other The other version object to compare with.
167: * @param limit The maximum number of components to compare.
168: * @return A negative value if this version is lower than the supplied version, a positive
169: * value if it is higher, or 0 if they are equal.
170: */
171: public int compareTo(final Version other, final int limit) {
172: for (int i = 0; i < limit; i++) {
173: final Comparable v1 = this .getComponent(i);
174: final Comparable v2 = other.getComponent(i);
175: if (v1 == null) {
176: return (v2 == null) ? 0 : -1;
177: } else if (v2 == null) {
178: return +1;
179: }
180: int c = getTypeRank(v1) - getTypeRank(v2);
181: if (c != 0) {
182: /*
183: * One value is a text while the other value is a number. We could be tempted to
184: * force a comparaison by converting the number to a String and then invoking the
185: * String.compareTo(String) method, but this strategy would violate the following
186: * contract from Comparable.compareTo(Object): "The implementor must also ensure
187: * that the relation is transitive". Use case:
188: *
189: * A is the integer 10
190: * B is the string "8Z"
191: * C is the integer 5.
192: *
193: * If mismatched types are converted to String before being compared, then we
194: * would have A < B < C. Transitivity implies that A < C, but if we compare A
195: * and C directly we get A > C because they are compared as numbers. An easy
196: * way to fix this inconsistency is to define all String as lexicographically
197: * preceding Integer, no matter their content. This is what we do here.
198: */
199: return c;
200: }
201: c = v1.compareTo(v2);
202: if (c != 0) {
203: return c;
204: }
205: }
206: return 0;
207: }
208:
209: /**
210: * Compares this version with an other version object. This method performs the same
211: * comparaison than {@link #compareTo(Version, int)} with no limit.
212: *
213: * @param other The other version object to compare with.
214: * @return A negative value if this version is lower than the supplied version, a positive
215: * value if it is higher, or 0 if they are equal.
216: */
217: public int compareTo(final Object other) {
218: return compareTo((Version) other, Integer.MAX_VALUE);
219: }
220:
221: /**
222: * Compare this version string with the specified object for equality. Two version are
223: * considered equal if <code>{@linkplain #compareTo(Object) compareTo}(other) == 0</code>.
224: */
225: public boolean equals(final Object other) {
226: if (other != null && getClass().equals(other.getClass())) {
227: return compareTo(other) == 0;
228: }
229: return false;
230: }
231:
232: /**
233: * Returns the length of the version string.
234: */
235: public int length() {
236: return version.length();
237: }
238:
239: /**
240: * Returns the {@code char} value at the specified index.
241: */
242: public char charAt(final int index) {
243: return version.charAt(index);
244: }
245:
246: /**
247: * Returns a new version string that is a subsequence of this sequence.
248: */
249: public CharSequence subSequence(final int start, final int end) {
250: return version.subSequence(start, end);
251: }
252:
253: /**
254: * Returns the version string. This is the string specified at construction time.
255: */
256: public String toString() {
257: return version;
258: }
259:
260: /**
261: * Returns a hash code value for this version.
262: */
263: public int hashCode() {
264: if (hashCode == 0) {
265: int code = (int) serialVersionUID;
266: int index = 0;
267: Comparable component;
268: while ((component = getComponent(index)) != null) {
269: code = code * 37 + component.hashCode();
270: index++;
271: }
272: hashCode = code;
273: }
274: return hashCode;
275: }
276: }
|