blob: cc95fcdb5bbafaf8e74687b325e1c72a03c8eee5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 Cloudsmith Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Cloudsmith Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.equinox.internal.provisional.p2.core;
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, Serializable {
private static final class MaxStringValue implements Comparable, Serializable {
private static final long serialVersionUID = -4936252230441132767L;
MaxStringValue() {
// Empty constructor
}
public int compareTo(Object o) {
return o == this ? 0 : (o == MAX_VALUE || o instanceof Integer || o instanceof VersionVector ? -1 : 1);
}
// For singleton deserialization
private Object readResolve() {
return MAXS_VALUE;
}
public String toString() {
return "m"; //$NON-NLS-1$
}
}
private static final class MaxValue implements Comparable, Serializable {
private static final long serialVersionUID = -5889641741635253589L;
MaxValue() {
// Empty constructor
}
public int compareTo(Object o) {
return o == this ? 0 : 1;
}
public String toString() {
return "M"; //$NON-NLS-1$
}
// For singleton deserialization
private Object readResolve() {
return MAX_VALUE;
}
}
private static class MinValue implements Comparable, Serializable {
private static final long serialVersionUID = -1066323980049812226L;
MinValue() {
// Empty constructor
}
public int compareTo(Object o) {
return o == this ? 0 : -1;
}
public String toString() {
return "-M"; //$NON-NLS-1$
}
private Object readResolve() {
return MIN_VALUE;
}
}
/**
* A value that is greater then any other value
*/
public static final Comparable 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 MAXS_VALUE = new MaxStringValue();
/**
* A value that is less then any other value
*/
public static final Comparable MIN_VALUE = new MinValue();
private static final long serialVersionUID = -8385373304298723744L;
static void rawToString(StringBuffer sb, boolean forRange, Comparable e) {
if (e instanceof String) {
writeQuotedString(sb, forRange, (String) e, '\'', 0, false);
} else if (e instanceof VersionVector) {
sb.append('<');
((VersionVector) e).toString(sb, forRange);
sb.append('>');
} else
sb.append(e);
}
/**
* Write a string within quotes. If the string is found to contain the quote, an attempt is made
* to flip quote character (single quote becomes double quote and vice versa). A string that contains
* both will be written as several adjacent quoted strings so that each string is quoted with a
* quote character that it does not contain.
* @param sb The buffer that will receive the string
* @param rangeSafe Set to <code>true</code> if the resulting string will be used in a range string
* and hence need to escape the range delimiter characters
* @param s The string to be written
* @param quote The quote character to start with. Must be the single or double quote character.
* @param startPos The start position
* @param didFlip True if the call is recursive and thus, cannot switch quotes in the first string.
*/
static void writeQuotedString(StringBuffer sb, boolean rangeSafe, String s, char quote, int startPos, boolean didFlip) {
int quotePos = sb.length();
sb.append(quote);
boolean otherSeen = false;
int top = s.length();
for (int idx = startPos; idx < top; ++idx) {
char c = s.charAt(idx);
if (c == '\'' || c == '"') {
if (c == quote) {
char otherQuote = quote == '\'' ? '"' : '\'';
if (didFlip || otherSeen) {
// We can only flip once
sb.append(quote);
writeQuotedString(sb, rangeSafe, s, otherQuote, idx, true);
return;
}
quote = otherQuote;
sb.setCharAt(quotePos, quote);
didFlip = true;
} else
otherSeen = true;
}
if (rangeSafe && (c == '\\' || c == '[' || c == '(' || c == ']' || c == ')' || c == ',' || c <= ' '))
sb.append('\\');
sb.append(c);
}
sb.append(quote);
}
private 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 a.compareTo(b);
if (a == MAX_VALUE || a == MIN_VALUE || a == MAXS_VALUE)
return a.compareTo(b);
if (b == MAX_VALUE || b == MIN_VALUE || b == MAXS_VALUE)
return -b.compareTo(a);
if (a instanceof Integer)
return 1;
if (b instanceof Integer)
return -1;
if (a instanceof VersionVector)
return (b instanceof VersionVector) ? a.compareTo(b) : 1;
if (b instanceof VersionVector)
return -1;
throw new IllegalArgumentException();
}
private Comparable padValue;
private Comparable[] vector;
VersionVector() {
// Constructor used in conjunction with init (when version is parsed from string)
}
VersionVector(Comparable[] vector, Comparable pad) {
this.vector = vector;
this.padValue = (pad == MIN_VALUE) ? null : pad;
}
public int compareTo(Object o) {
if (o == this)
return 0;
VersionVector ov = (VersionVector) o;
Comparable[] t_vector = vector;
Comparable[] o_vector = ov.vector;
int top = t_vector.length;
if (top > o_vector.length)
top = o_vector.length;
for (int idx = 0; idx < top; ++idx) {
int cmp = compareSegments(t_vector[idx], o_vector[idx]);
if (cmp != 0)
return cmp;
}
// All elements compared equal up to this point. Check
// pad values
if (top < t_vector.length)
return (ov.padValue == null) ? 1 : compareReminder(top, ov.padValue);
if (top < o_vector.length)
return (padValue == null) ? -1 : -ov.compareReminder(top, padValue);
// Lengths are equal. Compare pad values
return padValue == null ? (ov.padValue == null ? 0 : -1) : (ov.padValue == null ? 1 : compareSegments(padValue, ov.padValue));
}
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof VersionVector))
return false;
VersionVector ov = (VersionVector) o;
// We compare pad first since it is impossible for versions with
// different pad to be equal (versions are padded to infinity)
if (padValue == null) {
if (ov.padValue != null)
return false;
} else {
if (ov.padValue == null || !padValue.equals(ov.padValue))
return false;
}
Comparable[] t_vector = vector;
Comparable[] o_vector = ov.vector;
int idx = t_vector.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 != o_vector.length)
return false;
while (--idx >= 0)
if (!t_vector[idx].equals(o_vector[idx]))
return false;
return true;
}
/**
* 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;
}
public int hashCode() {
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;
}
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, 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.
*/
public void toString(StringBuffer sb, 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
rawToString(sb, rangeSafe, padValue == null ? MIN_VALUE : padValue);
else {
for (int idx = 0; idx < top; ++idx) {
if (idx > 0)
sb.append('.');
rawToString(sb, rangeSafe, vector[idx]);
}
}
if (padValue != null) {
sb.append('p');
rawToString(sb, rangeSafe, padValue);
}
}
/**
* 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;
}
void init(Comparable[] vec, Comparable pad) {
vector = vec;
padValue = (pad == MIN_VALUE) ? null : pad;
}
private int compareReminder(int idx, 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 : padValue.compareTo(othersPad);
return cmp;
}
}