001/*
002 * %% Ignore-License %%
003 * Copyright (c) OSGi Alliance (2004, 2009). All Rights Reserved.
004 * 
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package fr.ifremer.adagio.core.dao.technical;
019
020/*
021 * #%L
022 * SIH-Adagio Core Shared
023 * $Id: Version.java 11866 2013-12-02 15:53:58Z tc1fbb1 $
024 * $HeadURL: https://forge.ifremer.fr/svn/sih-adagio/tags/adagio-3.5.6/core-shared/src/main/java/fr/ifremer/adagio/core/dao/technical/Version.java $
025 * %%
026 * Copyright (C) 2012 - 2013 Ifremer
027 * %%
028 * This program is free software: you can redistribute it and/or modify
029 * it under the terms of the GNU Affero General Public License as published by
030 * the Free Software Foundation, either version 3 of the License, or
031 * (at your option) any later version.
032 * 
033 * This program is distributed in the hope that it will be useful,
034 * but WITHOUT ANY WARRANTY; without even the implied warranty of
035 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
036 * GNU General Public License for more details.
037 * 
038 * You should have received a copy of the GNU Affero General Public License
039 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
040 * #L%
041 */
042
043import java.util.NoSuchElementException;
044import java.util.StringTokenizer;
045
046/**
047 * Version identifier for bundles and packages.
048 * <p/>
049 * <p/>
050 * Version identifiers have four components.
051 * <ol>
052 * <li>Major version. A non-negative integer.</li>
053 * <li>Minor version. A non-negative integer.</li>
054 * <li>Micro version. A non-negative integer.</li>
055 * <li>Qualifier. A text string. See <code>Version(String)</code> for the
056 * format of the qualifier string.</li>
057 * </ol>
058 * <p/>
059 * <p/>
060 * <code>Version</code> objects are immutable.
061 *
062 * @Immutable
063 */
064
065public class Version implements Comparable<Version> {
066    private final int major;
067
068    private final int minor;
069
070    private final int micro;
071
072    private final String qualifier;
073
074    private static final String SEPARATOR = "."; //$NON-NLS-1$
075
076    /**
077     * The empty version "0.0.0".
078     */
079    public static final Version emptyVersion = new Version(0, 0, 0);
080
081    /**
082     * Creates a version identifier from the specified numerical components.
083     * <p/>
084     * <p/>
085     * The qualifier is set to the empty string.
086     *
087     * @param major Major component of the version identifier.
088     * @param minor Minor component of the version identifier.
089     * @param micro Micro component of the version identifier.
090     * @throws IllegalArgumentException If the numerical components are
091     *                                  negative.
092     */
093    public Version(int major, int minor, int micro) {
094        this(major, minor, micro, null);
095    }
096
097    /**
098     * Creates a version identifier from the specified components.
099     *
100     * @param major     Major component of the version identifier.
101     * @param minor     Minor component of the version identifier.
102     * @param micro     Micro component of the version identifier.
103     * @param qualifier Qualifier component of the version identifier. If
104     *                  <code>null</code> is specified, then the qualifier will be set to
105     *                  the empty string.
106     * @throws IllegalArgumentException If the numerical components are negative
107     *                                  or the qualifier string is invalid.
108     */
109    public Version(int major, int minor, int micro, String qualifier) {
110        if (qualifier == null) {
111            qualifier = ""; //$NON-NLS-1$
112        }
113
114        this.major = major;
115        this.minor = minor;
116        this.micro = micro;
117        this.qualifier = qualifier;
118        validate();
119    }
120
121    /**
122     * Created a version identifier from the specified string.
123     * <p/>
124     * <p/>
125     * Here is the grammar for version strings.
126     * <p/>
127     * <pre>
128     * version ::= major('.'minor('.'micro('.'qualifier)?)?)?
129     * major ::= digit+
130     * minor ::= digit+
131     * micro ::= digit+
132     * qualifier ::= (alpha|digit|'_'|'-')+
133     * digit ::= [0..9]
134     * alpha ::= [a..zA..Z]
135     * </pre>
136     * <p/>
137     * There must be no whitespace in version.
138     *
139     * @param version String representation of the version identifier.
140     * @throws IllegalArgumentException If <code>version</code> is improperly
141     *                                  formatted.
142     */
143    public Version(String version) {
144        int maj = 0;
145        int min = 0;
146        int mic = 0;
147        String qual = ""; //$NON-NLS-1$
148
149        try {
150            StringTokenizer st = new StringTokenizer(version, SEPARATOR, true);
151            maj = Integer.parseInt(st.nextToken());
152
153            if (st.hasMoreTokens()) {
154                st.nextToken(); // consume delimiter
155                min = Integer.parseInt(st.nextToken());
156
157                if (st.hasMoreTokens()) {
158                    st.nextToken(); // consume delimiter
159                    mic = Integer.parseInt(st.nextToken());
160
161                    if (st.hasMoreTokens()) {
162                        st.nextToken(); // consume delimiter
163                        qual = st.nextToken();
164
165                        if (st.hasMoreTokens()) {
166                            throw new IllegalArgumentException("invalid format"); //$NON-NLS-1$
167                        }
168                    }
169                }
170            }
171        } catch (NoSuchElementException e) {
172            throw new IllegalArgumentException("invalid format"); //$NON-NLS-1$
173        }
174
175        major = maj;
176        minor = min;
177        micro = mic;
178        qualifier = qual;
179        validate();
180    }
181
182    /**
183     * Called by the Version constructors to validate the version components.
184     *
185     * @throws IllegalArgumentException If the numerical components are negative
186     *                                  or the qualifier string is invalid.
187     */
188    private void validate() {
189        if (major < 0) {
190            throw new IllegalArgumentException("negative major"); //$NON-NLS-1$
191        }
192        if (minor < 0) {
193            throw new IllegalArgumentException("negative minor"); //$NON-NLS-1$
194        }
195        if (micro < 0) {
196            throw new IllegalArgumentException("negative micro"); //$NON-NLS-1$
197        }
198        char[] chars = qualifier.toCharArray();
199        for (int i = 0, length = chars.length; i < length; i++) {
200            char ch = chars[i];
201            if (('A' <= ch) && (ch <= 'Z')) {
202                continue;
203            }
204            if (('a' <= ch) && (ch <= 'z')) {
205                continue;
206            }
207            if (('0' <= ch) && (ch <= '9')) {
208                continue;
209            }
210            if ((ch == '_') || (ch == '-')) {
211                continue;
212            }
213            throw new IllegalArgumentException(
214                    "invalid qualifier: " + qualifier); //$NON-NLS-1$
215        }
216    }
217
218    /**
219     * Parses a version identifier from the specified string.
220     * <p/>
221     * <p/>
222     * See <code>Version(String)</code> for the format of the version string.
223     *
224     * @param version String representation of the version identifier. Leading
225     *                and trailing whitespace will be ignored.
226     * @return A <code>Version</code> object representing the version
227     * identifier. If <code>version</code> is <code>null</code> or
228     * the empty string then <code>emptyVersion</code> will be
229     * returned.
230     * @throws IllegalArgumentException If <code>version</code> is improperly
231     *                                  formatted.
232     */
233    public static Version parseVersion(String version) {
234        if (version == null) {
235            return emptyVersion;
236        }
237
238        version = version.trim();
239        if (version.length() == 0) {
240            return emptyVersion;
241        }
242
243        return new Version(version);
244    }
245
246    /**
247     * Returns the major component of this version identifier.
248     *
249     * @return The major component.
250     */
251    public int getMajor() {
252        return major;
253    }
254
255    /**
256     * Returns the minor component of this version identifier.
257     *
258     * @return The minor component.
259     */
260    public int getMinor() {
261        return minor;
262    }
263
264    /**
265     * Returns the micro component of this version identifier.
266     *
267     * @return The micro component.
268     */
269    public int getMicro() {
270        return micro;
271    }
272
273    /**
274     * Returns the qualifier component of this version identifier.
275     *
276     * @return The qualifier component.
277     */
278    public String getQualifier() {
279        return qualifier;
280    }
281
282    /**
283     * Returns the string representation of this version identifier.
284     * <p/>
285     * <p/>
286     * The format of the version string will be <code>major.minor.micro</code>
287     * if qualifier is the empty string or
288     * <code>major.minor.micro.qualifier</code> otherwise.
289     *
290     * @return The string representation of this version identifier.
291     */
292    public String toString() {
293        int q = qualifier.length();
294        StringBuffer result = new StringBuffer(20 + q);
295        result.append(major);
296        result.append(SEPARATOR);
297        result.append(minor);
298        result.append(SEPARATOR);
299        result.append(micro);
300        if (q > 0) {
301            result.append(SEPARATOR);
302            result.append(qualifier);
303        }
304        return result.toString();
305    }
306
307    /**
308     * Returns a hash code value for the object.
309     *
310     * @return An integer which is a hash code value for this object.
311     */
312    public int hashCode() {
313        return (major << 24) + (minor << 16) + (micro << 8)
314               + qualifier.hashCode();
315    }
316
317    /**
318     * Compares this <code>Version</code> object to another object.
319     * <p/>
320     * <p/>
321     * A version is considered to be <b>equal to </b> another version if the
322     * major, minor and micro components are equal and the qualifier component
323     * is equal (using <code>String.equals</code>).
324     *
325     * @param object The <code>Version</code> object to be compared.
326     * @return <code>true</code> if <code>object</code> is a
327     * <code>Version</code> and is equal to this object;
328     * <code>false</code> otherwise.
329     */
330    public boolean equals(Object object) {
331        if (object == this) { // quicktest
332            return true;
333        }
334
335        if (!(object instanceof Version)) {
336            return false;
337        }
338
339        Version other = (Version) object;
340        return (major == other.major) && (minor == other.minor)
341               && (micro == other.micro) && qualifier.equals(other.qualifier);
342    }
343
344    /**
345     * Compares this <code>Version</code> object to another object.
346     * <p/>
347     * <p/>
348     * A version is considered to be <b>less than </b> another version if its
349     * major component is less than the other version's major component, or the
350     * major components are equal and its minor component is less than the other
351     * version's minor component, or the major and minor components are equal
352     * and its micro component is less than the other version's micro component,
353     * or the major, minor and micro components are equal and it's qualifier
354     * component is less than the other version's qualifier component (using
355     * <code>String.compareTo</code>).
356     * <p/>
357     * <p/>
358     * A version is considered to be <b>equal to</b> another version if the
359     * major, minor and micro components are equal and the qualifier component
360     * is equal (using <code>String.compareTo</code>).
361     *
362     * @param object The <code>Version</code> object to be compared.
363     * @return A negative integer, zero, or a positive integer if this object is
364     * less than, equal to, or greater than the specified
365     * <code>Version</code> object.
366     * @throws ClassCastException If the specified object is not a
367     *                            <code>Version</code>.
368     */
369    public int compareTo(Version object) {
370        if (object == this) { // quicktest
371            return 0;
372        }
373
374        Version other = (Version) object;
375
376        int result = major - other.major;
377        if (result != 0) {
378            return result;
379        }
380
381        result = minor - other.minor;
382        if (result != 0) {
383            return result;
384        }
385
386        result = micro - other.micro;
387        if (result != 0) {
388            return result;
389        }
390
391        return qualifier.compareTo(other.qualifier);
392    }
393}