package org.eclipse.imp.preferences; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.eclipse.imp.preferences.PreferencesService.ConstantEvaluator; import org.eclipse.imp.preferences.PreferencesService.ParamEvaluator; /** * This class contains a parser for strings such as: * * -- "foobar${pluginLoc:lpg.runtime}bletchbletch" * -- "abcdef" * -- "${pluginResource:lpg.runtime/lpgexe/lpg-${os}_${arch}}" * * as well as the small AST hierarchy to represent that results of parsing and * subsititute the variables and parametrized variables they represent. */ public class PreferenceValues { private int cursor = 0; private final static char[] escapable; static { escapable = new char[] { ':' , '{' , ' ' , '}' , '$', '\\' }; // sorting is needed since we use Arrays.binarySearch later Arrays.sort(escapable); } private char[] input; public Value parse(String input) { try { this.input = input.toCharArray(); this.cursor = 0; return parseComposite(); } finally { // this is to clean up the working state of the parser in case a parse error is thrown this.input = null; this.cursor = -1; } } private char peek() { if (cursor < input.length) { return input[cursor]; } else { return '\0'; } } private char next() { if (cursor < input.length) { return input[cursor++]; } else { return '\0'; } } private Value parseComposite() { char ch = peek(); List<Value> elements = new LinkedList<Value>(); while (ch != '}' && cursor < input.length) { switch (ch = next()) { case '}': break; case '$': if (peek() == '{') { elements.add(parseVariable()); } else { return new Error("expected \'{\', but got " + peek() + " at offset " + cursor); } break; case '\\': elements.add(parseEscape()); break; default: elements.add(new Terminal(Character.toString(ch))); } } return new Composite(elements); } private Value parseEscape() { char ch = next(); if (Character.isLetterOrDigit(ch) || Arrays.binarySearch(escapable, ch) >= 0) { return new Terminal(Character.toString(ch)); } else { return new Error("unknown escape \\" + ch + " at " + cursor + " in \"" + Arrays.toString(input) + "\""); } } private Value parseVariable() { StringBuilder name = new StringBuilder(); char ch = next(); if (ch == '{') { for (ch = next();ch != ':' && ch != '}' && cursor < input.length; ch = next()) { name.append(ch); } if (ch == ':') { return new ParameterizedVariable(name.toString(), parseComposite()); } else if (ch == '}') { if (name.length() == 0) { return new Error("empty variable name is not allowed at " + cursor); } return new Variable(name.toString()); } else { return new Error("expected } but got " + ch + " at " + cursor); } } else { return new Error("expected { but got " + ch + " at " + cursor); } } public interface Value { public String substitute(Map<String, ParamEvaluator> params, Map<String, ConstantEvaluator> constants); } public static class Composite implements Value { private final List<Value> elements; public Composite(List<Value> elements) { this.elements = Collections.unmodifiableList(elements); } public String substitute(Map<String, ParamEvaluator> params, Map<String, ConstantEvaluator> constants) { StringBuilder n = new StringBuilder(); for (Value e : elements) { n.append(e.substitute(params, constants)); } return n.toString(); } } public static class Terminal implements Value { private final String value; public Terminal(String value) { this.value = value; } public String substitute(Map<String, ParamEvaluator> params, Map<String, ConstantEvaluator> constants) { return value; } } /** * We safe parse errors in this AST class, which is a design flaw, but at least * it is backward compatible with the previous implementation of the preference values. */ public static class Error implements Value { private final String message; public Error(String message) { this.message = message; } public String substitute(Map<String, ParamEvaluator> params, Map<String, ConstantEvaluator> constants) { return "Invalid preference: " + message; } } public static class Variable implements Value { private final String name; public Variable(String name) { this.name = name; } public String substitute(Map<String, ParamEvaluator> params, Map<String, ConstantEvaluator> constants) { if (constants.containsKey(name)) { return constants.get(name).getValue(); } else { return "${" + name + "}"; } } } public static class ParameterizedVariable implements Value { private final String name; private final Value arg; public ParameterizedVariable(String name, Value arg) { this.name = name; this.arg = arg; } public String substitute(Map<String, ParamEvaluator> params, Map<String, ConstantEvaluator> constants) { if (params.containsKey(name)) { return params.get(name).getValue(arg.substitute(params, constants)); } else { return "${" + name + ":" + arg.substitute(params, constants) + "}"; } } } }