| /******************************************************************************* |
| * 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)); |
| } |
| } |