/*
* Copyright (c) 2009 The Jackson Laboratory
*
* This software was developed by Gary Churchill's Lab at The Jackson
* Laboratory (see http://research.jax.org/faculty/churchill).
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jax.r.rintegration;
import java.util.Comparator;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Determines relative order of version strings where earlier releases are
* considered "less than" newer releases.
* @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A>
*/
// TODO unit test me
public class VersionStringComparator implements Comparator<String>
{
/**
* our logger
*/
private static final Logger LOG = Logger.getLogger(
VersionStringComparator.class.getName());
/**
* our singleton instance
* @see #getInstance()
*/
private static final VersionStringComparator INSTANCE =
new VersionStringComparator();
/**
* String containing the possible delimiter characters for a version
* string.
*/
// TODO this should be programmatically hooked up to RHomeScanner's regex
private static final String VERSION_DELIMITERS = ".-_";
/**
* compare the two given version strings
* @param versionString1
* the 1st string
* @param versionString2
* the 2nd string
* @return
* see {@link java.util.Comparator#compare(Object, Object)}
* for the rules on this
*/
public int compare(String versionString1, String versionString2)
{
StringTokenizer tok1 = new StringTokenizer(
versionString1,
VERSION_DELIMITERS);
StringTokenizer tok2 = new StringTokenizer(
versionString2,
VERSION_DELIMITERS);
// iterate through the version string's tokens until you see a
// difference, run out of tokens or hit an "invalid" string
while(tok1.hasMoreTokens() || tok2.hasMoreElements())
{
// at least one has more tokens
if(!tok1.hasMoreTokens() || !tok2.hasMoreTokens())
{
// only one ran out of tokens... the other is "greater"
return tok1.countTokens() - tok2.countTokens();
}
else
{
// both have more tokens
String currTokenString1 = tok1.nextToken();
String currTokenString2 = tok2.nextToken();
// convert tokens into ints checking for validity
boolean currTok1Valid = true;
boolean currTok2Valid = true;
int currTok1IntValue = 0;
int currTok2IntValue = 0;
try
{
currTok1IntValue = Integer.parseInt(currTokenString1);
}
catch(NumberFormatException ex)
{
// invalid number
currTok1Valid = false;
if(LOG.isLoggable(Level.FINE))
{
LOG.log(Level.FINE,
"version token is not a number",
ex);
}
}
try
{
currTok2IntValue = Integer.parseInt(currTokenString2);
}
catch(NumberFormatException ex)
{
// invalid number
currTok2Valid = false;
if(LOG.isLoggable(Level.FINE))
{
LOG.log(Level.FINE,
"version token is not a number",
ex);
}
}
// if they're both valid, compare int values
if(currTok1Valid && currTok2Valid)
{
// look for a value difference. keep looping if none is
// found
int valueDifference = currTok1IntValue - currTok2IntValue;
if(valueDifference != 0)
{
// we found a version difference... return it!
return valueDifference;
}
}
else
{
// at least one token is not a valid number. if the other
// is valid, it wins as "greater" if the other is also
// invalid, then we break out of the loop and fall back
// on plain string comparison
if(currTok1Valid)
{
return 1;
}
else if(currTok2Valid)
{
return -1;
}
else
{
break;
}
}
}
}
// our fall-back position is to just compare the two as normal strings
// which is not really what we want to do (this is a final option)
return versionString1.compareTo(versionString2);
}
/**
* Determines whether or not the potential subversion is a subversion of
* the given super-version or not. Eg: 3.2.1.15 is a subversion of
* 3.2 and 3.2.1 but is not a subversion of 3.2.2
* @param superVersion
* the super version
* @param potentialSubversion
* the version that we're testing
* @return
* true iff it is a subversion
*/
public boolean isSuperversionOf(
String superVersion,
String potentialSubversion)
{
StringTokenizer superVersionTok = new StringTokenizer(
superVersion,
VERSION_DELIMITERS);
StringTokenizer potentialSubversionTok = new StringTokenizer(
potentialSubversion,
VERSION_DELIMITERS);
while(superVersionTok.hasMoreTokens())
{
if(potentialSubversionTok.hasMoreTokens())
{
String currSuperTok = superVersionTok.nextToken();
String currSubTok = potentialSubversionTok.nextToken();
if(!currSuperTok.equals(currSubTok))
{
// found a miss match, so it isn't a subversion
return false;
}
}
else
{
// the subversion can't be shorter
return false;
}
}
// it's a subversion
return true;
}
/**
* Determine if any of the given candidate super-versions are
* super-versions of the the given sub-version as defined by
* {@link #isSuperversionOf(String, String)}.
* @param candidateSuperVersions
* the candidate super-versions
* @param potentialSubVersion
* the potential sub-version that we're testing the super
* versions against
* @return
* true if any are super-versions of the sub-version
*/
public boolean areAnySuperVersionOf(
String[] candidateSuperVersions,
String potentialSubVersion)
{
for(String currCandidateSuperVersion: candidateSuperVersions)
{
if(this.isSuperversionOf(
currCandidateSuperVersion,
potentialSubVersion))
{
return true;
}
}
return false;
}
/**
* Singleton instance getter
* @return
* the instance
*/
public static VersionStringComparator getInstance()
{
return VersionStringComparator.INSTANCE;
}
}