blob: 9b46f07ac34bf32e2b9ef049ca587c5d3c9c6be1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2010 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.p2.metadata;
import java.util.*;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.metadata.VersionFormatException;
import org.eclipse.osgi.util.NLS;
/**
* The Omni Version parser. Not intended for public API. Instead use
* {@link Version#create(String)} or {@link Version#parseVersion(String)}.
*
* The class also contains some general purpose parser support methods
*
* @noextend This class is not intended to be subclassed by clients.
*/
public abstract class VersionParser {
public static final Integer ZERO_INT = new Integer(0);
public static final Integer MAX_INT_OBJ = new Integer(Integer.MAX_VALUE);
private static final Integer cache[] = new Integer[100];
static {
cache[0] = ZERO_INT;
for (int i = 1; i < cache.length; i++)
cache[i] = new Integer(i);
}
public static Integer valueOf(int i) {
if (i >= 0 && i < cache.length)
return cache[i];
return (i == Integer.MAX_VALUE) ? MAX_INT_OBJ : new Integer(i);
}
static Comparable<?> removeRedundantTrail(List<Comparable<?>> segments, Comparable<?> padValue) {
Comparable<?> redundantTrail;
if (padValue == null)
redundantTrail = VersionVector.MIN_VALUE;
else {
redundantTrail = padValue;
if (padValue == VersionVector.MIN_VALUE)
padValue = null;
}
int idx = segments.size();
while (--idx >= 0 && segments.get(idx).equals(redundantTrail))
segments.remove(idx);
return padValue;
}
private VersionParser() {
// Prevent class from being instantiated
}
/**
* Parse the <code>version</code> string and assing the parsed portions to the <code>receiver</code>.
* This method is called from the version string constructor.
*
* @param version The string to be parsed
* @param start Start position in the <code>version</code> string
* @param maxPos End position in the <code>version</code> string
* @returns a version if one indeed was parsed or <code>null</code> if the string
* contained only whitespace.
* @throws IllegalArgumentException if the version is malformed
*/
public static Version parse(String version, int start, int maxPos) throws IllegalArgumentException {
// trim leading and trailing whitespace
int pos = skipWhite(version, start);
maxPos = skipTrailingWhite(version, start, maxPos);
if (pos == maxPos)
return null;
List<Comparable<?>> vector = null;
VersionFormat fmt = null;
char c = version.charAt(pos);
if (isDigit(c)) {
return OSGiVersion.fromVector(VersionFormat.OSGI_FORMAT.parse(version, pos, maxPos));
}
if (!isLetter(c))
throw new IllegalArgumentException();
if (version.startsWith(Version.RAW_PREFIX, pos)) {
VersionFormat rawFmt = VersionFormat.RAW_FORMAT;
pos += 4;
// Find ending '/' that is neither quoted or escaped
int end = maxPos;
for (int idx = pos; idx < maxPos; ++idx) {
c = version.charAt(idx);
switch (c) {
case '/' :
end = idx;
break;
case '\\' :
++idx;
continue;
case '\'' :
case '"' :
for (++idx; idx < maxPos; ++idx) {
char e = version.charAt(idx);
if (e == c) {
break;
}
if (e == '\\')
++idx;
}
// fall through to default
default :
continue;
}
break;
}
vector = rawFmt.parse(version, pos, end);
pos = end;
if (pos == maxPos)
// This was a pure raw version
//
return OmniVersion.fromVector(vector, null, null);
if (version.charAt(pos) != '/')
throw new IllegalArgumentException(NLS.bind(Messages.expected_slash_after_raw_vector_0, version.substring(start, maxPos)));
++pos;
if (pos == maxPos)
throw new IllegalArgumentException(NLS.bind(Messages.expected_orignal_after_slash_0, version.substring(start, maxPos)));
}
if (version.startsWith("format(", pos)) { //$NON-NLS-1$
// Parse the format
//
pos += 7;
try {
// Find matching ')' that is neither quoted or escaped
//
int end = findEndOfFormat(version, pos, maxPos);
fmt = VersionFormat.compile(version, pos, end);
pos = end + 1;
} catch (VersionFormatException e) {
throw new IllegalArgumentException(e.getMessage());
}
if (pos == maxPos) {
// This was a raw version with format but no original
//
if (vector == null)
throw new IllegalArgumentException(NLS.bind(Messages.only_format_specified_0, version.substring(start, maxPos)));
return fmt == VersionFormat.OSGI_FORMAT ? OSGiVersion.fromVector(vector) : OmniVersion.fromVector(vector, fmt, null);
}
}
if (fmt == null && vector == null)
throw new IllegalArgumentException(NLS.bind(Messages.neither_raw_vector_nor_format_specified_0, version.substring(start, maxPos)));
if (version.charAt(pos) != ':')
throw new IllegalArgumentException(NLS.bind(Messages.colon_expected_before_original_version_0, version.substring(start, maxPos)));
pos++;
if (pos == maxPos)
throw new IllegalArgumentException(NLS.bind(Messages.expected_orignal_after_colon_0, version.substring(start, maxPos)));
if (vector == null) {
// Vector and pad must be created by parsing the original
//
vector = fmt.parse(version, pos, maxPos);
}
return fmt == VersionFormat.OSGI_FORMAT ? OSGiVersion.fromVector(vector) : OmniVersion.fromVector(vector, fmt, version.substring(pos));
}
static boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
public static boolean isLetter(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
static boolean isLetterOrDigit(char c) {
return isDigit(c) || isLetter(c);
}
public static int findEndOfFormat(String string, int pos, int maxPos) {
int end = -1;
int depth = 1;
for (int idx = pos; idx < maxPos; ++idx) {
char c = string.charAt(idx);
switch (c) {
case ')' :
if (--depth == 0) {
end = idx;
break;
}
continue;
case '(' :
++depth;
continue;
case '\\' :
++idx;
continue;
case '\'' :
case '"' :
for (++idx; idx < maxPos; ++idx) {
char e = string.charAt(idx);
if (e == c) {
break;
}
if (e == '\\')
++idx;
}
// fall through to default
default :
continue;
}
break;
}
if (depth != 0)
throw new IllegalArgumentException(NLS.bind(Messages.unbalanced_format_parenthesis, string.substring(pos - 1, maxPos)));
return end;
}
static Comparable<?> parseRawElement(String value, int[] position, int maxPos) {
int current = position[0];
if (current >= maxPos)
return null;
boolean negate = false;
char c = value.charAt(current);
Comparable<?> v;
switch (c) {
case '\'' :
case '"' : {
StringBuffer sb = new StringBuffer();
for (;;) {
char q = c;
if (++current == maxPos)
return null;
c = value.charAt(current);
while (c != q) {
if (c < 32)
return null;
sb.append(c);
if (++current == maxPos)
return null;
c = value.charAt(current);
}
if (++current == maxPos)
break;
c = value.charAt(current);
if (c != '\'' && c != '"')
break;
}
v = sb.length() == 0 ? VersionVector.MINS_VALUE : sb.toString();
break;
}
case '{' : {
if (++current == maxPos)
return null;
position[0] = current;
v = parseRawEnum(value, position, maxPos);
if (v == null)
return null;
current = position[0];
break;
}
case '<' : {
if (++current == maxPos)
return null;
position[0] = current;
v = parseRawVector(value, position, maxPos);
if (v == null)
return null;
current = position[0];
break;
}
case 'm' :
v = VersionVector.MAXS_VALUE;
++current;
break;
case 'M' :
v = VersionVector.MAX_VALUE;
++current;
break;
case '-' :
if (++current >= maxPos)
return null;
c = value.charAt(current);
if (c == 'M') {
++current;
v = VersionVector.MIN_VALUE;
break;
}
negate = true;
// Fall through to default
default : {
if (isDigit(c)) {
int start = current++;
while (current < maxPos && isDigit(value.charAt(current)))
++current;
int val = Integer.parseInt(value.substring(start, current));
if (negate)
val = -val;
v = valueOf(val);
break;
}
return null;
}
}
position[0] = current;
return v;
}
private static Comparable<?> parseRawVector(String value, int[] position, int maxPos) {
int pos = position[0];
if (pos >= maxPos)
return null;
char c = value.charAt(pos);
if (c == '>')
return null;
ArrayList<Comparable<?>> rawList = new ArrayList<Comparable<?>>();
boolean padMarkerSeen = (c == 'p');
if (padMarkerSeen) {
if (++pos >= maxPos)
return null;
position[0] = pos;
}
Comparable<?> pad = null;
for (;;) {
Comparable<?> elem = parseRawElement(value, position, maxPos);
if (elem == null)
return null;
if (padMarkerSeen)
pad = elem;
else
rawList.add(elem);
pos = position[0];
if (pos >= maxPos)
return null;
c = value.charAt(pos);
position[0] = ++pos;
if (c == '>')
break;
if (padMarkerSeen || pos >= maxPos)
return null;
if (c == 'p') {
padMarkerSeen = true;
continue;
}
if (c != '.')
return null;
}
pad = removeRedundantTrail(rawList, pad);
return new VersionVector(rawList.toArray(new Comparable[rawList.size()]), pad);
}
private static Comparable<?> parseRawEnum(String value, int[] position, int maxPos) {
int pos = position[0];
ArrayList<List<String>> identifiers = new ArrayList<List<String>>();
int ordinal = -1;
StringBuffer sb = new StringBuffer();
for (;;) {
if (pos >= maxPos)
return null;
char c = value.charAt(pos++);
while (c != '}' && c != ',') {
if (pos == maxPos)
return null;
if (c <= ' ')
return null;
if (c == '\\' || c == '^') {
if (c == '^')
ordinal = identifiers.size();
c = value.charAt(pos++);
if (pos == maxPos)
return null;
}
sb.append(c);
c = value.charAt(pos++);
}
identifiers.add(Collections.singletonList(sb.toString()));
if (c == '}')
break;
// c must be ',' at this point
sb.setLength(0);
}
if (ordinal == -1)
return null;
position[0] = pos;
return EnumDefinition.getSegment(identifiers, ordinal);
}
public static int skipWhite(String string, int pos) {
int top = string.length();
while (pos < top && string.charAt(pos) <= ' ')
++pos;
return pos;
}
public static int skipTrailingWhite(String string, int start, int end) {
while (end > start && string.charAt(end - 1) <= ' ')
--end;
return end;
}
}