blob: 603a49d4f5a857a3e7b9f85cfd300106af82aab3 [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;
import java.util.*;
import org.eclipse.osgi.util.NLS;
/**
* <p>The VersionFormat represents the Omni Version Format in compiled form. It
* is also a parser for versions of that format.</p>
* <p>An instance of VersionFormat is immutable and thus thread safe. The parser
* does not maintain any state.</p>
*
* @Immutable
* @noextend This class is not intended to be subclassed by clients.
*/
public class VersionFormat implements Serializable {
private static final long serialVersionUID = 6888925893926932754L;
/**
* Represents one fragment of a format (i.e. auto, number, string, delimiter, etc.)
*/
static abstract class Fragment implements Serializable {
private static final long serialVersionUID = 4109185333058622681L;
private final Qualifier qualifier;
Fragment(Qualifier qualifier) {
this.qualifier = qualifier;
}
public final boolean equals(Object f) {
return f == this || getClass().equals(f.getClass()) && qualifier.equals(((Fragment) f).qualifier);
}
public final int hashCode() {
return 11 * qualifier.hashCode();
}
public boolean isGroup() {
return false;
}
public String toString() {
StringBuffer sb = new StringBuffer();
toString(sb);
return sb.toString();
}
Comparable getDefaultValue() {
return null;
}
Fragment getFirstLeaf() {
return this;
}
Comparable getPadValue() {
return null;
}
Qualifier getQualifier() {
return qualifier;
}
boolean parse(List segments, String version, int maxPos, TreeInfo info) {
return qualifier.parse(new Fragment[] {this}, 0, segments, version, maxPos, info);
}
abstract boolean parseOne(List segments, String version, int maxPos, TreeInfo info);
void setDefaults(List segments) {
// No-op at this level
}
void toString(StringBuffer sb) {
if (!(qualifier == VersionFormatParser.EXACT_ONE_QUALIFIER || (qualifier == VersionFormatParser.ZERO_OR_ONE_QUALIFIER && this.isGroup())))
qualifier.toString(sb);
}
}
/**
* Specifies the min and max occurrences of a fragment
*/
static class Qualifier implements Serializable {
private static final long serialVersionUID = 7494021832824671685L;
private final int max;
private final int min;
Qualifier(int min, int max) {
this.min = min;
this.max = max;
}
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Qualifier))
return false;
Qualifier oq = (Qualifier) o;
return min == oq.min && max == oq.max;
}
public int hashCode() {
return 31 * min + 67 * max;
}
public String toString() {
StringBuffer sb = new StringBuffer();
toString(sb);
return sb.toString();
}
int getMax() {
return max;
}
int getMin() {
return min;
}
boolean parse(Fragment[] fragments, int fragIdx, List segments, String version, int maxPos, TreeInfo info) {
Fragment fragment = fragments[fragIdx++];
int idx = 0;
// Do the required parsing. I.e. iterate this fragment
// min number of times.
//
for (; idx < min; ++idx)
if (!fragment.parseOne(segments, version, maxPos, info))
return false;
for (; idx < max; ++idx) {
// We are greedy. Continue parsing until we get an exception
// and remember the state before each parse is performed.
//
info.pushState(segments.size(), fragment);
if (!fragment.parseOne(segments, version, maxPos, info)) {
info.popState(segments, fragment);
break;
}
}
int maxParsed = idx;
for (;;) {
// Pad with default values unless the max is unbounded
//
if (max != Integer.MAX_VALUE) {
for (; idx < max; ++idx)
fragment.setDefaults(segments);
}
if (fragIdx == fragments.length)
// We are the last segment
//
return true;
// Try to parse the next segment. If it fails, pop the state of
// this segment (or a child thereof) and try again
//
if (fragments[fragIdx].getQualifier().parse(fragments, fragIdx, segments, version, maxPos, info))
return true;
// Be less greedy, step back one position and try again.
//
if (maxParsed <= min)
// We have no more states to pop. Tell previous that we failed.
//
return false;
info.popState(segments, fragment);
idx = --maxParsed; // segments now have room for one more default value
}
}
void toString(StringBuffer sb) {
if (min == 0) {
if (max == 1)
sb.append('?');
else if (max == Integer.MAX_VALUE)
sb.append('*');
else {
sb.append('{');
sb.append(min);
sb.append(',');
sb.append(max);
sb.append('}');
}
} else if (max == Integer.MAX_VALUE) {
if (min == 1)
sb.append('+');
else {
sb.append('{');
sb.append(min);
sb.append(",}"); //$NON-NLS-1$
}
} else {
sb.append('{');
sb.append(min);
if (min != max) {
sb.append(',');
sb.append(max);
}
sb.append('}');
}
}
// Preserve singleton when deserialized
private Object readResolve() {
Qualifier q = this;
if (min == 0) {
if (max == 1)
q = VersionFormatParser.ZERO_OR_ONE_QUALIFIER;
else if (max == Integer.MAX_VALUE)
q = VersionFormatParser.ZERO_OR_MANY_QUALIFIER;
} else if (min == 1) {
if (max == 1)
q = VersionFormatParser.EXACT_ONE_QUALIFIER;
else if (max == Integer.MAX_VALUE)
q = VersionFormatParser.ONE_OR_MANY_QUALIFIER;
}
return q;
}
}
private static class AutoFragment extends RangeFragment {
private static final long serialVersionUID = -1016534328164247755L;
AutoFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
super(instr, qualifier);
}
boolean parseOne(List segments, String version, int maxPos, TreeInfo info) {
int pos = info.getPosition();
maxPos = checkRange(pos, maxPos);
if (maxPos < 0)
return false;
char c = version.charAt(pos);
if (VersionParser.isDigit(c) && isAllowed(c)) {
// Parse to next non-digit
//
int start = pos;
int value = c - '0';
while (++pos < maxPos) {
c = version.charAt(pos);
if (!(VersionParser.isDigit(c) && isAllowed(c)))
break;
value *= 10;
value += (c - '0');
}
int len = pos - start;
if (rangeMin > len || len > rangeMax)
return false;
if (!isIgnored())
segments.add(Version.valueOf(value));
info.setPosition(pos);
return true;
}
if (!(VersionParser.isLetter(c) && isAllowed(c)))
return false;
// Parse to next non-letter or next delimiter
//
int start = pos++;
for (; pos < maxPos; ++pos) {
c = version.charAt(pos);
if (!(VersionParser.isLetter(c) && isAllowed(c)))
break;
}
int len = pos - start;
if (rangeMin > len || len > rangeMax)
return false;
if (!isIgnored())
segments.add(version.substring(start, pos));
info.setPosition(pos);
return true;
}
void toString(StringBuffer sb) {
sb.append('a');
super.toString(sb);
}
}
private static class DelimiterFragment extends Fragment {
private static final long serialVersionUID = 8173654376143370605L;
private final char[] delimChars;
private final boolean inverted;
DelimiterFragment(VersionFormatParser.Instructions ep, Qualifier qualifier) {
super(qualifier);
if (ep == null) {
delimChars = null;
inverted = false;
} else {
inverted = ep.inverted;
delimChars = ep.characters;
}
}
boolean isMatch(String version, int pos) {
char c = version.charAt(pos);
if (delimChars != null) {
for (int idx = 0; idx < delimChars.length; ++idx)
if (c == delimChars[idx])
return !inverted;
return inverted;
} else if (VersionParser.isLetterOrDigit(c))
return false;
return true;
}
boolean parseOne(List segments, String version, int maxPos, TreeInfo info) {
int pos = info.getPosition();
if (pos < maxPos && isMatch(version, pos)) {
// Just swallow, a delimiter does not contribute to the vector.
//
info.setPosition(pos + 1);
return true;
}
return false;
}
void toString(StringBuffer sb) {
sb.append('d');
if (delimChars != null)
appendCharacterRange(sb, delimChars, inverted);
super.toString(sb);
}
}
static boolean equalsAllowNull(Object a, Object b) {
return (a == null) ? (b == null) : (b != null && a.equals(b));
}
private static abstract class ElementFragment extends Fragment {
private static final long serialVersionUID = -6834591415456539713L;
private final Comparable defaultValue;
private final boolean ignored;
private final Comparable padValue;
ElementFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
super(qualifier);
if (instr != null) {
ignored = instr.ignore;
defaultValue = instr.defaultValue;
padValue = instr.padValue;
} else {
ignored = false;
defaultValue = null;
padValue = null;
}
}
Comparable getDefaultValue() {
return defaultValue;
}
Comparable getPadValue() {
return padValue;
}
boolean isIgnored() {
return ignored;
}
void setDefaults(List segments) {
Object defaultVal = getDefaultValue();
if (defaultVal != null)
segments.add(defaultVal);
}
void toString(StringBuffer sb) {
if (ignored) {
sb.append('=');
sb.append('!');
sb.append(';');
}
if (defaultValue != null) {
sb.append('=');
VersionVector.rawToString(sb, false, defaultValue);
sb.append(';');
}
if (padValue != null) {
sb.append('=');
sb.append('p');
VersionVector.rawToString(sb, false, padValue);
sb.append(';');
}
super.toString(sb);
}
}
private static class GroupFragment extends ElementFragment {
private static final long serialVersionUID = 9219978678087669699L;
private final boolean array;
private final Fragment[] fragments;
GroupFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, Fragment[] fragments, boolean array) {
super(instr, qualifier);
this.fragments = fragments;
this.array = array;
}
public boolean isGroup() {
return !array;
}
Fragment getFirstLeaf() {
return fragments[0].getFirstLeaf();
}
boolean parseOne(List segments, String version, int maxPos, TreeInfo info) {
if (array) {
ArrayList subSegs = new ArrayList();
boolean success = fragments[0].getQualifier().parse(fragments, 0, subSegs, version, maxPos, info);
if (!success || subSegs.isEmpty())
return false;
Comparable padValue = info.getPadValue();
if (padValue != null)
info.setPadValue(null); // Prevent outer group from getting this.
else
padValue = getPadValue();
VersionParser.removeRedundantTrail(segments, padValue);
segments.add(new VersionVector((Comparable[]) subSegs.toArray(new Comparable[subSegs.size()]), padValue));
return true;
}
if (fragments[0].getQualifier().parse(fragments, 0, segments, version, maxPos, info)) {
Comparable padValue = getPadValue();
if (padValue != null)
info.setPadValue(padValue);
return true;
}
return false;
}
void setDefaults(List segments) {
Comparable dflt = getDefaultValue();
if (dflt != null) {
// A group default overrides any defaults within the
// group fragments
super.setDefaults(segments);
} else {
// Assign defaults for all fragments
for (int idx = 0; idx < fragments.length; ++idx)
fragments[idx].setDefaults(segments);
}
}
void toString(StringBuffer sb) {
if (array) {
sb.append('<');
for (int idx = 0; idx < fragments.length; ++idx)
fragments[idx].toString(sb);
sb.append('>');
} else {
if (getQualifier() == VersionFormatParser.ZERO_OR_ONE_QUALIFIER) {
sb.append('[');
for (int idx = 0; idx < fragments.length; ++idx)
fragments[idx].toString(sb);
sb.append(']');
} else {
sb.append('(');
for (int idx = 0; idx < fragments.length; ++idx)
fragments[idx].toString(sb);
sb.append(')');
}
}
super.toString(sb);
}
}
private static class LiteralFragment extends Fragment {
private static final long serialVersionUID = 6210696245839471802L;
private final String string;
LiteralFragment(Qualifier qualifier, String string) {
super(qualifier);
this.string = string;
}
boolean parseOne(List segments, String version, int maxPos, TreeInfo info) {
int pos = info.getPosition();
int litLen = string.length();
if (pos + litLen > maxPos)
return false;
for (int idx = 0; idx < litLen; ++idx, ++pos) {
if (string.charAt(idx) != version.charAt(pos))
return false;
}
info.setPosition(pos);
return true;
}
void toString(StringBuffer sb) {
String str = string;
if (str.length() != 1) {
sb.append('\'');
toStringEscaped(sb, str, "\'"); //$NON-NLS-1$
sb.append('\'');
} else {
char c = str.charAt(0);
switch (c) {
case '\'' :
case '\\' :
case '<' :
case '[' :
case '(' :
case '{' :
case '?' :
case '*' :
case '+' :
case '=' :
sb.append('\\');
sb.append(c);
break;
default :
if (VersionParser.isLetterOrDigit(c)) {
sb.append('\\');
sb.append(c);
} else
sb.append(c);
}
}
super.toString(sb);
}
}
private static class NumberFragment extends RangeFragment {
private static final long serialVersionUID = -8552754381106711507L;
private final boolean signed;
NumberFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean signed) {
super(instr, qualifier);
this.signed = signed;
}
boolean parseOne(List segments, String version, int maxPos, TreeInfo info) {
int pos = info.getPosition();
maxPos = checkRange(pos, maxPos);
if (maxPos < 0)
return false;
// Parse to next non-digit
//
int start = pos;
int value;
char c = version.charAt(pos);
if (signed || characters != null) {
boolean negate = false;
if (signed && c == '-' && pos + 1 < maxPos) {
negate = true;
c = version.charAt(++pos);
}
if (!(c >= '0' && c <= '9' && isAllowed(c)))
return false;
// Parse to next non-digit
//
value = c - '0';
while (++pos < maxPos) {
c = version.charAt(pos);
if (!(c >= '0' && c <= '9' && isAllowed(c)))
break;
value *= 10;
value += (c - '0');
}
if (negate)
value = -value;
} else {
if (c < '0' || c > '9')
return false;
// Parse to next non-digit
//
value = c - '0';
while (++pos < maxPos) {
c = version.charAt(pos);
if (c < '0' || c > '9')
break;
value *= 10;
value += (c - '0');
}
}
int len = pos - start;
if (rangeMin > len || len > rangeMax)
return false;
if (!isIgnored())
segments.add(Version.valueOf(value));
info.setPosition(pos);
return true;
}
void toString(StringBuffer sb) {
sb.append(signed ? 'N' : 'n');
super.toString(sb);
}
}
private static class PadFragment extends ElementFragment {
private static final long serialVersionUID = 5052010199974380170L;
PadFragment(Qualifier qualifier) {
super(null, qualifier);
}
boolean parseOne(List segments, String version, int maxPos, TreeInfo info) {
int pos = info.getPosition();
if (pos >= maxPos || version.charAt(pos) != 'p')
return false;
int[] position = new int[] {++pos};
Comparable v = VersionParser.parseRawElement(version, position, maxPos);
if (v == null)
return false;
if (!isIgnored())
info.setPadValue(v);
info.setPosition(position[0]);
return true;
}
void toString(StringBuffer sb) {
sb.append('p');
super.toString(sb);
}
}
private static class QuotedFragment extends RangeFragment {
private static final long serialVersionUID = 6057751133533608969L;
QuotedFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
super(instr, qualifier);
}
boolean parseOne(List segments, String version, int maxPos, TreeInfo info) {
int pos = info.getPosition();
if (pos >= maxPos)
return false;
char endQuote;
char quote = version.charAt(pos);
switch (quote) {
case '<' :
endQuote = '>';
break;
case '{' :
endQuote = '}';
break;
case '(' :
endQuote = ')';
break;
case '[' :
endQuote = ']';
break;
case '>' :
endQuote = '<';
break;
case '}' :
endQuote = '{';
break;
case ')' :
endQuote = '(';
break;
case ']' :
endQuote = '[';
break;
default :
if (VersionParser.isLetterOrDigit(quote))
return false;
endQuote = quote;
}
int start = ++pos;
char c = version.charAt(pos);
while (c != endQuote && isAllowed(c) && ++pos < maxPos)
c = version.charAt(pos);
if (c != endQuote || rangeMin > pos - start)
// End quote not found
return false;
int len = pos - start;
if (rangeMin > len || len > rangeMax)
return false;
if (!isIgnored())
segments.add(version.substring(start, pos));
info.setPosition(++pos); // Skip quote
return true;
}
void toString(StringBuffer sb) {
sb.append('q');
super.toString(sb);
}
}
private static abstract class RangeFragment extends ElementFragment {
private static final long serialVersionUID = -6680402803630334708L;
final char[] characters;
final boolean inverted;
final int rangeMax;
final int rangeMin;
RangeFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
super(instr, qualifier);
if (instr == null) {
characters = null;
inverted = false;
rangeMin = 0;
rangeMax = Integer.MAX_VALUE;
} else {
characters = instr.characters;
inverted = instr.inverted;
rangeMin = instr.rangeMin;
rangeMax = instr.rangeMax;
}
}
/**
* Checks that pos is at a valid character position, that we
* have at least the required minimum characters left, and
* if a maximum number of characters is set, limits the
* returned value to a maxPos that reflects that maximum.
* @param pos the current position
* @param maxPos the current maxPos
* @return maxPos, possibly limited by rangeMax
*/
int checkRange(int pos, int maxPos) {
int check = pos;
if (rangeMin == 0)
check++; // Verify one character
else
check += rangeMin;
if (check > maxPos)
// Less then min characters left
maxPos = -1;
else {
if (rangeMax != Integer.MAX_VALUE) {
check = pos + rangeMax;
if (check < maxPos)
maxPos = check;
}
}
return maxPos;
}
boolean isAllowed(char c) {
char[] crs = characters;
if (crs != null) {
int idx = crs.length;
while (--idx >= 0)
if (c == crs[idx])
return !inverted;
return inverted;
}
return true;
}
void toString(StringBuffer sb) {
if (characters != null)
appendCharacterRange(sb, characters, inverted);
if (rangeMin != 0 || rangeMax != Integer.MAX_VALUE) {
sb.append('=');
sb.append('{');
sb.append(rangeMin);
if (rangeMin != rangeMax) {
sb.append(',');
if (rangeMax != Integer.MAX_VALUE)
sb.append(rangeMax);
}
sb.append('}');
sb.append(';');
}
super.toString(sb);
}
}
private static class RawFragment extends ElementFragment {
private static final long serialVersionUID = 4107448125256042602L;
RawFragment(VersionFormatParser.Instructions processing, Qualifier qualifier) {
super(processing, qualifier);
}
boolean parseOne(List segments, String version, int maxPos, TreeInfo info) {
int[] position = new int[] {info.getPosition()};
Comparable v = VersionParser.parseRawElement(version, position, maxPos);
if (v == null)
return false;
if (!isIgnored())
segments.add(v);
info.setPosition(position[0]);
return true;
}
void toString(StringBuffer sb) {
sb.append('r');
super.toString(sb);
}
}
private static class StringFragment extends RangeFragment {
private static final long serialVersionUID = -2265924553606430164L;
final boolean anyChar;
StringFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean noLimit) {
super(instr, qualifier);
anyChar = noLimit;
}
boolean parseOne(List segments, String version, int maxPos, TreeInfo info) {
int pos = info.getPosition();
maxPos = checkRange(pos, maxPos);
if (maxPos < 0)
return false;
// Parse to next delimiter or end of string
//
int start = pos;
if (characters != null) {
if (anyChar) {
// Swallow everything that matches the allowed characters
for (; pos < maxPos; ++pos) {
if (!isAllowed(version.charAt(pos)))
break;
}
} else {
// Swallow letters that matches the allowed characters
for (; pos < maxPos; ++pos) {
char c = version.charAt(pos);
if (!(VersionParser.isLetter(c) && isAllowed(c)))
break;
}
}
} else {
if (anyChar)
// Swallow all characters
pos = maxPos;
else {
// Swallow all letters
for (; pos < maxPos; ++pos) {
if (!VersionParser.isLetter(version.charAt(pos)))
break;
}
}
}
int len = pos - start;
if (len == 0 || rangeMin > len || len > rangeMax)
return false;
if (!isIgnored())
segments.add(version.substring(start, pos));
info.setPosition(pos);
return true;
}
void toString(StringBuffer sb) {
sb.append(anyChar ? 'S' : 's');
super.toString(sb);
}
}
private static class TreeInfo extends ArrayList {
private static final long serialVersionUID = 4770093863009659750L;
private static class StateInfo {
Fragment fragment;
int segmentCount;
int position;
StateInfo(int position, int segmentCount, Fragment fragment) {
this.fragment = fragment;
this.position = position;
this.segmentCount = segmentCount;
}
}
private Comparable padValue;
private int top;
TreeInfo(Fragment frag, int pos) {
add(new StateInfo(pos, 0, frag));
top = 0;
}
Comparable getPadValue() {
return padValue;
}
int getPosition() {
return ((StateInfo) get(top)).position;
}
void popState(List segments, Fragment frag) {
int idx = top;
while (idx > 0) {
StateInfo si = (StateInfo) get(idx);
if (si.fragment == frag) {
int nsegs = segments.size();
int segMax = si.segmentCount;
while (nsegs > segMax)
segments.remove(--nsegs);
top = idx - 1;
break;
}
}
}
void pushState(int segCount, Fragment fragment) {
int pos = ((StateInfo) get(top)).position;
if (++top == size())
add(new StateInfo(pos, segCount, fragment));
else {
StateInfo si = (StateInfo) get(top);
si.fragment = fragment;
si.position = pos;
si.segmentCount = segCount;
}
}
void setPadValue(Comparable pad) {
padValue = pad;
}
void setPosition(int pos) {
((StateInfo) get(top)).position = pos;
}
}
/**
* The predefined OSGi format that is used when parsing OSGi
* versions.
*/
public static final VersionFormat OSGI_FORMAT;
/**
* The predefined OSGi format that is used when parsing raw
* versions.
*/
public static final VersionFormat RAW_FORMAT;
private static final Map formatCache = Collections.synchronizedMap(new HashMap());
private static final String OSGI_FORMAT_STRING = "n[.n=0;[.n=0;[.S=[A-Za-z0-9_-];]]]"; //$NON-NLS-1$
private static final String RAW_FORMAT_STRING = "r(.r)*p?"; //$NON-NLS-1$
static {
try {
VersionFormatParser parser = new VersionFormatParser();
OSGI_FORMAT = new VersionFormat(parser.compile(OSGI_FORMAT_STRING, 0, OSGI_FORMAT_STRING.length()));
formatCache.put(OSGI_FORMAT_STRING, OSGI_FORMAT);
RAW_FORMAT = new RawFormat(parser.compile(RAW_FORMAT_STRING, 0, RAW_FORMAT_STRING.length()));
formatCache.put(RAW_FORMAT_STRING, RAW_FORMAT);
} catch (FormatException e) {
// If this happens, something is wrong with the actual
// implementation of the FormatCompiler.
//
throw new ExceptionInInitializerError(e);
}
}
/**
* Compile a version format string into a compiled format. This method is
* shorthand for:<pre>CompiledFormat.compile(format, 0, format.length())</pre>.
*
* @param format The format to compile.
* @return The compiled format
* @throws FormatException If the format could not be compiled
*/
public static VersionFormat compile(String format) throws FormatException {
return compile(format, 0, format.length());
}
/**
* Compile a version format string into a compiled format. The parsing starts
* at position start and ends at position end. The returned format is cached so
* subsequent calls to this method using the same format string will yield the
* same compiled format instance.
*
* @param format The format string to compile.
* @param start Start position in the format string
* @param end End position in the format string
* @return The compiled format
* @throws FormatException If the format could not be compiled
*/
public static VersionFormat compile(String format, int start, int end) throws FormatException {
String fmtString = format.substring(start, end).intern();
synchronized (fmtString) {
VersionFormat fmt = (VersionFormat) formatCache.get(fmtString);
if (fmt == null) {
VersionFormatParser parser = new VersionFormatParser();
fmt = new VersionFormat(parser.compile(format, start, end));
formatCache.put(fmtString, fmt);
}
return fmt;
}
}
/**
* Parse a version string using the {@link #RAW_FORMAT} parser.
*
* @param version The version to parse.
* @param originalFormat The original format to assign to the created version. Can be <code>null</code>.
* @param original The original version string to assign to the created version. Can be <code>null</code>.
* @return A created version
* @throws IllegalArgumentException If the version string could not be parsed.
*/
static Version parseRaw(String version, VersionFormat originalFormat, String original) {
Comparable[] padReturn = new Comparable[1];
Comparable[] vector = RAW_FORMAT.parse(version, 0, version.length(), padReturn);
return new Version(vector, padReturn[0], originalFormat, original);
}
static void appendCharacterRange(StringBuffer sb, char[] range, boolean inverted) {
sb.append('=');
sb.append('[');
if (inverted)
sb.append('^');
int top = range.length;
for (int idx = 0; idx < top; ++idx) {
char b = range[idx];
if (b == '\\' || b == ']' || (b == '-' && idx + 1 < top))
sb.append('\\');
sb.append(b);
int ndx = idx + 1;
if (ndx + 2 < top) {
char c = b;
for (; ndx < top; ++ndx) {
char n = range[ndx];
if (c + 1 != n)
break;
c = n;
}
if (ndx <= idx + 3)
continue;
sb.append('-');
if (c == '\\' || c == ']' || (c == '-' && idx + 1 < top))
sb.append('\\');
sb.append(c);
idx = ndx - 1;
}
}
sb.append(']');
sb.append(';');
}
static Fragment createAutoFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
return new AutoFragment(instr, qualifier);
}
static Fragment createDelimiterFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
return new DelimiterFragment(instr, qualifier);
}
static Fragment createGroupFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, Fragment[] fragments, boolean array) {
return new GroupFragment(instr, qualifier, fragments, array);
}
static Fragment createLiteralFragment(Qualifier qualifier, String literal) {
return new LiteralFragment(qualifier, literal);
}
static Fragment createNumberFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean signed) {
return new NumberFragment(instr, qualifier, signed);
}
static Fragment createPadFragment(Qualifier qualifier) {
return new PadFragment(qualifier);
}
static Fragment createQuotedFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
return new QuotedFragment(instr, qualifier);
}
static Fragment createRawFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
return new RawFragment(instr, qualifier);
}
static Fragment createStringFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean unbound) {
return new StringFragment(instr, qualifier, unbound);
}
static void toStringEscaped(StringBuffer sb, String value, String escapes) {
for (int idx = 0; idx < value.length(); ++idx) {
char c = value.charAt(idx);
if (c == '\\' || escapes.indexOf(c) >= 0)
sb.append('\\');
sb.append(c);
}
}
private final Fragment topFragment;
private String fmtString;
VersionFormat(Fragment topFragment) {
this.topFragment = topFragment;
}
public boolean equals(Object o) {
return this == o || o instanceof VersionFormat && toString().equals(o.toString());
}
public int hashCode() {
return 11 * toString().hashCode();
}
/**
* Parse the given version string.
* @param version The version string to parse.
* @return A created version.
* @throws IllegalArgumentException If the version string could not be parsed.
*/
public Version parse(String version) {
return parse(version, 0, version.length());
}
/**
* Parse the given version string.
* @param version The version string to parse.
* @param start Start position in the version string
* @return A created version.
* @throws IllegalArgumentException If the version string could not be parsed.
*/
public Version parse(String version, int start, int maxPos) {
Comparable[] padReturn = new Comparable[1];
Comparable[] vector = parse(version, start, maxPos, padReturn);
return new Version(vector, padReturn[0], this, version.substring(start, maxPos));
}
/**
* Returns the string representation of this compiled format
*/
public synchronized String toString() {
if (fmtString == null) {
StringBuffer sb = new StringBuffer();
toString(sb);
}
return fmtString;
}
/**
* Appends the string representation of this compiled format to
* the given StringBuffer.
* @param sb The buffer that will receive the string representation
*/
public synchronized void toString(StringBuffer sb) {
if (fmtString != null)
sb.append(fmtString);
else {
int start = sb.length();
sb.append("format"); //$NON-NLS-1$
if (topFragment.getPadValue() != null) {
sb.append('(');
topFragment.toString(sb);
sb.append(')');
} else
topFragment.toString(sb);
fmtString = sb.substring(start);
}
}
TreeInfo createInfo(int start) {
return new TreeInfo(topFragment, start);
}
Comparable[] parse(String version, int start, int maxPos, Comparable[] padReturn) {
ArrayList entries = new ArrayList();
if (start == maxPos)
throw new IllegalArgumentException(NLS.bind(Messages.format_0_unable_to_parse_empty_version, this, version.substring(start, maxPos)));
TreeInfo info = new TreeInfo(topFragment, start);
if (!(topFragment.parse(entries, version, maxPos, info) && info.getPosition() == maxPos))
throw new IllegalArgumentException(NLS.bind(Messages.format_0_unable_to_parse_1, this, version.substring(start, maxPos)));
Comparable pad = info.getPadValue();
VersionParser.removeRedundantTrail(entries, pad);
padReturn[0] = pad;
return (Comparable[]) entries.toArray(new Comparable[entries.size()]);
}
// Preserve cache during deserialization
private Object readResolve() {
synchronized (formatCache) {
String string = toString();
VersionFormat fmt = (VersionFormat) formatCache.put(string, this);
if (fmt == null)
fmt = this;
else
// Put old format back
formatCache.put(string, fmt);
return fmt;
}
}
}
class RawFormat extends VersionFormat {
private static final long serialVersionUID = 8851695938450999819L;
RawFormat(Fragment topFragment) {
super(topFragment);
}
/**
* Parse but do not assign this format as the Version format nor the version
* string as the original.
*/
public Version parse(String version, int start, int maxPos) {
Comparable[] padReturn = new Comparable[1];
Comparable[] vector = parse(version, start, maxPos, padReturn);
return new Version(vector, padReturn[0], null, null);
}
// Preserve singleton when deserialized
private Object readResolve() {
return RAW_FORMAT;
}
}