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