]> source.dussan.org Git - archiva.git/commitdiff
Creating VersionComparator for doing logical sorts of Lists of versions.
authorJoakim Erdfelt <joakime@apache.org>
Thu, 14 Jun 2007 14:41:19 +0000 (14:41 +0000)
committerJoakim Erdfelt <joakime@apache.org>
Thu, 14 Jun 2007 14:41:19 +0000 (14:41 +0000)
git-svn-id: https://svn.apache.org/repos/asf/maven/archiva/trunk@547259 13f79535-47bb-0310-9956-ffa450edef68

archiva-base/archiva-common/src/main/java/org/apache/maven/archiva/common/utils/VersionComparator.java [new file with mode: 0644]
archiva-base/archiva-common/src/test/java/org/apache/maven/archiva/common/utils/VersionComparatorTest.java [new file with mode: 0644]

diff --git a/archiva-base/archiva-common/src/main/java/org/apache/maven/archiva/common/utils/VersionComparator.java b/archiva-base/archiva-common/src/main/java/org/apache/maven/archiva/common/utils/VersionComparator.java
new file mode 100644 (file)
index 0000000..a503769
--- /dev/null
@@ -0,0 +1,271 @@
+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()] );
+    }
+}
diff --git a/archiva-base/archiva-common/src/test/java/org/apache/maven/archiva/common/utils/VersionComparatorTest.java b/archiva-base/archiva-common/src/test/java/org/apache/maven/archiva/common/utils/VersionComparatorTest.java
new file mode 100644 (file)
index 0000000..d0153da
--- /dev/null
@@ -0,0 +1,99 @@
+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] );
+        }
+    }
+}