blob: 98560bf7ae59c4e62f4dfcde781259bd62a8b410 [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.util.ArrayList;
import java.util.List;
import org.eclipse.osgi.util.NLS;
/**
* The Omni Version parser. Not intended for public API. Instead use
* {@link Version#Version(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.
*/
abstract class VersionParser {
static void removeRedundantTrail(List segments, Comparable padValue) {
Comparable redundantTrail = padValue == null ? VersionVector.MIN_VALUE : padValue;
int idx = segments.size();
while (--idx >= 0 && segments.get(idx).equals(redundantTrail))
segments.remove(idx);
}
static final String RAW_PREFIX = "raw:"; //$NON-NLS-1$
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
* @param receiver The version to be filled in
* @returns <code>true</code> if a version indeed was parsed or <code>false</code> if the string
* contained only whitespace.
* @throws IllegalArgumentException if the version is malformed
*/
static boolean parseInto(String version, int start, int maxPos, Version receiver) throws IllegalArgumentException {
// trim leading and trailing whitespace
int pos = skipWhite(version, start);
maxPos = skipTrailingWhite(version, start, maxPos);
if (pos == maxPos)
return false;
Comparable[] padReturn = new Comparable[1];
Comparable[] vector = null;
Comparable pad = null;
VersionFormat fmt = null;
char c = version.charAt(pos);
if (isDigit(c)) {
fmt = VersionFormat.OSGI_FORMAT;
vector = fmt.parse(version, pos, maxPos, padReturn);
receiver.init(vector, padReturn[0], fmt, version);
return true;
}
if (!isLetter(c))
throw new IllegalArgumentException();
if (version.startsWith(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, padReturn);
pad = padReturn[0];
pos = end;
if (pos == maxPos) {
// This was a pure raw version
//
receiver.init(vector, pad, null, null);
return true;
}
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 (FormatException 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)));
receiver.init(vector, pad, fmt, null);
return true;
}
}
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, padReturn);
pad = padReturn[0];
}
receiver.init(vector, pad, fmt, version.substring(pos));
return true;
}
static boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
static boolean isLetter(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
static boolean isLetterOrDigit(char c) {
return isDigit(c) || isLetter(c);
}
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.toString();
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 = Version.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 rawList = new ArrayList();
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;
}
removeRedundantTrail(rawList, pad);
return new VersionVector((Comparable[]) rawList.toArray(new Comparable[rawList.size()]), pad);
}
static int skipWhite(String string, int pos) {
int top = string.length();
while (pos < top && string.charAt(pos) <= ' ')
++pos;
return pos;
}
static int skipTrailingWhite(String string, int start, int end) {
while (end > start && string.charAt(end - 1) <= ' ')
--end;
return end;
}
}