blob: 96e24bac9e54bdbd1834358a0082f1675c4e5663 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2017 Cloudsmith Inc. and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Cloudsmith Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
import java.io.Serializable;
/**
* The VersionVector represents an array of Comparable objects. The array can be
* nested since a VersionVector is Comparable in itself.
*
* @Immutable
*/
public class VersionVector implements Comparable<VersionVector>, Serializable {
interface MinMaxComparable extends Comparable<Object>, Serializable {
//
}
private static final class MaxStringValue implements MinMaxComparable {
private static final long serialVersionUID = -4936252230441132767L;
MaxStringValue() {
// Empty constructor
}
@Override
public int compareTo(Object o) {
return o == this ? 0 : (o == MAX_VALUE || o instanceof Integer || o instanceof EnumDefinition.EnumSegment || o instanceof VersionVector ? -1 : 1);
}
// For singleton deserialization
private Object readResolve() {
return MAXS_VALUE;
}
@Override
public String toString() {
return "m"; //$NON-NLS-1$
}
}
private static final class MaxValue implements MinMaxComparable {
private static final long serialVersionUID = -5889641741635253589L;
MaxValue() {
// Empty constructor
}
@Override
public int compareTo(Object o) {
return o == this ? 0 : 1;
}
// For singleton deserialization
private Object readResolve() {
return MAX_VALUE;
}
@Override
public String toString() {
return "M"; //$NON-NLS-1$
}
}
private static class MinValue implements MinMaxComparable {
private static final long serialVersionUID = -1066323980049812226L;
MinValue() {
// Empty constructor
}
@Override
public int compareTo(Object o) {
return o == this ? 0 : -1;
}
private Object readResolve() {
return MIN_VALUE;
}
@Override
public String toString() {
return "-M"; //$NON-NLS-1$
}
}
/**
* A value that is greater then any other value
*/
public static final Comparable<Object> MAX_VALUE = new MaxValue();
/**
* A value that is greater then any string but less then {@link #MAX_VALUE} and
* any Integer or VersionVector.
*/
public static final Comparable<Object> MAXS_VALUE = new MaxStringValue();
/**
* A value that is less then any other value
*/
public static final Comparable<Object> MIN_VALUE = new MinValue();
/**
* A value that is greater then {@link #MIN_VALUE} and less then any string,
* Integer, or VersionVector (a.k.a. empty_string)
*/
public static final String MINS_VALUE = ""; //$NON-NLS-1$
private static final long serialVersionUID = -8385373304298723744L;
static int compare(Comparable<?>[] vectorA, Comparable<?> padA, Comparable<?>[] vectorB, Comparable<?> padB) {
int top = vectorA.length;
if (top > vectorB.length)
top = vectorB.length;
for (int idx = 0; idx < top; ++idx) {
int cmp = compareSegments(vectorA[idx], vectorB[idx]);
if (cmp != 0)
return cmp;
}
// All elements compared equal up to this point. Check
// pad values
if (top < vectorA.length)
return (padB == null) ? 1 : compareReminder(top, vectorA, padA, padB);
if (top < vectorB.length)
return (padA == null) ? -1 : -compareReminder(top, vectorB, padB, padA);
// Lengths are equal. Compare pad values
return padA == null ? (padB == null ? 0 : -1) : (padB == null ? 1 : compareSegments(padA, padB));
}
static boolean equals(Comparable<?>[] vectorA, Comparable<?> padValueA, Comparable<?>[] vectorB, Comparable<?> padValueB) {
// We compare pad first since it is impossible for versions with
// different pad to be equal (versions are padded to infinity)
if (padValueA == null) {
if (padValueB != null)
return false;
} else {
if (padValueB == null || !padValueA.equals(padValueB))
return false;
}
int idx = vectorA.length;
// If the length of the vector differs, the versions cannot be equal
// since segments equal to pad are stripped by the parser
if (idx != vectorB.length)
return false;
while (--idx >= 0)
if (!vectorA[idx].equals(vectorB[idx]))
return false;
return true;
}
static int hashCode(Comparable<?>[] vector, Comparable<?> padValue) {
int hashCode = padValue == null ? 31 : padValue.hashCode();
int idx = vector.length;
while (--idx >= 0) {
Object elem = vector[idx];
if (elem != null)
hashCode += elem.hashCode();
hashCode = hashCode * 31;
}
return hashCode;
}
static void toString(StringBuffer sb, Comparable<?>[] vector, Comparable<?> padValue, boolean rangeSafe) {
int top = vector.length;
if (top == 0)
// Write one pad value as explicit. It will be considered
// redundant and removed by the parser but the raw format
// does not allow zero elements
VersionFormat.rawToString(sb, rangeSafe, padValue == null ? MIN_VALUE : padValue);
else {
for (int idx = 0; idx < top; ++idx) {
if (idx > 0)
sb.append('.');
VersionFormat.rawToString(sb, rangeSafe, vector[idx]);
}
}
if (padValue != null) {
sb.append('p');
VersionFormat.rawToString(sb, rangeSafe, padValue);
}
}
private static int compareReminder(int idx, Comparable<?>[] vector, Comparable<?> padValue, Comparable<?> othersPad) {
int cmp;
for (cmp = 0; idx < vector.length && cmp == 0; ++idx)
cmp = compareSegments(vector[idx], othersPad);
if (cmp == 0)
cmp = (padValue == null) ? -1 : compareSegments(padValue, othersPad);
return cmp;
}
static int compareSegments(Comparable<?> a, Comparable<?> b) {
if (a == b)
return 0;
if (a instanceof Integer && b instanceof Integer) {
int ai = ((Integer) a).intValue();
int bi = ((Integer) b).intValue();
return ai > bi ? 1 : (ai < bi ? -1 : 0);
}
if (a instanceof String && b instanceof String)
return ((String) a).compareTo((String) b);
if (a == MAX_VALUE || a == MIN_VALUE || a == MAXS_VALUE)
return ((MinMaxComparable) a).compareTo(b);
if (b == MAX_VALUE || b == MIN_VALUE || b == MAXS_VALUE)
return -((MinMaxComparable) b).compareTo(a);
if (a instanceof Integer)
return 1;
if (b instanceof Integer)
return -1;
if (a instanceof VersionVector)
return (b instanceof VersionVector) ? ((VersionVector) a).compareTo((VersionVector) b) : 1;
if (b instanceof VersionVector)
return -1;
if (a instanceof EnumDefinition.EnumSegment)
return (b instanceof EnumDefinition.EnumSegment) ? ((EnumDefinition.EnumSegment) a).compareTo((EnumDefinition.EnumSegment) b) : 1;
if (b instanceof EnumDefinition.EnumSegment)
return -1;
throw new IllegalArgumentException();
}
private final Comparable<?> padValue;
private final Comparable<?>[] vector;
public VersionVector(Comparable<?>[] vector, Comparable<?> pad) {
this.vector = vector;
this.padValue = (pad == MIN_VALUE) ? null : pad;
}
@Override
public int compareTo(VersionVector ov) {
if (ov == this)
return 0;
return compare(vector, padValue, ov.vector, ov.padValue);
}
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof VersionVector))
return false;
VersionVector ov = (VersionVector) o;
return equals(vector, padValue, ov.vector, ov.padValue);
}
/**
* Returns the pad value used when comparing this versions to
* versions that has a raw vector with a larger number of elements
* @return The pad value or <code>null</code> if not set.
*/
public Comparable<?> getPad() {
return padValue;
}
/**
* An element from the raw vector
* @param index The zero based index of the desired element
* @return An element from the raw vector
*/
public Comparable<?> getSegment(int index) {
return vector[index];
}
/**
* Returns the number of elements in the raw vector
* @return The element count
*/
public int getSegmentCount() {
return vector.length;
}
/**
* This method is package protected since it violates the immutable
* contract.
* @return The raw vector. Must be treated as read-only
*/
Comparable<?>[] getVector() {
return vector;
}
@Override
public int hashCode() {
return hashCode(vector, padValue);
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
toString(sb);
return sb.toString();
}
/**
* Append the string representation of this instance to the
* <code>sb</code> buffer.
* @param sb The buffer to append to
*/
public void toString(StringBuffer sb) {
toString(sb, vector, padValue, false);
}
/**
* Append the string representation of this instance to the
* <code>sb</code> buffer.
* @param sb The buffer to append to
* @param rangeSafe If <code>true</code>, the range delimiters will be escaped
* with backslash.
*/
void toString(StringBuffer sb, boolean rangeSafe) {
toString(sb, vector, padValue, rangeSafe);
}
private Object readResolve() {
VersionVector vv = this;
// Preserve the emptyString singleton
int idx = vector.length;
while (--idx >= 0)
if (MINS_VALUE.equals(vector[idx]))
vector[idx] = MINS_VALUE;
if (MINS_VALUE.equals(padValue))
vv = new VersionVector(vector, MINS_VALUE);
return vv;
}
}