/*
* Copyright 2005 by Oracle USA
* 500 Oracle Parkway, Redwood Shores, California, 94065, U.S.A.
* All rights reserved.
*/
package javax.ide.util;
import java.util.HashMap;
import java.util.StringTokenizer;
/**
* Represents the version number of an extension or component. A version
* number is an immutable ordered sequence of integer values.
*
* Version numbers can be represented in two forms. When you construct a
* new Version object using a String representation of the version number, this
* String representation is stored and returned by the {@link #toString()} method.
* This form of the version number will retain any redundant leading zeros in
* any of the integer values which make up the version number.
*
* Conversely, in the canonical form, a version number does not retain
* leading zeros. The canonical form of new Version( "1.02.03" ) would
* be "1.2.3". You can retrieve the canonical form of a Version object
* using the {@link #toCanonicalString()} method.
*/
public final class Version implements Comparable
{
private static final HashMap _convertCache = new HashMap( 100 );
private final int[] _numbers;
private final String _versionLabel;
/**
* Constructs a Version object from a String representation.
*
* @param versionLabel a String representation of a version number. This must
* start with an integer and contain only integers and periods.
* @throws NumberFormatException if the specified version label contains
* non-numeric characters other than periods.
*/
public Version( String versionLabel ) throws NumberFormatException
{
_versionLabel = versionLabel;
_numbers = convert( versionLabel );
}
/**
* Determines whether the specified version label is valid. A version number
* is valid if it consists only of numeric characters and periods.
*
* @param versionLabel a version label.
* @return true if the version label is valid, false
* otherwise.
* @throws NullPointerException if versionLabel is null.
* @since 2.0
*/
public static boolean isValid( String versionLabel )
{
if ( versionLabel == null ) throw new NullPointerException( "versionLabel is null" );
try
{
new Version( versionLabel );
return true;
}
catch ( NumberFormatException nfe )
{
return false;
}
}
/**
* Converts this Version to an int array.
*
* @return a newly allocated int array whose length is equal to the number
* of integer values in this version object and whose contents are
* the integer values represented by this version.
*/
public int[] toIntArray()
{
int[] numbers = new int[ _numbers.length ];
System.arraycopy( _numbers, 0, numbers, 0, _numbers.length );
return numbers;
}
//--------------------------------------------------------------------------
// Comparable interface.
//--------------------------------------------------------------------------
/**
* Compare this version object with another version object.
* Comparison of two version numbers applies to the canonical form of the
* version number. For example, the result of evaluating the expression
* new Version( "1.05.07" ).compareTo( new Version( "1.5.0006" ) ) is
* a positive integer indicating that version 1.5.7 is greater than 1.5.6.
*
* @param other another instance of Version.
* @return a negative integer, zero, or a positive integer as this object is
* less than, equal to, or greater than the specified object.
*/
public int compareTo( Version other )
{
if ( this == other )
{
return 0;
}
int[] otherNumbers = other._numbers;
int len1 = _numbers.length;
int len2 = otherNumbers.length;
int max = Math.max(len1, len2);
for ( int i = 0; i < max; i++ )
{
int d1 = (i < len1 ? _numbers[i] : 0);
int d2 = (i < len2 ? otherNumbers[i] : 0);
if (d1 != d2)
{
return d1 - d2;
}
}
return 0;
}
//--------------------------------------------------------------------------
// Object overrides.
//--------------------------------------------------------------------------
/**
* Get this version number as a string. The string returned is equal to the
* string provided in the constructor. For example,
* new Version( "1.05.06" ).toString(); would return
* "1.05.06".
*
* @return the string representation of this version.
* @see #toCanonicalString()
*/
public String toString()
{
return _versionLabel;
}
/**
* Get this version number as a canonicalized string. Leading zeros will be
* removed from all numerical components of the version. For example,
* new Version( "1.05.06" ).toString(); would return
* "1.5.6".
*
* @return a canonical string representation of this version.
* @see #toString()
*/
public String toCanonicalString()
{
final int len = _numbers.length;
final StringBuffer rtn = new StringBuffer( len * 3 ).append( _numbers[0] );
for ( int i = 1; i < len; i++ )
{
rtn.append('.'); // NORES
rtn.append( _numbers[ i ] );
}
return rtn.toString();
}
/**
* Compare this version object with another version object for equality.
* Equality of two version numbers applies to the canonical form of the
* version number. For example, the result of evaluating the expression
* new Version( "1.05.06" ).equals( new Version( "1.5.0006" ) ) is
* true.
*
* @param other another instance of Version.
* @return true if this version is equal to the other version.
*/
public boolean equals( Object other )
{
if ( other == this )
{
return true;
}
if ( !(other instanceof Version) )
{
return false;
}
final Version version = (Version)other;
final int len = _numbers.length;
if ( len != version._numbers.length )
{
return false;
}
for ( int i = 0; i < len; i++ )
{
if ( _numbers[ i ] != version._numbers[ i ] )
{
return false;
}
}
return true;
}
public int hashCode()
{
int hash = 925295;
final int len = _numbers.length;
for ( int i = 0; i < len; i++ )
{
hash = 37*hash + i;
}
return hash;
}
//---------------------------------------------------------------------------
// Private methods.
//---------------------------------------------------------------------------
private int[] convert( String versionLabel ) throws NumberFormatException
{
int[] numbers = (int[])_convertCache.get( versionLabel );
if ( numbers != null )
{
return numbers;
}
final StringTokenizer tokenizer =
new StringTokenizer(versionLabel, ".", true); // NORES
final int count = tokenizer.countTokens() + 1;
if ( count % 2 != 0 )
{
throw new NumberFormatException(
" Malformed version specification: `" //NORES
+ versionLabel + "`."); //NORES
}
numbers = new int[ count / 2 ];
boolean expectingNumber = true;
int i = 0;
while ( tokenizer.hasMoreTokens() )
{
final String token = tokenizer.nextToken();
if (expectingNumber)
{
expectingNumber = false;
int piece = Integer.parseInt( token );
if (piece < 0)
{
throw new NumberFormatException(
"Malformed version specification: `" //NORES
+ piece + "`." //NORES
+ "Version number must be > 0."); //NORES
}
numbers[ i++ ] = piece;
}
else
{
expectingNumber = true;
}
}
return numbers;
}
}