mirror of https://github.com/apache/archiva.git
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
This commit is contained in:
parent
4d18f5723d
commit
5d08631288
|
@ -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()] );
|
||||||
|
}
|
||||||
|
}
|
|
@ -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] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue