001    /*
002     * Copyright 2004-2006 Stephen J. McConnell.
003     * Copyright 1999-2004 The Apache Software Foundation
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    package net.dpml.lang;
018    
019    import java.io.Serializable;
020    
021    import java.util.StringTokenizer;
022    
023    /**
024     * This class is used to hold version information.
025     * <p />
026     *
027     * The version number is made up of three dot-separated fields:
028     * <p />
029     * &quot;<b>major.minor.micro</b>&quot;
030     * <p />
031     * The <b>major</b>, <b>minor</b> and <b>micro</b> fields are
032     * <i>integer</i> numbers represented in decimal notation and have the
033     * following meaning:
034     * <ul>
035     *
036     * <p /><li><b>major</b> - When the major version changes (in ex. from
037     * &quot;1.5.12&quot; to &quot;2.0.0&quot;), then backward compatibility
038     * with previous releases is not granted.</li><p />
039     *
040     * <p /><li><b>minor</b> - When the minor version changes (in ex. from
041     * &quot;1.5.12&quot; to &quot;1.6.0&quot;), then backward compatibility
042     * with previous releases is granted, but something changed in the
043     * implementation of the Component. (ie it methods could have been added)</li><p />
044     *
045     * <p /><li><b>micro</b> - When the micro version changes (in ex.
046     * from &quot;1.5.12&quot; to &quot;1.5.13&quot;), then the the changes are
047     * small forward compatible bug fixes or documentation modifications etc.
048     * </li>
049     * </ul>
050     *
051     * @author <a href="http://www.dpml.net">Digital Product Management Library</a>
052     * @version 2.1.1
053     */
054    public final class Version implements Comparable, Serializable
055    {
056       /**
057        * Version -1.0.0.
058        */
059        public static final Version NULL_VERSION = new Version( -1, 0, 0 );
060        
061       /**
062        * Serial version identifier.
063        */
064        static final long serialVersionUID = 1L;
065    
066        private int m_major;
067        private int m_minor;
068        private int m_micro;
069    
070        /**
071         * Parse a version out of a string.
072         * The version string format is <major>.<minor>.<micro> where
073         * both minor and micro are optional.
074         *
075         * @param version The input version string
076         * @return the new Version object
077         * @throws NumberFormatException if an error occurs
078         * @throws IllegalArgumentException if an error occurs
079         * @throws NullPointerException if the version argument is <code>null</code>.
080         * @since 4.1
081         */
082        public static Version parse( final String version )
083            throws NumberFormatException, IllegalArgumentException, NullPointerException
084        {
085            if( version == null )
086            {
087                throw new NullPointerException( "version" );
088            }
089    
090            final StringTokenizer tokenizer = new StringTokenizer( version, "." );
091            final String[] levels = new String[ tokenizer.countTokens() ];
092            for( int i = 0; i < levels.length; i++ )
093            {
094                levels[ i ] = tokenizer.nextToken();
095            }
096    
097            int major = -1;
098            if( 0 < levels.length )
099            {
100                major = Integer.parseInt( levels[ 0 ] );
101            }
102    
103            int minor = 0;
104            if( 1 < levels.length )
105            {
106                minor = Integer.parseInt( levels[ 1 ] );
107            }
108    
109            int micro = 0;
110            if( 2 < levels.length )
111            {
112                micro = Integer.parseInt( levels[ 2 ] );
113            }
114    
115            return new Version( major, minor, micro );
116        }
117    
118        /**
119         * Create a new instance of a <code>Version</code> object with the
120         * specified version numbers.
121         *
122         * @param major This <code>Version</code> major number.
123         * @param minor This <code>Version</code> minor number.
124         * @param micro This <code>Version</code> micro number.
125         */
126        public Version( final int major, final int minor, final int micro )
127        {
128            m_major = major;
129            m_minor = minor;
130            m_micro = micro;
131        }
132    
133        /**
134         * Retrieve major component of version.
135         *
136         * @return the major component of version
137         * @since 4.1
138         */
139        public int getMajor()
140        {
141            return m_major;
142        }
143    
144        /**
145         * Retrieve minor component of version.
146         *
147         * @return the minor component of version
148         * @since 4.1
149         */
150        public int getMinor()
151        {
152            return m_minor;
153        }
154    
155        /**
156         * Retrieve micro component of version.
157         *
158         * @return the micro component of version.
159         * @since 4.1
160         */
161        public int getMicro()
162        {
163            return m_micro;
164        }
165    
166        /**
167         * Check this <code>Version</code> against another for equality.
168         * <p />
169         * If this <code>Version</code> is compatible with the specified one, then
170         * <b>true</b> is returned, otherwise <b>false</b>.
171         *
172         * @param other The other <code>Version</code> object to be compared with this
173         *          for equality.
174         * @return <b>true</b> if this <code>Version</code> is compatible with the specified one
175         * @since 4.1
176         */
177        public boolean equals( final Version other )
178        {
179            if( other == null )
180            {
181                return false;
182            }
183            boolean isEqual = ( getMajor() == other.getMajor() );
184            if( isEqual )
185            {
186                isEqual = ( getMinor() == other.getMinor() );
187            }
188            if( isEqual )
189            {
190                isEqual = ( getMicro() == other.getMicro() );
191            }
192            return isEqual;
193        }
194    
195        /**
196         * Indicates whether some other object is "equal to" this <code>Version</code>.
197         * Returns <b>true</b> if the other object is an instance of <code>Version</code>
198         * and has the same major, minor, and micro components.
199         *
200         * @param other an <code>Object</code> value
201         * @return <b>true</b> if the other object is equal to this <code>Version</code>
202         */
203        public boolean equals( final Object other )
204        {
205            boolean isEqual = false;
206            if( other instanceof Version )
207            {
208                isEqual = equals( (Version) other );
209            }
210            return isEqual;
211        }
212    
213        /**
214         * Add a hashing function to ensure the Version object is
215         * treated as expected in hashmaps and sets.  NOTE: any
216         * time the equals() is overridden, hashCode() should also
217         * be overridden.
218         *
219         * @return the hashCode
220         */
221        public int hashCode()
222        {
223            int hash = 61486123 * getMajor();
224            hash = hash + 1273621 * getMinor();
225            hash = hash + 8912738 * getMicro();
226            return hash;
227        }
228    
229        /**
230         * Check this <code>Version</code> against another for compliancy
231         * (compatibility).
232         * <p />
233         * If this <code>Version</code> is compatible with the specified one, then
234         * <b>true</b> is returned, otherwise <b>false</b>. Be careful when using
235         * this method since, in example, version 1.3.7 is compliant to version
236         * 1.3.6, while the opposite is not.
237         * <p />
238         * The following example displays the expected behaviour and results of version.
239         * <pre>
240         * final Version v1 = new Version( 1, 3, 6 );
241         * final Version v2 = new Version( 1, 3, 7 );
242         * final Version v3 = new Version( 1, 4, 0 );
243         * final Version v4 = new Version( 2, 0, 1 );
244         *
245         * assert(   v1.complies( v1 ) );
246         * assert( ! v1.complies( v2 ) );
247         * assert(   v2.complies( v1 ) );
248         * assert( ! v1.complies( v3 ) );
249         * assert(   v3.complies( v1 ) );
250         * assert( ! v1.complies( v4 ) );
251         * assert( ! v4.complies( v1 ) );
252         * </pre>
253         *
254         * @param other The other <code>Version</code> object to be compared with this
255         *              for compliancy (compatibility).
256         * @return <b>true</b> if this <code>Version</code> is compatible with the specified one
257         */
258        public boolean complies( final Version other )
259        {
260            if( other == null )
261            {
262                return false;
263            }
264            if( other.m_major == -1 )
265            {
266                return true;
267            }
268            if( m_major != other.m_major )
269            {
270                return false;
271            }
272            else if( m_minor < other.m_minor )
273            {
274                //If of major version but lower minor version then incompatible
275                return false;
276            }
277            else
278            {
279                //If same major version, same minor version but lower micro level
280                //then incompatible
281                return !( m_minor == other.m_minor && m_micro < other.m_micro );
282            }
283        }
284    
285        /**
286         * Overload toString to report version correctly.
287         *
288         * @return the dot seperated version string
289         */
290        public String toString()
291        {
292            return m_major + "." + m_minor + "." + m_micro;
293        }
294    
295        /**
296         * Compare two versions together according to the
297         * {@link Comparable} interface.
298         * @param o the other object
299         * @return number indicating relative value (-1, 0, 1)
300         * @throws NullPointerException if the argument is null.
301         */
302        public int compareTo( Object o )
303            throws NullPointerException 
304        {
305            if( o == null )
306            {
307                throw new NullPointerException ( "o" );
308            }
309    
310            Version other = (Version) o;
311    
312            if( getMajor() < other.getMajor() )
313            {
314                return -1;
315            }
316    
317            if( getMajor() > other.getMajor() )
318            {
319                return 1;
320            }
321    
322            if( getMinor() < other.getMinor() )
323            {
324                return -1;
325            }
326    
327            if( getMinor() > other.getMinor() )
328            {
329                return 1;
330            }
331    
332            if( getMicro() < other.getMicro() )
333            {
334                return -1;
335            }
336    
337            if( getMicro() > other.getMicro() )
338            {
339                return 1;
340            }
341    
342            return 0;
343        }
344    }