001    // Code source of this file: 
002    //   http://grepcode.com/file/repo1.maven.org/maven2/
003    //     org.apache.maven/maven-artifact/3.1.1/
004    //       org/apache/maven/artifact/versioning/ComparableVersion.java/
005    //
006    // Modifications made on top of the source:
007    //   1. Changed
008    //        package org.apache.maven.artifact.versioning;
009    //      to
010    //        package org.apache.hadoop.util;
011    //   2. Removed author tags to clear hadoop author tag warning
012    //        author <a href="mailto:[email protected]">Kenney Westerhof</a>
013    //        author <a href="mailto:[email protected]">HervĂ© Boutemy</a>
014    //
015    package org.apache.hadoop.util;
016    
017    /*
018     * Licensed to the Apache Software Foundation (ASF) under one
019     * or more contributor license agreements.  See the NOTICE file
020     * distributed with this work for additional information
021     * regarding copyright ownership.  The ASF licenses this file
022     * to you under the Apache License, Version 2.0 (the
023     * "License"); you may not use this file except in compliance
024     * with the License.  You may obtain a copy of the License at
025     *
026     *  http://www.apache.org/licenses/LICENSE-2.0
027     *
028     * Unless required by applicable law or agreed to in writing,
029     * software distributed under the License is distributed on an
030     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
031     * KIND, either express or implied.  See the License for the
032     * specific language governing permissions and limitations
033     * under the License.
034     */
035    
036    import java.math.BigInteger;
037    import java.util.ArrayList;
038    import java.util.Arrays;
039    import java.util.Iterator;
040    import java.util.List;
041    import java.util.ListIterator;
042    import java.util.Locale;
043    import java.util.Properties;
044    import java.util.Stack;
045    
046    /**
047     * Generic implementation of version comparison.
048     * 
049     * <p>Features:
050     * <ul>
051     * <li>mixing of '<code>-</code>' (dash) and '<code>.</code>' (dot) separators,</li>
052     * <li>transition between characters and digits also constitutes a separator:
053     *     <code>1.0alpha1 =&gt; [1, 0, alpha, 1]</code></li>
054     * <li>unlimited number of version components,</li>
055     * <li>version components in the text can be digits or strings,</li>
056     * <li>strings are checked for well-known qualifiers and the qualifier ordering is used for version ordering.
057     *     Well-known qualifiers (case insensitive) are:<ul>
058     *     <li><code>alpha</code> or <code>a</code></li>
059     *     <li><code>beta</code> or <code>b</code></li>
060     *     <li><code>milestone</code> or <code>m</code></li>
061     *     <li><code>rc</code> or <code>cr</code></li>
062     *     <li><code>snapshot</code></li>
063     *     <li><code>(the empty string)</code> or <code>ga</code> or <code>final</code></li>
064     *     <li><code>sp</code></li>
065     *     </ul>
066     *     Unknown qualifiers are considered after known qualifiers, with lexical order (always case insensitive),
067     *   </li>
068     * <li>a dash usually precedes a qualifier, and is always less important than something preceded with a dot.</li>
069     * </ul></p>
070     *
071     * @see <a href="https://cwiki.apache.org/confluence/display/MAVENOLD/Versioning">"Versioning" on Maven Wiki</a>
072     */
073    public class ComparableVersion
074        implements Comparable<ComparableVersion>
075    {
076        private String value;
077    
078        private String canonical;
079    
080        private ListItem items;
081    
082        private interface Item
083        {
084            int INTEGER_ITEM = 0;
085            int STRING_ITEM = 1;
086            int LIST_ITEM = 2;
087    
088            int compareTo( Item item );
089    
090            int getType();
091    
092            boolean isNull();
093        }
094    
095        /**
096         * Represents a numeric item in the version item list.
097         */
098        private static class IntegerItem
099            implements Item
100        {
101            private static final BigInteger BIG_INTEGER_ZERO = new BigInteger( "0" );
102    
103            private final BigInteger value;
104    
105            public static final IntegerItem ZERO = new IntegerItem();
106    
107            private IntegerItem()
108            {
109                this.value = BIG_INTEGER_ZERO;
110            }
111    
112            public IntegerItem( String str )
113            {
114                this.value = new BigInteger( str );
115            }
116    
117            public int getType()
118            {
119                return INTEGER_ITEM;
120            }
121    
122            public boolean isNull()
123            {
124                return BIG_INTEGER_ZERO.equals( value );
125            }
126    
127            public int compareTo( Item item )
128            {
129                if ( item == null )
130                {
131                    return BIG_INTEGER_ZERO.equals( value ) ? 0 : 1; // 1.0 == 1, 1.1 > 1
132                }
133    
134                switch ( item.getType() )
135                {
136                    case INTEGER_ITEM:
137                        return value.compareTo( ( (IntegerItem) item ).value );
138    
139                    case STRING_ITEM:
140                        return 1; // 1.1 > 1-sp
141    
142                    case LIST_ITEM:
143                        return 1; // 1.1 > 1-1
144    
145                    default:
146                        throw new RuntimeException( "invalid item: " + item.getClass() );
147                }
148            }
149    
150            public String toString()
151            {
152                return value.toString();
153            }
154        }
155    
156        /**
157         * Represents a string in the version item list, usually a qualifier.
158         */
159        private static class StringItem
160            implements Item
161        {
162            private static final String[] QUALIFIERS = { "alpha", "beta", "milestone", "rc", "snapshot", "", "sp" };
163    
164            private static final List<String> _QUALIFIERS = Arrays.asList( QUALIFIERS );
165    
166            private static final Properties ALIASES = new Properties();
167            static
168            {
169                ALIASES.put( "ga", "" );
170                ALIASES.put( "final", "" );
171                ALIASES.put( "cr", "rc" );
172            }
173    
174            /**
175             * A comparable value for the empty-string qualifier. This one is used to determine if a given qualifier makes
176             * the version older than one without a qualifier, or more recent.
177             */
178            private static final String RELEASE_VERSION_INDEX = String.valueOf( _QUALIFIERS.indexOf( "" ) );
179    
180            private String value;
181    
182            public StringItem( String value, boolean followedByDigit )
183            {
184                if ( followedByDigit && value.length() == 1 )
185                {
186                    // a1 = alpha-1, b1 = beta-1, m1 = milestone-1
187                    switch ( value.charAt( 0 ) )
188                    {
189                        case 'a':
190                            value = "alpha";
191                            break;
192                        case 'b':
193                            value = "beta";
194                            break;
195                        case 'm':
196                            value = "milestone";
197                            break;
198                    }
199                }
200                this.value = ALIASES.getProperty( value , value );
201            }
202    
203            public int getType()
204            {
205                return STRING_ITEM;
206            }
207    
208            public boolean isNull()
209            {
210                return ( comparableQualifier( value ).compareTo( RELEASE_VERSION_INDEX ) == 0 );
211            }
212    
213            /**
214             * Returns a comparable value for a qualifier.
215             *
216             * This method takes into account the ordering of known qualifiers then unknown qualifiers with lexical ordering.
217             *
218             * just returning an Integer with the index here is faster, but requires a lot of if/then/else to check for -1
219             * or QUALIFIERS.size and then resort to lexical ordering. Most comparisons are decided by the first character,
220             * so this is still fast. If more characters are needed then it requires a lexical sort anyway.
221             *
222             * @param qualifier
223             * @return an equivalent value that can be used with lexical comparison
224             */
225            public static String comparableQualifier( String qualifier )
226            {
227                int i = _QUALIFIERS.indexOf( qualifier );
228    
229                return i == -1 ? ( _QUALIFIERS.size() + "-" + qualifier ) : String.valueOf( i );
230            }
231    
232            public int compareTo( Item item )
233            {
234                if ( item == null )
235                {
236                    // 1-rc < 1, 1-ga > 1
237                    return comparableQualifier( value ).compareTo( RELEASE_VERSION_INDEX );
238                }
239                switch ( item.getType() )
240                {
241                    case INTEGER_ITEM:
242                        return -1; // 1.any < 1.1 ?
243    
244                    case STRING_ITEM:
245                        return comparableQualifier( value ).compareTo( comparableQualifier( ( (StringItem) item ).value ) );
246    
247                    case LIST_ITEM:
248                        return -1; // 1.any < 1-1
249    
250                    default:
251                        throw new RuntimeException( "invalid item: " + item.getClass() );
252                }
253            }
254    
255            public String toString()
256            {
257                return value;
258            }
259        }
260    
261        /**
262         * Represents a version list item. This class is used both for the global item list and for sub-lists (which start
263         * with '-(number)' in the version specification).
264         */
265        private static class ListItem
266            extends ArrayList<Item>
267            implements Item
268        {
269            public int getType()
270            {
271                return LIST_ITEM;
272            }
273    
274            public boolean isNull()
275            {
276                return ( size() == 0 );
277            }
278    
279            void normalize()
280            {
281                for ( ListIterator<Item> iterator = listIterator( size() ); iterator.hasPrevious(); )
282                {
283                    Item item = iterator.previous();
284                    if ( item.isNull() )
285                    {
286                        iterator.remove(); // remove null trailing items: 0, "", empty list
287                    }
288                    else
289                    {
290                        break;
291                    }
292                }
293            }
294    
295            public int compareTo( Item item )
296            {
297                if ( item == null )
298                {
299                    if ( size() == 0 )
300                    {
301                        return 0; // 1-0 = 1- (normalize) = 1
302                    }
303                    Item first = get( 0 );
304                    return first.compareTo( null );
305                }
306                switch ( item.getType() )
307                {
308                    case INTEGER_ITEM:
309                        return -1; // 1-1 < 1.0.x
310    
311                    case STRING_ITEM:
312                        return 1; // 1-1 > 1-sp
313    
314                    case LIST_ITEM:
315                        Iterator<Item> left = iterator();
316                        Iterator<Item> right = ( (ListItem) item ).iterator();
317    
318                        while ( left.hasNext() || right.hasNext() )
319                        {
320                            Item l = left.hasNext() ? left.next() : null;
321                            Item r = right.hasNext() ? right.next() : null;
322    
323                            // if this is shorter, then invert the compare and mul with -1
324                            int result = l == null ? -1 * r.compareTo( l ) : l.compareTo( r );
325                            
326                            if ( result != 0 )
327                            {
328                                return result;
329                            }
330                        }
331    
332                        return 0;
333    
334                    default:
335                        throw new RuntimeException( "invalid item: " + item.getClass() );
336                }
337            }
338    
339            public String toString()
340            {
341                StringBuilder buffer = new StringBuilder( "(" );
342                for ( Iterator<Item> iter = iterator(); iter.hasNext(); )
343                {
344                    buffer.append( iter.next() );
345                    if ( iter.hasNext() )
346                    {
347                        buffer.append( ',' );
348                    }
349                }
350                buffer.append( ')' );
351                return buffer.toString();
352            }
353        }
354    
355        public ComparableVersion( String version )
356        {
357            parseVersion( version );
358        }
359    
360        public final void parseVersion( String version )
361        {
362            this.value = version;
363    
364            items = new ListItem();
365    
366            version = version.toLowerCase( Locale.ENGLISH );
367    
368            ListItem list = items;
369    
370            Stack<Item> stack = new Stack<Item>();
371            stack.push( list );
372    
373            boolean isDigit = false;
374    
375            int startIndex = 0;
376    
377            for ( int i = 0; i < version.length(); i++ )
378            {
379                char c = version.charAt( i );
380    
381                if ( c == '.' )
382                {
383                    if ( i == startIndex )
384                    {
385                        list.add( IntegerItem.ZERO );
386                    }
387                    else
388                    {
389                        list.add( parseItem( isDigit, version.substring( startIndex, i ) ) );
390                    }
391                    startIndex = i + 1;
392                }
393                else if ( c == '-' )
394                {
395                    if ( i == startIndex )
396                    {
397                        list.add( IntegerItem.ZERO );
398                    }
399                    else
400                    {
401                        list.add( parseItem( isDigit, version.substring( startIndex, i ) ) );
402                    }
403                    startIndex = i + 1;
404    
405                    if ( isDigit )
406                    {
407                        list.normalize(); // 1.0-* = 1-*
408    
409                        if ( ( i + 1 < version.length() ) && Character.isDigit( version.charAt( i + 1 ) ) )
410                        {
411                            // new ListItem only if previous were digits and new char is a digit,
412                            // ie need to differentiate only 1.1 from 1-1
413                            list.add( list = new ListItem() );
414    
415                            stack.push( list );
416                        }
417                    }
418                }
419                else if ( Character.isDigit( c ) )
420                {
421                    if ( !isDigit && i > startIndex )
422                    {
423                        list.add( new StringItem( version.substring( startIndex, i ), true ) );
424                        startIndex = i;
425                    }
426    
427                    isDigit = true;
428                }
429                else
430                {
431                    if ( isDigit && i > startIndex )
432                    {
433                        list.add( parseItem( true, version.substring( startIndex, i ) ) );
434                        startIndex = i;
435                    }
436    
437                    isDigit = false;
438                }
439            }
440    
441            if ( version.length() > startIndex )
442            {
443                list.add( parseItem( isDigit, version.substring( startIndex ) ) );
444            }
445    
446            while ( !stack.isEmpty() )
447            {
448                list = (ListItem) stack.pop();
449                list.normalize();
450            }
451    
452            canonical = items.toString();
453        }
454    
455        private static Item parseItem( boolean isDigit, String buf )
456        {
457            return isDigit ? new IntegerItem( buf ) : new StringItem( buf, false );
458        }
459    
460        public int compareTo( ComparableVersion o )
461        {
462            return items.compareTo( o.items );
463        }
464    
465        public String toString()
466        {
467            return value;
468        }
469    
470        public boolean equals( Object o )
471        {
472            return ( o instanceof ComparableVersion ) && canonical.equals( ( (ComparableVersion) o ).canonical );
473        }
474    
475        public int hashCode()
476        {
477            return canonical.hashCode();
478        }
479    }