/*
* Created on Aug 23, 2007
*
* This code originally came from grodbots, but has been donated to
* SQL Power by Jonathan Fuerth.
*
* Copyright (c) 2009, SQL Power Group Inc.
*
* This file is part of SQL Power Library.
*
* SQL Power Library 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.
*
* SQL Power Library 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 program. If not, see <http://www.gnu.org/licenses/>.
*/
package ca.sqlpower.util;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
/**
* The Version class represents a dotted version number with an arbitrary number
* of numeric parts and an optional alphanumeric suffix.
*
* @author fuerth
* @version $Id$
*/
public class Version implements Comparable<Version> {
/**
* The components of this version, from most major to least major. Parts
* will either be Integer values or String values. If there is a String
* part, it will be the last part, and is referred to as the "Suffix."
*/
private Object[] parts;
/**
* Creates a new Version object from the given string. The format is
* <tt>a1.a2.(...).aN[suffix]</tt>. Examples: <tt>1.2.3alpha</tt>
* or <tt>1.3</tt> or <tt>2</tt>. The version number must have at
* least one numeric component, so <tt>1suffix</tt> is legal but
* <tt>suffix</tt> on its own is not.
*
* @param v The version string, cannot be null.
*/
public Version(@Nonnull String v) {
String[] rawParts = v.split("\\.");
List<Object> parsedParts = new ArrayList<Object>();
Pattern p = Pattern.compile("[0-9]+");
for (int i = 0; i < rawParts.length; i++) {
Matcher m = p.matcher(rawParts[i]);
if (m.matches()) {
parsedParts.add(Integer.parseInt(rawParts[i]));
} else if (i == rawParts.length - 1) {
Pattern suffixPattern = Pattern.compile("([0-9]+)(.+)");
Matcher suffixMatcher = suffixPattern.matcher((String) rawParts[i]);
if (suffixMatcher.matches()) {
parsedParts.add(Integer.parseInt(suffixMatcher.group(1)));
parsedParts.add(suffixMatcher.group(2));
} else {
throw new VersionParseException("Bad version format \""+v+"\"");
}
} else {
throw new VersionParseException("Bad version format \""+v+"\"");
}
}
parts = parsedParts.toArray();
}
/**
* Creates a copy of the given version that can have some of the minor
* version numbers stripped off of it.
*
* @param copyMe
* The version object to copy.
* @param numPartsToCopy
* The number of version parts to copy. For example, this can be
* 2 to take only the two most important version numbers or 1
* less than copyMe's version number to strip off the minor
* version or suffix. This must be equal to or less than the
* given version's number of parts or an
* {@link IndexOutOfBoundsException} will be thrown.
*/
public Version(Version copyMe, int numPartsToCopy) {
Object[] newVersionParts = new Object[numPartsToCopy];
Object[] oldVersions = copyMe.getParts();
for (int i = 0; i < numPartsToCopy; i++) {
newVersionParts[i] = oldVersions[i];
}
parts = newVersionParts;
}
/**
* Returns the String representation of this version number in the same format
* accepted by the {@link #Version(String)} constructor.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Object part : parts) {
if (!first && part instanceof Integer) {
sb.append(".");
}
sb.append(part);
first = false;
}
return sb.toString();
}
/**
* Returns a copy of the parts that make up this version value.
*
* @see #parts
*/
public Object[] getParts() {
Object[] partsCopy = new Object[parts.length];
for (int i = 0; i < parts.length; i++) {
partsCopy[i] = parts[i];
}
return partsCopy;
}
/**
* Version numbers are mutually comparable even if they have different
* numbers of parts, and in that case, version <tt>2.0</tt> is older
* than <tt>2.0.0</tt> or <tt>2.0.1</tt> but still newer than
* <tt>1.0.0</tt>.
* <p>
* If two versions differ only as far as one having a suffix and the other
* not having a suffix, the one without the suffix is considered newer. This
* allows the natural idea that the following are in chronological order:
* <ul>
* <li>1.0alpha
* <li>1.0beta
* <li>1.0rc1
* <li>1.0rc2
* <li>1.0
* <li>1.1alpha
* <li>1.1
* </ul>
*/
public int compareTo(Version o) {
int i;
for (i = 0; i < parts.length && i < o.parts.length; i++) {
if (parts[i] instanceof Integer && o.parts[i] instanceof Integer) {
int v = (Integer) parts[i];
int ov = (Integer) o.parts[i];
if (v > ov) return 1;
if (v < ov) return -1;
} else if (parts[i] instanceof String && o.parts[i] instanceof String) {
String v = (String) parts[i];
String ov = (String) o.parts[i];
int diff = v.compareTo(ov);
if (diff != 0) return diff;
} else if (parts[i] instanceof Integer && o.parts[i] instanceof String) {
return 1;
} else if (parts[i] instanceof String && o.parts[i] instanceof Integer) {
return -1;
} else {
throw new IllegalStateException("Found a version part that's not a String or Integer");
}
}
// check for special case where comparing 1.0a to 1.0 (1.0 should be newer)
if (parts.length == o.parts.length + 1 && parts[parts.length-1] instanceof String) return -1;
if (o.parts.length == parts.length + 1 && o.parts[o.parts.length-1] instanceof String) return 1;
// otherwise if one version has more integer parts, it's newer.
if (parts.length > o.parts.length) return 1;
if (parts.length < o.parts.length) return -1;
// they're actually the same
return 0;
}
/**
* Returns true if and only if obj is an instance of Version and
* compareTo(obj) would return 0.
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Version)) {
return false;
}
return compareTo((Version) obj) == 0;
}
/**
* Returns a hash code that depends on every part of this version.
*/
@Override
public int hashCode() {
final int PRIME = 31;
int hashCode = 0;
for (Object part : parts) {
hashCode += PRIME * part.hashCode();
}
return hashCode;
}
}