Browse Source

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
tags/archiva-1.0-alpha-2
Joakim Erdfelt 17 years ago
parent
commit
5d08631288

+ 271
- 0
archiva-base/archiva-common/src/main/java/org/apache/maven/archiva/common/utils/VersionComparator.java View File

@@ -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()] );
}
}

+ 99
- 0
archiva-base/archiva-common/src/test/java/org/apache/maven/archiva/common/utils/VersionComparatorTest.java View File

@@ -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…
Cancel
Save