001: /*
002: * The contents of this file are subject to the Mozilla Public License
003: * Version 1.1 (the "License"); you may not use this file except in
004: * compliance with the License. You may obtain a copy of the License at
005: * http://www.mozilla.org/MPL/
006: *
007: * Software distributed under the License is distributed on an "AS IS"
008: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
009: * License for the specific language governing rights and limitations
010: * under the License.
011: *
012: * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
013: *
014: * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
015: * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
016: *
017: * Contributor(s):
018: * Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
019: *
020: * If you didn't download this code from the following link, you should check
021: * if you aren't using an obsolete version: http://www.isqlviewer.com
022: */
023: package org.isqlviewer.util;
024:
025: import java.io.Serializable;
026: import java.text.MessageFormat;
027: import java.util.NoSuchElementException;
028: import java.util.StringTokenizer;
029:
030: /**
031: * Version identifier for software bundles and packages.
032: * <p>
033: * This Version class supports four four components.
034: * <ol>
035: * <li>Major version. A non-negative integer.</li>
036: * <li>Minor version. A non-negative integer.</li>
037: * <li>Patch version. A non-negative integer.</li>
038: * <li>Qualifier. A text string. See <code>Version(String)</code> for the format of the qualifier string.</li>
039: * </ol>
040: * <p>
041: * All Version object instances are immutable.
042: *
043: * @author Mark A. Kobold <mkobold at isqlviewer dot com>
044: * @version 1.0
045: */
046: public class Version implements Comparable<Version>, Serializable {
047:
048: private static final long serialVersionUID = 1L;
049: private static final String SEPARATOR = ".";
050: private static final String BUNDLE_IDENTIFIER = "org.isqlviewer.util.ResourceBundle.properties";
051: private static final MessageFormat baseFormat = new MessageFormat(
052: "{0}.{1}.{2}");
053:
054: private final int major;
055: private final int minor;
056: private final int patch;
057: private final String qualifier;
058: private LocalMessages messages = new LocalMessages(
059: BUNDLE_IDENTIFIER);
060:
061: /**
062: * The empty version "0.0.0". Equivalent to calling <code>new Version(0,0,0)</code>.
063: */
064: public static final Version EMPTY_VERSION = new Version(0, 0, 0);
065:
066: /**
067: * Creates a version identifier from the specified numerical components.
068: * <p>
069: * The qualifier is set to the empty string.
070: *
071: * @param major Major component of the version identifier.
072: * @param minor Minor component of the version identifier.
073: * @param micro Micro component of the version identifier.
074: * @throws IllegalArgumentException If the numerical components are negative.
075: */
076: public Version(int major, int minor, int micro) {
077:
078: this (major, minor, micro, null);
079: }
080:
081: /**
082: * Creates a version identifier from the specifed components.
083: *
084: * @param major Major component of the version identifier.
085: * @param minor Minor component of the version identifier.
086: * @param micro Micro component of the version identifier.
087: * @param qualifier Qualifier component of the version identifier. If <code>null</code> is specified, then the
088: * qualifier will be set to the empty string.
089: * @throws IllegalArgumentException If the numerical components are negative or the qualifier string is invalid.
090: */
091: public Version(int major, int minor, int micro, String qualifier) {
092:
093: this .major = major;
094: this .minor = minor;
095: this .patch = micro;
096:
097: if (qualifier == null) {
098: this .qualifier = "";
099: } else {
100: this .qualifier = qualifier;
101: }
102: validateComponents();
103: }
104:
105: /**
106: * Parses a version identifier from the specified string.
107: * <p>
108: * Standard format for a version string.
109: *
110: * <pre>
111: * VERSION ::= MAJOR [SEPERATOR MINOR [SEPERATOR PATCH [SEPERATOR QUALIFIER] ] ]
112: * MAJOR ::= (DIGIT)+
113: * MINOR ::= (DIGIT)+
114: * PATCH ::= (DIGIT)+
115: * QUALIFIER ::= (ALPHA|DIGIT|'_'|'-')+
116: * SEPERATOR ::= '.'
117: * DIGIT ::= {0..9}
118: * ALPHA ::= {a..zA..Z}
119: * </pre>
120: *
121: * @param version String representation of the version identifier.
122: * @return new Version instance with the respective components from the version text.
123: * @throws IllegalArgumentException If version text is not properly formatted.
124: */
125: public static Version forString(String version) {
126:
127: if (version == null) {
128: return EMPTY_VERSION;
129: }
130:
131: String workingCopy = version.trim();
132: if (workingCopy.length() == 0) {
133: return EMPTY_VERSION;
134: }
135: int major = 0;
136: int minor = 0;
137: int micro = 0;
138: String qualifier = "";
139: StringTokenizer st = new StringTokenizer(workingCopy,
140: SEPARATOR, true);
141: try {
142: major = Integer.parseInt(st.nextToken());
143: if (st.hasMoreTokens()) {
144: st.nextToken();
145: minor = Integer.parseInt(st.nextToken());
146: if (st.hasMoreTokens()) {
147: st.nextToken();
148: micro = Integer.parseInt(st.nextToken());
149: if (st.hasMoreTokens()) {
150: st.nextToken();
151: qualifier = st.nextToken();
152: }
153: }
154: }
155: } catch (NoSuchElementException e) {
156: throw new IllegalArgumentException();
157: }
158: return new Version(major, minor, micro, qualifier);
159: }
160:
161: /**
162: * Returns the major component of this version identifier.
163: *
164: * @return The major component.
165: */
166: public int getMajor() {
167:
168: return major;
169: }
170:
171: /**
172: * Returns the minor component of this version identifier.
173: *
174: * @return The minor component.
175: */
176: public int getMinor() {
177:
178: return minor;
179: }
180:
181: /**
182: * Returns the patch component of this version identifier.
183: *
184: * @return The patch component.
185: */
186: public int getPatch() {
187:
188: return patch;
189: }
190:
191: /**
192: * Returns the qualifier component of this version identifier.
193: *
194: * @return The qualifier component.
195: */
196: public String getQualifier() {
197:
198: return qualifier;
199: }
200:
201: /**
202: * Returns the string representation of this version identifier.
203: * <p>
204: * The format of the version string will be <code>major.minor.micro</code> if qualifier is the empty string or
205: * <code>major.minor.micro.qualifier</code> otherwise.
206: *
207: * @return The string representation of this version identifier.
208: */
209: public String toString() {
210:
211: Object[] argument = new Object[3];
212: argument[0] = Integer.toString(major);
213: argument[1] = Integer.toString(minor);
214: argument[2] = Integer.toString(patch);
215: String versionText = baseFormat.format(argument);
216: if (qualifier.length() == 0) {
217: return versionText;
218: }
219: return versionText.concat(SEPARATOR.concat(qualifier));
220: }
221:
222: /**
223: * Returns a hash code value for the object.
224: * <p>
225: *
226: * @return An integer which is a hash code value for this object.
227: */
228: public int hashCode() {
229:
230: return (major << 24) + (minor << 16) + (patch << 8)
231: + qualifier.hashCode();
232: }
233:
234: /**
235: * Compares this instance to another object.
236: * <p>
237: * A version is considered to be <b>equal to </b> another version if the version components (major, minor, micro,
238: * qualifier) are equal.
239: *
240: * @param object to test for equality.
241: * @return <tt>true</tt> if object is a Version and is equal to this instance. Otherwise <tt>false</tt>.
242: */
243: public boolean equals(Object object) {
244:
245: if (object instanceof Version) {
246: Version other = (Version) object;
247: boolean eqMajor = (major == other.major);
248: boolean eqMinor = (minor == other.minor);
249: boolean eqPatch = (patch == other.patch);
250: boolean eqQualifier = qualifier.equals(other.qualifier);
251: return eqMajor && eqMinor && eqPatch && eqQualifier;
252: }
253: return false;
254: }
255:
256: /**
257: * Compares this instance to another version.
258: * <p>
259: * A version is considered to be <b>less than </b> another version if its major component is less than the other
260: * version's major component, or the major components are equal and its minor component is less than the other
261: * version's minor component, or the major and minor components are equal and its micro component is less than the
262: * other version's micro component, or the major, minor and micro components are equal and it's qualifier component
263: * is less than the other version's qualifier component (using <code>String.compareTo</code>).
264: * <p>
265: * A version is considered to be equal to< another version if the all version components (major, minor, micro, and
266: * qualifier) are equal.
267: *
268: * @param object The <code>Version</code> object to be compared.
269: * @return Comparsion value defined by the comparable interface.
270: */
271: public int compareTo(Version version) {
272:
273: if (version == this ) {
274: return 0;
275: }
276: int comparison = -1;
277:
278: comparison = (major < version.major ? -1
279: : (major == version.major ? 0 : 1));
280: if (comparison != 0) {
281: return comparison;
282: }
283: comparison = (minor < version.minor ? -1
284: : (minor == version.minor ? 0 : 1));
285: if (comparison != 0) {
286: return comparison;
287: }
288: comparison = (patch < version.patch ? -1
289: : (patch == version.patch ? 0 : 1));
290: if (comparison != 0) {
291: return comparison;
292: }
293: return qualifier.compareTo(version.qualifier);
294: }
295:
296: /**
297: * Called by the constructors to ensure the correctness of the version components.
298: *
299: * @throws IllegalArgumentException If the numerical components are negative or the qualifier string is invalid.
300: */
301: private void validateComponents() {
302:
303: if (major < 0) {
304: throw new IllegalArgumentException(messages.format(
305: "Version.bad_major", Integer.toString(major)));
306: }
307: if (minor < 0) {
308: throw new IllegalArgumentException(messages.format(
309: "Version.bad_minor", Integer.toString(minor)));
310: }
311: if (patch < 0) {
312: throw new IllegalArgumentException(messages.format(
313: "Version.bad_patch", Integer.toString(patch)));
314: }
315: int length = qualifier.length();
316: for (int i = 0; i < length; i++) {
317: char cdata = qualifier.charAt(i);
318: if (!Character.isLetterOrDigit(cdata)) {
319: switch (cdata) {
320: case '_':
321: case '-':
322: break;
323: default:
324: Object[] arguments = new Object[2];
325: arguments[0] = Integer.toString(i);
326: arguments[1] = Character.toString(cdata);
327: throw new IllegalArgumentException(messages.format(
328: "Version.bad_text", arguments));
329: }
330: }
331: }
332: }
333:
334: }
|