blob: cc8e6c0630c5ced9808047299d8894f8c940fdd4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.runtime;
import java.util.StringTokenizer;
import java.util.Vector;
import org.eclipse.core.internal.runtime.Assert;
import org.eclipse.core.internal.runtime.Policy;
/**
* <p>
* Version identifier for a plug-in. In its string representation,
* it consists of up to 4 tokens separated by a decimal point.
* The first 3 tokens are positive integer numbers, the last token
* is an uninterpreted string (no whitespace characters allowed).
* For example, the following are valid version identifiers
* (as strings):
* <ul>
* <li><code>0.0.0</code></li>
* <li><code>1.0.127564</code></li>
* <li><code>3.7.2.build-127J</code></li>
* <li><code>1.9</code> (interpreted as <code>1.9.0</code>)</li>
* <li><code>3</code> (interpreted as <code>3.0.0</code>)</li>
* </ul>
* </p>
* <p>
* The version identifier can be decomposed into a major, minor,
* service level component and qualifier components. A difference
* in the major component is interpreted as an incompatible version
* change. A difference in the minor (and not the major) component
* is interpreted as a compatible version change. The service
* level component is interpreted as a cumulative and compatible
* service update of the minor version component. The qualifier is
* not interpreted, other than in version comparisons. The
* qualifiers are compared using lexicographical string comparison.
* </p>
* <p>
* Version identifiers can be matched as perfectly equal, equivalent,
* compatible or greaterOrEqual.
* </p>
* <p>
* Clients may instantiate; not intended to be subclassed by clients.
* </p>
* @see IPluginDescriptor#getVersionIdentifier()
* @see java.lang.String#compareTo(java.lang.String)
*/
public final class PluginVersionIdentifier {
private int major = 0;
private int minor = 0;
private int service = 0;
private String qualifier = ""; //$NON-NLS-1$
private static final String SEPARATOR = "."; //$NON-NLS-1$
/**
* Creates a plug-in version identifier from its components.
*
* @param major major component of the version identifier
* @param minor minor component of the version identifier
* @param service service update component of the version identifier
*/
public PluginVersionIdentifier(int major, int minor, int service) {
this(major, minor, service, null);
}
/**
* Creates a plug-in version identifier from its components.
*
* @param major major component of the version identifier
* @param minor minor component of the version identifier
* @param service service update component of the version identifier
* @param qualifier qualifier component of the version identifier.
* Qualifier characters that are not a letter or a digit are replaced.
*/
public PluginVersionIdentifier(int major, int minor, int service, String qualifier) {
// Do the test outside of the assert so that they 'Policy.bind'
// will not be evaluated each time (including cases when we would
// have passed by the assert).
if (major < 0)
Assert.isTrue(false, Policy.bind("parse.postiveMajor", major + SEPARATOR + minor + SEPARATOR + service + SEPARATOR + qualifier)); //$NON-NLS-1$
if (minor < 0)
Assert.isTrue(false, Policy.bind("parse.postiveMinor", major + SEPARATOR + minor + SEPARATOR + service + SEPARATOR + qualifier)); //$NON-NLS-1$
if (service < 0)
Assert.isTrue(false, Policy.bind("parse.postiveService", major + SEPARATOR + minor + SEPARATOR + service + SEPARATOR + qualifier)); //$NON-NLS-1$
if (qualifier == null)
qualifier = ""; //$NON-NLS-1$
this.major = major;
this.minor = minor;
this.service = service;
this.qualifier = verifyQualifier(qualifier);
}
/**
* Creates a plug-in version identifier from the given string.
* The string represenation consists of up to 4 tokens
* separated by decimal point.
* For example, the following are valid version identifiers
* (as strings):
* <ul>
* <li><code>0.0.0</code></li>
* <li><code>1.0.127564</code></li>
* <li><code>3.7.2.build-127J</code></li>
* <li><code>1.9</code> (interpreted as <code>1.9.0</code>)</li>
* <li><code>3</code> (interpreted as <code>3.0.0</code>)</li>
* </ul>
* </p>
*
* @param versionId string representation of the version identifier.
* Qualifier characters that are not a letter or a digit are replaced.
*/
public PluginVersionIdentifier(String versionId) {
Object[] parts = parseVersion(versionId);
this.major = ((Integer) parts[0]).intValue();
this.minor = ((Integer) parts[1]).intValue();
this.service = ((Integer) parts[2]).intValue();
this.qualifier = (String) parts[3];
}
/**
* Validates the given string as a plug-in version identifier.
*
* @param version the string to validate
* @return a status object with code <code>IStatus.OK</code> if
* the given string is valid as a plug-in version identifier, otherwise a status
* object indicating what is wrong with the string
* @since 2.0
*/
public static IStatus validateVersion(String version) {
try {
parseVersion(version);
} catch (RuntimeException e) {
return new Status(IStatus.ERROR, IPlatform.PI_RUNTIME, IStatus.ERROR, e.getMessage(), e);
}
return new Status(IStatus.OK, IPlatform.PI_RUNTIME, IStatus.OK, Policy.bind("ok"), null); //$NON-NLS-1$
}
private static Object[] parseVersion(String versionId) {
// Do the test outside of the assert so that they 'Policy.bind'
// will not be evaluated each time (including cases when we would
// have passed by the assert).
if (versionId == null)
Assert.isNotNull(null, Policy.bind("parse.emptyPluginVersion")); //$NON-NLS-1$
String s = versionId.trim();
if (s.equals("")) //$NON-NLS-1$
Assert.isTrue(false, Policy.bind("parse.emptyPluginVersion")); //$NON-NLS-1$
if (s.startsWith(SEPARATOR))
Assert.isTrue(false, Policy.bind("parse.separatorStartVersion", s)); //$NON-NLS-1$
if (s.endsWith(SEPARATOR))
Assert.isTrue(false, Policy.bind("parse.separatorEndVersion", s)); //$NON-NLS-1$
if (s.indexOf(SEPARATOR + SEPARATOR) != -1)
Assert.isTrue(false, Policy.bind("parse.doubleSeparatorVersion", s)); //$NON-NLS-1$
StringTokenizer st = new StringTokenizer(s, SEPARATOR);
Vector elements = new Vector(4);
while (st.hasMoreTokens())
elements.addElement(st.nextToken());
int elementSize = elements.size();
if (elementSize <= 0)
Assert.isTrue(false, Policy.bind("parse.oneElementPluginVersion", s)); //$NON-NLS-1$
if (elementSize > 4)
Assert.isTrue(false, Policy.bind("parse.fourElementPluginVersion", s)); //$NON-NLS-1$
int[] numbers = new int[3];
try {
numbers[0] = Integer.parseInt((String) elements.elementAt(0));
if (numbers[0] < 0)
Assert.isTrue(false, Policy.bind("parse.postiveMajor", s)); //$NON-NLS-1$
} catch (NumberFormatException nfe) {
Assert.isTrue(false, Policy.bind("parse.numericMajorComponent", s)); //$NON-NLS-1$
}
try {
if (elementSize >= 2) {
numbers[1] = Integer.parseInt((String) elements.elementAt(1));
if (numbers[1] < 0)
Assert.isTrue(false, Policy.bind("parse.postiveMinor", s)); //$NON-NLS-1$
} else
numbers[1] = 0;
} catch (NumberFormatException nfe) {
Assert.isTrue(false, Policy.bind("parse.numericMinorComponent", s)); //$NON-NLS-1$
}
try {
if (elementSize >= 3) {
numbers[2] = Integer.parseInt((String) elements.elementAt(2));
if (numbers[2] < 0)
Assert.isTrue(false, Policy.bind("parse.postiveService", s)); //$NON-NLS-1$
} else
numbers[2] = 0;
} catch (NumberFormatException nfe) {
Assert.isTrue(false, Policy.bind("parse.numericServiceComponent", s)); //$NON-NLS-1$
}
// "result" is a 4-element array with the major, minor, service, and qualifier
Object[] result = new Object[4];
result[0] = new Integer(numbers[0]);
result[1] = new Integer(numbers[1]);
result[2] = new Integer(numbers[2]);
if (elementSize >= 4)
result[3] = verifyQualifier((String) elements.elementAt(3));
else
result[3] = ""; //$NON-NLS-1$
return result;
}
/**
* Compare version identifiers for equality. Identifiers are
* equal if all of their components are equal.
*
* @param object an object to compare
* @return whehter or not the two objects are equal
*/
public boolean equals(Object object) {
if (!(object instanceof PluginVersionIdentifier))
return false;
PluginVersionIdentifier v = (PluginVersionIdentifier) object;
return v.getMajorComponent() == major && v.getMinorComponent() == minor && v.getServiceComponent() == service && v.getQualifierComponent().equals(qualifier);
}
/**
* Returns a hash code value for the object.
*
* @return an integer which is a hash code value for this object.
*/
public int hashCode() {
int code = major + minor + service; // R1.0 result
if (qualifier.equals("")) //$NON-NLS-1$
return code;
else
return code + qualifier.hashCode();
}
/**
* Returns the major (incompatible) component of this
* version identifier.
*
* @return the major version
*/
public int getMajorComponent() {
return major;
}
/**
* Returns the minor (compatible) component of this
* version identifier.
*
* @return the minor version
*/
public int getMinorComponent() {
return minor;
}
/**
* Returns the service level component of this
* version identifier.
*
* @return the service level
*/
public int getServiceComponent() {
return service;
}
/**
* Returns the qualifier component of this
* version identifier.
*
* @return the qualifier
*/
public String getQualifierComponent() {
return qualifier;
}
/**
* Compares two version identifiers to see if this one is
* greater than or equal to the argument.
* <p>
* A version identifier is considered to be greater than or equal
* if its major component is greater than the argument major
* component, or the major components are equal and its minor component
* is greater than the argument minor component, or the
* major and minor components are equal and its service component is
* greater than the argument service component, or the major, minor and
* service components are equal and the qualifier component is
* greated than the argument qualifier component (using lexicographic
* string comparison), or all components are equal.
* </p>
*
* @param id the other version identifier
* @return <code>true</code> is this version identifier
* is compatible with the given version identifier, and
* <code>false</code> otherwise
* @since 2.0
*/
public boolean isGreaterOrEqualTo(PluginVersionIdentifier id) {
if (id == null)
return false;
if (major > id.getMajorComponent())
return true;
if ((major == id.getMajorComponent()) && (minor > id.getMinorComponent()))
return true;
if ((major == id.getMajorComponent()) && (minor == id.getMinorComponent()) && (service > id.getServiceComponent()))
return true;
if ((major == id.getMajorComponent()) && (minor == id.getMinorComponent()) && (service == id.getServiceComponent()) && (qualifier.compareTo(id.getQualifierComponent()) >= 0))
return true;
else
return false;
}
/**
* Compares two version identifiers for compatibility.
* <p>
* A version identifier is considered to be compatible if its major
* component equals to the argument major component, and its minor component
* is greater than or equal to the argument minor component.
* If the minor components are equal, than the service level of the
* version identifier must be greater than or equal to the service level
* of the argument identifier. If the service levels are equal, the two
* version identifiers are considered to be equivalent if this qualifier is
* greated or equal to the qualifier of the argument (using lexicographic
* string comparison).
* </p>
*
* @param id the other version identifier
* @return <code>true</code> is this version identifier
* is compatible with the given version identifier, and
* <code>false</code> otherwise
*/
public boolean isCompatibleWith(PluginVersionIdentifier id) {
if (id == null)
return false;
if (major != id.getMajorComponent())
return false;
if (minor > id.getMinorComponent())
return true;
if (minor < id.getMinorComponent())
return false;
if (service > id.getServiceComponent())
return true;
if (service < id.getServiceComponent())
return false;
if (qualifier.compareTo(id.getQualifierComponent()) >= 0)
return true;
else
return false;
}
/**
* Compares two version identifiers for equivalency.
* <p>
* Two version identifiers are considered to be equivalent if their major
* and minor component equal and are at least at the same service level
* as the argument. If the service levels are equal, the two version
* identifiers are considered to be equivalent if this qualifier is
* greated or equal to the qualifier of the argument (using lexicographic
* string comparison).
*
* </p>
*
* @param id the other version identifier
* @return <code>true</code> is this version identifier
* is equivalent to the given version identifier, and
* <code>false</code> otherwise
*/
public boolean isEquivalentTo(PluginVersionIdentifier id) {
if (id == null)
return false;
if (major != id.getMajorComponent())
return false;
if (minor != id.getMinorComponent())
return false;
if (service > id.getServiceComponent())
return true;
if (service < id.getServiceComponent())
return false;
if (qualifier.compareTo(id.getQualifierComponent()) >= 0)
return true;
else
return false;
}
/**
* Compares two version identifiers for perfect equality.
* <p>
* Two version identifiers are considered to be perfectly equal if their
* major, minor, service and qualifier components are equal
* </p>
*
* @param id the other version identifier
* @return <code>true</code> is this version identifier
* is perfectly equal to the given version identifier, and
* <code>false</code> otherwise
* @since 2.0
*/
public boolean isPerfect(PluginVersionIdentifier id) {
if (id == null)
return false;
if ((major != id.getMajorComponent()) || (minor != id.getMinorComponent()) || (service != id.getServiceComponent()) || (!qualifier.equals(id.getQualifierComponent())))
return false;
else
return true;
}
/**
* Compares two version identifiers for order using multi-decimal
* comparison.
*
* @param id the other version identifier
* @return <code>true</code> is this version identifier
* is greater than the given version identifier, and
* <code>false</code> otherwise
*/
public boolean isGreaterThan(PluginVersionIdentifier id) {
if (id == null) {
if (major == 0 && minor == 0 && service == 0 && qualifier.equals("")) //$NON-NLS-1$
return false; //$NON-NLS-1$
else
return true;
}
if (major > id.getMajorComponent())
return true;
if (major < id.getMajorComponent())
return false;
if (minor > id.getMinorComponent())
return true;
if (minor < id.getMinorComponent())
return false;
if (service > id.getServiceComponent())
return true;
if (service < id.getServiceComponent())
return false;
if (qualifier.compareTo(id.getQualifierComponent()) > 0)
return true;
else
return false;
}
/**
* Returns the string representation of this version identifier.
* The result satisfies
* <code>vi.equals(new PluginVersionIdentifier(vi.toString()))</code>.
*
* @return the string representation of this plug-in version identifier
*/
public String toString() {
String base = major + SEPARATOR + minor + SEPARATOR + service; // R1.0 result
if (qualifier.equals("")) //$NON-NLS-1$
return base;
else
return base + SEPARATOR + qualifier;
}
private static String verifyQualifier(String s) {
char[] chars = s.trim().toCharArray();
boolean whitespace = false;
for (int i = 0; i < chars.length; i++) {
if (!Character.isLetterOrDigit(chars[i])) {
chars[i] = '-';
whitespace = true;
}
}
return whitespace ? new String(chars) : s;
}
}