From 5d0863128890af3d4df037df68dd5507909cc127 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 14 Jun 2007 14:41:19 +0000 Subject: [PATCH] Creating VersionComparator for doing logical sorts of Lists of versions. git-svn-id: https://svn.apache.org/repos/asf/maven/archiva/trunk@547259 13f79535-47bb-0310-9956-ffa450edef68 --- .../common/utils/VersionComparator.java | 271 ++++++++++++++++++ .../common/utils/VersionComparatorTest.java | 99 +++++++ 2 files changed, 370 insertions(+) create mode 100644 archiva-base/archiva-common/src/main/java/org/apache/maven/archiva/common/utils/VersionComparator.java create mode 100644 archiva-base/archiva-common/src/test/java/org/apache/maven/archiva/common/utils/VersionComparatorTest.java 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 index 000000000..a5037693e --- /dev/null +++ b/archiva-base/archiva-common/src/main/java/org/apache/maven/archiva/common/utils/VersionComparator.java @@ -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 "-._" first, then breaking apart words from numbers. + * + * + * "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" + * + * + * 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 Joakim Erdfelt + * @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 index 000000000..d0153da6c --- /dev/null +++ b/archiva-base/archiva-common/src/test/java/org/apache/maven/archiva/common/utils/VersionComparatorTest.java @@ -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 Joakim Erdfelt + * @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] ); + } + } +}