blob: 4a31345e95c3e80b16133945a121daf73a26e0b2 [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;
/**
* This is the Omni Version Format parser. It will parse a version format in string form
* into a group of {@link VersionFormat.Fragment} elements. That group, wrapped in a
* {@link VersionFormat}, becomes the parser for versions corresponding to the format.
*
* The class is not intended to included in a public API. Instead VersionFormats should
* be created using {@link VersionFormat#parse(String)}
*
*/
class VersionFormatParser {
static class Instructions {
char[] characters = null;
Comparable defaultValue = null;
boolean ignore = false;
boolean inverted = false;
Comparable padValue = null;
int rangeMax = Integer.MAX_VALUE;
int rangeMin = 0;
}
static final VersionFormat.Qualifier EXACT_ONE_QUALIFIER = new VersionFormat.Qualifier(1, 1);
static final VersionFormat.Qualifier ONE_OR_MANY_QUALIFIER = new VersionFormat.Qualifier(1, Integer.MAX_VALUE);
static final VersionFormat.Qualifier ZERO_OR_MANY_QUALIFIER = new VersionFormat.Qualifier(0, Integer.MAX_VALUE);
static final VersionFormat.Qualifier ZERO_OR_ONE_QUALIFIER = new VersionFormat.Qualifier(0, 1);
private int current;
private List currentList;
private int eos;
private String format;
private int start;
VersionFormat.Fragment compile(String fmt, int pos, int maxPos) throws FormatException {
format = fmt;
if (start >= maxPos)
throw new FormatException(Messages.format_is_empty);
start = pos;
current = pos;
eos = maxPos;
currentList = new ArrayList();
while (current < eos)
parseFragment();
VersionFormat.Fragment topFrag;
switch (currentList.size()) {
case 0 :
throw new FormatException(Messages.format_is_empty);
case 1 :
VersionFormat.Fragment frag = (VersionFormat.Fragment) currentList.get(0);
if (frag.isGroup()) {
topFrag = frag;
break;
}
// Fall through to default
default :
topFrag = VersionFormat.createGroupFragment(null, EXACT_ONE_QUALIFIER, (VersionFormat.Fragment[]) currentList.toArray(new VersionFormat.Fragment[currentList.size()]), false);
}
currentList = null;
return topFrag;
}
private void assertChar(char expected) throws FormatException {
if (current >= eos)
throw formatException(NLS.bind(Messages.premature_end_of_format_expected_0, new String(new char[] {expected})));
char c = format.charAt(current);
if (c != expected)
throw formatException(c, new String(new char[] {expected}));
++current;
}
private FormatException formatException(char found, String expected) {
return formatException(new String(new char[] {found}), expected);
}
private FormatException formatException(String message) {
return new FormatException(NLS.bind(Messages.syntax_error_in_version_format_0_1_2, new Object[] {format.substring(start, eos), new Integer(current), message}));
}
private FormatException formatException(String found, String expected) {
return new FormatException(NLS.bind(Messages.syntax_error_in_version_format_0_1_found_2_expected_3, new Object[] {format.substring(start, eos), new Integer(current), found, expected}));
}
private FormatException illegalControlCharacter(char c) {
return formatException(NLS.bind(Messages.illegal_character_encountered_ascii_0, Version.valueOf(c)));
}
private String parseAndConsiderEscapeUntil(char endChar) throws FormatException {
StringBuffer sb = new StringBuffer();
while (current < eos) {
char c = format.charAt(current++);
if (c == endChar)
break;
if (c < 32)
throw illegalControlCharacter(c);
if (c == '\\') {
if (current == eos)
throw formatException(Messages.EOS_after_escape);
c = format.charAt(current++);
if (c < 32)
throw illegalControlCharacter(c);
}
sb.append(c);
}
return sb.toString();
}
private void parseAuto() throws FormatException {
VersionFormatParser.Instructions ep = parseProcessing();
if (ep != null) {
if (ep.padValue != null)
throw formatException(Messages.auto_can_not_have_pad_value);
}
currentList.add(VersionFormat.createAutoFragment(ep, parseQualifier()));
}
private void parseBracketGroup() throws FormatException {
List saveList = currentList;
currentList = new ArrayList();
while (current < eos && format.charAt(current) != ']')
parseFragment();
if (current == eos)
throw formatException(NLS.bind(Messages.premature_end_of_format_expected_0, "]")); //$NON-NLS-1$
++current;
VersionFormatParser.Instructions ep = parseProcessing();
saveList.add(VersionFormat.createGroupFragment(ep, ZERO_OR_ONE_QUALIFIER, (VersionFormat.Fragment[]) currentList.toArray(new VersionFormat.Fragment[currentList.size()]), false));
currentList = saveList;
}
private void parseCharacterGroup(VersionFormatParser.Instructions ep) throws FormatException {
assertChar('[');
StringBuffer sb = new StringBuffer();
outer: for (; current < eos; ++current) {
char c = format.charAt(current);
switch (c) {
case '\\' :
if (current + 1 < eos) {
sb.append(format.charAt(++current));
continue;
}
throw formatException(Messages.premature_end_of_format);
case '^' :
if (sb.length() == 0)
ep.inverted = true;
else
sb.append(c);
continue;
case ']' :
break outer;
case '-' :
if (sb.length() > 0 && current + 1 < eos) {
char rangeEnd = format.charAt(++current);
if (rangeEnd == ']') {
// Use dash verbatim when last in range
sb.append(c);
break outer;
}
char rangeStart = sb.charAt(sb.length() - 1);
if (rangeEnd < rangeStart)
throw formatException(Messages.negative_character_range);
while (++rangeStart <= rangeEnd)
sb.append(rangeStart);
continue;
}
// Fall through to default
default :
if (c < 32)
throw illegalControlCharacter(c);
sb.append(c);
}
}
assertChar(']');
int top = sb.length();
char[] chars = new char[top];
sb.getChars(0, top, chars, 0);
ep.characters = chars;
}
private void parseDelimiter() throws FormatException {
VersionFormatParser.Instructions ep = parseProcessing();
if (ep != null) {
if (ep.rangeMin != 0 || ep.rangeMax != Integer.MAX_VALUE)
throw formatException(Messages.delimiter_can_not_have_range);
if (ep.ignore)
throw formatException(Messages.delimiter_can_not_be_ignored);
if (ep.defaultValue != null)
throw formatException(Messages.delimiter_can_not_have_default_value);
if (ep.padValue != null)
throw formatException(Messages.delimiter_can_not_have_pad_value);
}
currentList.add(VersionFormat.createDelimiterFragment(ep, parseQualifier()));
}
private void parseFragment() throws FormatException {
if (current == eos)
throw formatException(Messages.premature_end_of_format);
char c = format.charAt(current++);
switch (c) {
case '(' :
parseGroup(false);
break;
case '<' :
parseGroup(true);
break;
case '[' :
parseBracketGroup();
break;
case 'a' :
parseAuto();
break;
case 'r' :
parseRaw();
break;
case 'n' :
parseNumber(false);
break;
case 'N' :
parseNumber(true);
break;
case 's' :
parseString(false);
break;
case 'S' :
parseString(true);
break;
case 'd' :
parseDelimiter();
break;
case 'q' :
parseQuotedString();
break;
case 'p' :
parsePad();
break;
default :
parseLiteral(c);
}
}
private void parseGroup(boolean array) throws FormatException {
List saveList = currentList;
currentList = new ArrayList();
char expectedEnd = array ? '>' : ')';
while (current < eos && format.charAt(current) != expectedEnd)
parseFragment();
assertChar(expectedEnd);
VersionFormatParser.Instructions ep = parseProcessing();
if (ep != null) {
if (ep.characters != null)
throw formatException(Messages.array_can_not_have_character_group);
if (ep.rangeMax != Integer.MAX_VALUE && ep.padValue != null) {
throw formatException(Messages.cannot_combine_range_upper_bound_with_pad_value);
}
}
if (currentList.isEmpty())
throw formatException(array ? Messages.array_can_not_be_empty : Messages.group_can_not_be_empty);
saveList.add(VersionFormat.createGroupFragment(ep, parseQualifier(), (VersionFormat.Fragment[]) currentList.toArray(new VersionFormat.Fragment[currentList.size()]), array));
currentList = saveList;
}
private int parseIntegerLiteral() throws FormatException {
if (current == eos)
throw formatException(NLS.bind(Messages.premature_end_of_format_expected_0, "<integer>")); //$NON-NLS-1$
char c = format.charAt(current);
if (!VersionParser.isDigit(c))
throw formatException(c, "<integer>"); //$NON-NLS-1$
int value = c - '0';
while (++current < eos) {
c = format.charAt(current);
if (!VersionParser.isDigit(c))
break;
value *= 10;
value += (c - '0');
}
return value;
}
private void parseLiteral(char c) throws FormatException {
String value;
switch (c) {
case '\'' :
value = parseAndConsiderEscapeUntil(c);
break;
case ')' :
case ']' :
case '{' :
case '}' :
case '?' :
case '*' :
throw formatException(c, "<literal>"); //$NON-NLS-1$
default :
if (VersionParser.isLetterOrDigit(c))
throw formatException(c, "<literal>"); //$NON-NLS-1$
if (c < 32)
throw illegalControlCharacter(c);
if (c == '\\') {
if (current == eos)
throw formatException(Messages.EOS_after_escape);
c = format.charAt(current++);
if (c < 32)
throw illegalControlCharacter(c);
}
value = new String(new char[] {c});
}
currentList.add(VersionFormat.createLiteralFragment(parseQualifier(), value));
}
private int[] parseMinMax() throws FormatException {
int max = Integer.MAX_VALUE;
++current;
int min = parseIntegerLiteral();
char c = format.charAt(current);
if (c == '}') {
max = min;
if (max == 0)
throw formatException(Messages.range_max_cannot_be_zero);
++current;
} else if (c == ',' && current + 1 < eos) {
if (format.charAt(++current) != '}') {
max = parseIntegerLiteral();
if (max == 0)
throw formatException(Messages.range_max_cannot_be_zero);
if (max < min)
throw formatException(Messages.range_max_cannot_be_less_then_range_min);
}
assertChar('}');
} else
throw formatException(c, "},"); //$NON-NLS-1$
return new int[] {min, max};
}
private void parseNumber(boolean signed) throws FormatException {
VersionFormatParser.Instructions ep = parseProcessing();
if (ep != null) {
if (ep.padValue != null)
throw formatException(Messages.number_can_not_have_pad_value);
}
currentList.add(VersionFormat.createNumberFragment(ep, parseQualifier(), signed));
}
private void parsePad() throws FormatException {
currentList.add(VersionFormat.createPadFragment(parseQualifier()));
}
private VersionFormatParser.Instructions parseProcessing() throws FormatException {
if (current >= eos)
return null;
char c = format.charAt(current);
if (c != '=')
return null;
VersionFormatParser.Instructions ep = new VersionFormatParser.Instructions();
do {
current++;
parseProcessingInstruction(ep);
} while (current < eos && format.charAt(current) == '=');
return ep;
}
private void parseProcessingInstruction(VersionFormatParser.Instructions processing) throws FormatException {
if (current == eos)
throw formatException(Messages.premature_end_of_format);
char c = format.charAt(current);
if (c == 'p') {
// =pad(<raw-element>);
//
if (processing.padValue != null)
throw formatException(Messages.pad_defined_more_then_once);
if (processing.ignore)
throw formatException(Messages.cannot_combine_ignore_with_other_instruction);
++current;
processing.padValue = parseRawElement();
} else if (c == '!') {
// =ignore;
//
if (processing.ignore)
throw formatException(Messages.ignore_defined_more_then_once);
if (processing.padValue != null || processing.characters != null || processing.rangeMin != 0 || processing.rangeMax != Integer.MAX_VALUE || processing.defaultValue != null)
throw formatException(Messages.cannot_combine_ignore_with_other_instruction);
++current;
processing.ignore = true;
} else if (c == '[') {
// =[<character group];
//
if (processing.characters != null)
throw formatException(Messages.character_group_defined_more_then_once);
if (processing.ignore)
throw formatException(Messages.cannot_combine_ignore_with_other_instruction);
parseCharacterGroup(processing);
} else if (c == '{') {
// ={min,max};
//
if (processing.rangeMin != 0 || processing.rangeMax != Integer.MAX_VALUE)
throw formatException(Messages.range_defined_more_then_once);
if (processing.ignore)
throw formatException(Messages.cannot_combine_ignore_with_other_instruction);
int[] minMax = parseMinMax();
processing.rangeMin = minMax[0];
processing.rangeMax = minMax[1];
} else {
// =<raw-element>;
if (processing.defaultValue != null)
throw formatException(Messages.default_defined_more_then_once);
if (processing.ignore)
throw formatException(Messages.cannot_combine_ignore_with_other_instruction);
processing.defaultValue = parseRawElement();
}
assertChar(';');
}
private VersionFormat.Qualifier parseQualifier() throws FormatException {
if (current >= eos)
return EXACT_ONE_QUALIFIER;
char c = format.charAt(current);
if (c == '?') {
++current;
return ZERO_OR_ONE_QUALIFIER;
}
if (c == '*') {
++current;
return ZERO_OR_MANY_QUALIFIER;
}
if (c == '+') {
++current;
return ONE_OR_MANY_QUALIFIER;
}
if (c != '{')
return EXACT_ONE_QUALIFIER;
int[] minMax = parseMinMax();
int min = minMax[0];
int max = minMax[1];
// Use singletons for commonly used ranges
//
if (min == 0) {
if (max == 1)
return ZERO_OR_ONE_QUALIFIER;
if (max == Integer.MAX_VALUE)
return ZERO_OR_MANY_QUALIFIER;
} else if (min == 1) {
if (max == 1)
return EXACT_ONE_QUALIFIER;
if (max == Integer.MAX_VALUE)
return ONE_OR_MANY_QUALIFIER;
}
return new VersionFormat.Qualifier(min, max);
}
private void parseQuotedString() throws FormatException {
VersionFormatParser.Instructions ep = parseProcessing();
if (ep != null) {
if (ep.padValue != null)
throw formatException(Messages.string_can_not_have_pad_value);
}
currentList.add(VersionFormat.createQuotedFragment(ep, parseQualifier()));
}
private void parseRaw() throws FormatException {
VersionFormatParser.Instructions ep = parseProcessing();
if (ep != null) {
if (ep.padValue != null)
throw formatException(Messages.raw_element_can_not_have_pad_value);
}
currentList.add(VersionFormat.createRawFragment(ep, parseQualifier()));
}
private Comparable parseRawElement() throws FormatException {
int[] position = new int[] {current};
Comparable v = VersionParser.parseRawElement(format, position, eos);
if (v == null)
throw new FormatException(NLS.bind(Messages.raw_element_expected_0, format));
current = position[0];
return v;
}
private void parseString(boolean unlimited) throws FormatException {
VersionFormatParser.Instructions ep = parseProcessing();
if (ep != null) {
if (ep.padValue != null)
throw formatException(Messages.string_can_not_have_pad_value);
}
currentList.add(VersionFormat.createStringFragment(ep, parseQualifier(), unlimited));
}
}