blob: f1cf244d9d2c3d04e4b992d8c5e113897283272e [file] [log] [blame]
package freemarker.template;
import java.io.Serializable;
import java.util.Date;
import freemarker.template.utility.StringUtil;
/**
* Represents a version number plus the further qualifiers and build into. This is
* mostly used for representing a FreeMarker version number, but must also be able
* to parse the version strings of 3rd party libraries.
*
* @see Configuration#getVersion()
*
* @since 2.3.20
*/
public final class Version implements Serializable {
private final int major;
private final int minor;
private final int micro;
private final String extraInfo;
private final Boolean gaeCompliant;
private final Date buildDate;
private int intValue;
private String stringValue; // not final because it's calculated on demand
private Integer hashCode; // not final because it's calculated on demand
public Version(String stringValue) {
this(stringValue, null, null);
}
public Version(String stringValue, Boolean gaeCompliant, Date buildDate) {
stringValue = stringValue.trim();
int[] parts = new int[3];
String extraInfoTmp = null;
{
int partIdx = 0;
boolean valid = false;
for (int i = 0; i < stringValue.length(); i++) {
char c = stringValue.charAt(i);
if (c >= '0' && c <= '9') {
parts[partIdx] = parts[partIdx] * 10 + (c - '0');
valid = true;
} else {
if (c == '.') {
if (partIdx == 2) {
extraInfoTmp = stringValue.substring(i);
break;
} else {
partIdx++;
}
} else {
extraInfoTmp = stringValue.substring(i);
break;
}
}
}
if (!valid) throw new IllegalArgumentException(
"A version number string " + StringUtil.jQuote(stringValue)
+ " must start with a number.");
if (extraInfoTmp != null) {
char firstChar = extraInfoTmp.charAt(0);
if (firstChar == '.' || firstChar == '-' || firstChar == '_') {
extraInfoTmp = extraInfoTmp.substring(1);
}
}
}
extraInfo = extraInfoTmp;
major = parts[0];
minor = parts[1];
micro = parts[2];
calculateIntValue();
this.stringValue = stringValue;
this.gaeCompliant = gaeCompliant;
this.buildDate = buildDate;
}
public Version(int major, int minor, int micro) {
this(major, minor, micro, null, null, null);
}
public Version(int major, int minor, int micro, String extraInfo, Boolean gaeCompatible, Date buildDate) {
this.major = major;
this.minor = minor;
this.micro = micro;
this.extraInfo = extraInfo;
this.gaeCompliant = gaeCompatible;
this.buildDate = buildDate;
calculateIntValue();
}
private void calculateIntValue() {
intValue = intValueFor(major, minor, micro);
}
static public int intValueFor(int major, int minor, int micro) {
return major * 1000000 + minor * 1000 + micro;
}
/**
* Contains the major.minor.micor numbers and the extraInfo part, not the other information.
*/
public synchronized String toString() { // Switch to double-check + volatile with Java 5
if (stringValue == null) {
stringValue = major + "." + minor + "." + micro;
if (extraInfo != null) stringValue += "-" + extraInfo;
}
return stringValue;
}
/**
* The 1st version number, like 1 in "1.2.3".
*/
public int getMajor() {
return major;
}
/**
* The 2nd version number, like 2 in "1.2.3".
*/
public int getMinor() {
return minor;
}
/**
* The 3rd version number, like 3 in "1.2.3".
*/
public int getMicro() {
return micro;
}
/**
* The arbitrary string after the micro version number without leading dot, dash or underscore,
* like "RC03" in "2.4.0-RC03".
* This is usually a qualifier (RC, SNAPHOST, nightly, beta, etc) and sometimes build info (like
* date).
*/
public String getExtraInfo() {
return extraInfo;
}
/**
* @return The Google App Engine compliance, or {@code null}.
*/
public Boolean isGAECompliant() {
return gaeCompliant;
}
/**
* @return The build date if known, or {@code null}.
*/
public Date getBuildDate() {
return buildDate;
}
/**
* @return major * 1000000 + minor * 1000 + micro.
*/
public int intValue() {
return intValue;
}
public int hashCode() {
if (hashCode == null) {
final int prime = 31;
int result = 1;
result = prime * result + ((buildDate == null) ? 0 : buildDate.hashCode());
result = prime * result + ((extraInfo == null) ? 0 : extraInfo.hashCode());
result = prime * result + ((gaeCompliant == null) ? 0 : gaeCompliant.hashCode());
result = prime * result + ((hashCode == null) ? 0 : hashCode.hashCode());
result = prime * result + intValue;
result = prime * result + ((stringValue == null) ? 0 : stringValue.hashCode());
hashCode = new Integer(result); // J2SE 1.2 had no Integer.valueOf(int)
}
return hashCode.intValue();
}
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Version other = (Version) obj;
if (intValue != other.intValue) return false;
if (other.hashCode() != hashCode()) return false;
if (buildDate == null) {
if (other.buildDate != null) return false;
} else if (!buildDate.equals(other.buildDate)) {
return false;
}
if (extraInfo == null) {
if (other.extraInfo != null) return false;
} else if (!extraInfo.equals(other.extraInfo)) {
return false;
}
if (gaeCompliant == null) {
if (other.gaeCompliant != null) return false;
} else if (!gaeCompliant.equals(other.gaeCompliant)) {
return false;
}
return true;
}
}