--- /dev/null
+package org.apache.maven.archiva.common.utils;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.math.NumberUtils;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * VersionComparator - compare the parts of two version strings.
+ *
+ * Technique.
+ *
+ * * Split the version strings into parts by splitting on <code>"-._"</code> first, then breaking apart words from numbers.
+ *
+ * <code>
+ * "1.0" = "1", "0"
+ * "1.0-alpha-1" = "1", "0", "alpha", "1"
+ * "2.0-rc2" = "2", "0", "rc", "2"
+ * "1.3-m2" = "1", "3", "m", "3"
+ * </code>
+ *
+ * compare each part individually, and when they do not match, perform the following test.
+ *
+ * Numbers are calculated per normal comparison rules.
+ * Words that are part of the "special word list" will be treated as their index within that heirarchy.
+ * Words that cannot be identified as special, are treated using normal case-insensitive comparison rules.
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public class VersionComparator
+ implements Comparator
+{
+ private static Comparator INSTANCE = new VersionComparator();
+
+ private List specialWords;
+
+ public VersionComparator()
+ {
+ specialWords = new ArrayList();
+
+ // ids that refer to LATEST
+ specialWords.add( "final" );
+ specialWords.add( "release" );
+ specialWords.add( "current" );
+ specialWords.add( "latest" );
+ specialWords.add( "g" );
+ specialWords.add( "gold" );
+ specialWords.add( "fcs" );
+
+ // ids that are for a release cycle.
+ specialWords.add( "a" );
+ specialWords.add( "alpha" );
+ specialWords.add( "b" );
+ specialWords.add( "beta" );
+ specialWords.add( "pre" );
+ specialWords.add( "rc" );
+ specialWords.add( "m" );
+ specialWords.add( "milestone" );
+
+ // ids that are for dev / debug cycles.
+ specialWords.add( "dev" );
+ specialWords.add( "test" );
+ specialWords.add( "debug" );
+ specialWords.add( "unofficial" );
+ specialWords.add( "nightly" );
+ specialWords.add( "incubating" );
+ specialWords.add( "incubator" );
+ specialWords.add( "snapshot" );
+ }
+
+ public static Comparator getInstance()
+ {
+ return INSTANCE;
+ }
+
+ public int compare( Object o1, Object o2 )
+ {
+ if ( o1 == null && o2 == null )
+ {
+ return 0;
+ }
+
+ if ( o1 == null && o2 != null )
+ {
+ return 1;
+ }
+
+ if ( o1 != null && o2 == null )
+ {
+ return -1;
+ }
+
+ if ( ( o1 instanceof String ) && ( o2 instanceof String ) )
+ {
+ String s1 = ( (String) o1 );
+ String s2 = ( (String) o2 );
+
+ String parts1[] = toParts( s1 );
+ String parts2[] = toParts( s2 );
+
+ int diff;
+ int partLen = Math.max( parts1.length, parts2.length );
+ for ( int i = 0; i < partLen; i++ )
+ {
+ diff = comparePart( safePart( parts1, i ), safePart( parts2, i ) );
+ if ( diff != 0 )
+ {
+ return diff;
+ }
+ }
+
+ diff = parts2.length - parts1.length;
+
+ if ( diff != 0 )
+ {
+ return diff;
+ }
+
+ return s1.compareToIgnoreCase( s2 );
+ }
+
+ return 0;
+ }
+
+ private String safePart( String parts[], int idx )
+ {
+ if ( idx < parts.length )
+ {
+ return parts[idx];
+ }
+
+ return "0";
+ }
+
+ private int comparePart( String s1, String s2 )
+ {
+ boolean is1Num = NumberUtils.isNumber( s1 );
+ boolean is2Num = NumberUtils.isNumber( s2 );
+
+ // (Special Case) Test for numbers both first.
+ if ( is1Num && is2Num )
+ {
+ int i1 = NumberUtils.toInt( s1 );
+ int i2 = NumberUtils.toInt( s2 );
+
+ return i1 - i2;
+ }
+
+ // Test for text both next.
+ if ( !is1Num && !is2Num )
+ {
+ int idx1 = specialWords.indexOf( s1.toLowerCase() );
+ int idx2 = specialWords.indexOf( s2.toLowerCase() );
+
+ // Only operate perform index based operation, if both strings
+ // are found in the specialWords index.
+ if ( ( idx1 >= 0 ) && ( idx2 >= 0 ) )
+ {
+ return idx1 - idx2;
+ }
+ }
+
+ // Comparing text to num
+ if ( !is1Num && is2Num )
+ {
+ return -1;
+ }
+
+ // Comparing num to text
+ if ( is1Num && !is2Num )
+ {
+ return 1;
+ }
+
+ // Return comparison of strings themselves.
+ return s1.compareToIgnoreCase( s2 );
+ }
+
+ public static String[] toParts( String version )
+ {
+ if ( StringUtils.isBlank( version ) )
+ {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+
+ final int modeOther = 0;
+ final int modeDigit = 1;
+ final int modeText = 2;
+
+ List parts = new ArrayList();
+ int len = version.length();
+ int i = 0;
+ int start = 0;
+ int mode = modeOther;
+
+ while ( i < len )
+ {
+ char c = version.charAt( i );
+
+ if ( Character.isDigit( c ) )
+ {
+ if ( mode != modeDigit )
+ {
+ if ( mode != modeOther )
+ {
+ parts.add( version.substring( start, i ) );
+ }
+ mode = modeDigit;
+ start = i;
+ }
+ }
+ else if ( Character.isLetter( c ) )
+ {
+ if ( mode != modeText )
+ {
+ if ( mode != modeOther )
+ {
+ parts.add( version.substring( start, i ) );
+ }
+ mode = modeText;
+ start = i;
+ }
+ }
+ else
+ {
+ // Other.
+ if ( mode != modeOther )
+ {
+ if ( mode != modeOther )
+ {
+ parts.add( version.substring( start, i ) );
+ }
+ mode = modeOther;
+ }
+ }
+
+ i++;
+ }
+
+ // Add remainder
+ if ( mode != modeOther )
+ {
+ parts.add( version.substring( start, i ) );
+ }
+
+ return (String[]) parts.toArray( new String[parts.size()] );
+ }
+}
--- /dev/null
+package org.apache.maven.archiva.common.utils;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * VersionComparatorTest
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public class VersionComparatorTest
+ extends TestCase
+{
+ public void testComparator()
+ {
+ /* Sort order is oldest to newest */
+
+ assertSort( new String[] { "1.0", "3.0", "2.0" }, new String[] { "1.0", "2.0", "3.0" } );
+ assertSort( new String[] { "1.5", "1.2", "1.0" }, new String[] { "1.0", "1.2", "1.5" } );
+
+ assertSort( new String[] { "1.5-SNAPSHOT", "1.2", "1.20" }, new String[] { "1.2", "1.5-SNAPSHOT", "1.20" } );
+
+ assertSort( new String[] { "1.1", "1.0-SNAPSHOT", "1.1-m6", "1.1-rc1" }, new String[] {
+ "1.0-SNAPSHOT",
+ "1.1-rc1",
+ "1.1-m6",
+ "1.1" } );
+ assertSort( new String[] { "1.1-m6", "1.0-SNAPSHOT", "1.1-rc1", "1.1" }, new String[] {
+ "1.0-SNAPSHOT",
+ "1.1-rc1",
+ "1.1-m6",
+ "1.1" } );
+
+ assertSort( new String[] { "2.0.5", "2.0.4-SNAPSHOT", "2.0", "2.0-rc1" }, new String[] {
+ "2.0-rc1",
+ "2.0",
+ "2.0.4-SNAPSHOT",
+ "2.0.5" } );
+
+ // TODO: write more unit tests.
+ }
+
+ private void assertSort( String[] rawVersions, String[] expectedSort )
+ {
+ List versions = new ArrayList();
+ versions.addAll( Arrays.asList( rawVersions ) );
+
+ Collections.sort( versions, VersionComparator.getInstance() );
+
+ assertEquals( "Versions.size()", expectedSort.length, versions.size() );
+ for ( int i = 0; i < expectedSort.length; i++ )
+ {
+ assertEquals( "Sorted Versions[" + i + "]", expectedSort[i], (String) versions.get( i ) );
+ }
+ }
+
+ public void testToParts()
+ {
+ assertParts( "1.0", new String[] { "1", "0" } );
+ assertParts( "1.0-alpha-1", new String[] { "1", "0", "alpha", "1" } );
+ assertParts( "2.0-rc2", new String[] { "2", "0", "rc", "2" } );
+ assertParts( "1.3-m6", new String[] { "1", "3", "m", "6" } );
+ }
+
+ private void assertParts( String version, String[] expectedParts )
+ {
+ String actualParts[] = VersionComparator.toParts( version );
+ assertEquals( "Parts.length", expectedParts.length, actualParts.length );
+
+ for ( int i = 0; i < expectedParts.length; i++ )
+ {
+ assertEquals( "parts[" + i + "]", expectedParts[i], actualParts[i] );
+ }
+ }
+}