/******************************************************************************* * Copyright (c) 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.expression.parser; import java.util.*; import org.eclipse.equinox.internal.p2.metadata.Messages; import org.eclipse.equinox.internal.p2.metadata.expression.IExpressionConstants; import org.eclipse.equinox.internal.p2.metadata.expression.LDAPApproximation; import org.eclipse.equinox.p2.metadata.expression.*; import org.eclipse.osgi.util.NLS; /** * Parser class for OSGi filter strings. This class parses the complete filter string and builds a tree of Filter * objects rooted at the parent. */ public class LDAPFilterParser { @SuppressWarnings("serial") private static final Map<String, IFilterExpression> filterCache = Collections.<String, IFilterExpression> synchronizedMap(new LinkedHashMap<String, IFilterExpression>() { public boolean removeEldestEntry(Map.Entry<String, IFilterExpression> expr) { return size() > 64; } }); private final IExpressionFactory factory; private final IExpression self; private final StringBuffer sb = new StringBuffer(); private String filterString; private int position; public LDAPFilterParser(IExpressionFactory factory) { this.factory = factory; self = factory.variable(IExpressionConstants.VARIABLE_THIS); position = 0; } public IFilterExpression parse(String filterStr) { IFilterExpression filter = filterCache.get(filterStr); if (filter != null) return filter; synchronized (this) { filterString = filterStr; position = 0; try { IExpression expr = parseFilter(); if (position != filterString.length()) throw syntaxException(Messages.filter_trailing_characters); filter = factory.filterExpression(expr); filterCache.put(filterStr, filter); return filter; } catch (StringIndexOutOfBoundsException e) { throw syntaxException(Messages.filter_premature_end); } } } private IExpression parseAnd() { skipWhiteSpace(); char c = filterString.charAt(position); if (c != '(') throw syntaxException(Messages.filter_missing_leftparen); ArrayList<IExpression> operands = new ArrayList<IExpression>(); while (c == '(') { IExpression child = parseFilter(); if (!operands.contains(child)) operands.add(child); c = filterString.charAt(position); } // int sz = operands.size(); // return sz == 1 ? operands.get(0) : factory.and(operands.toArray(new IExpression[sz])); return factory.normalize(operands, IExpression.TYPE_AND); } private IExpression parseAttr() { skipWhiteSpace(); int begin = position; int end = position; char c = filterString.charAt(begin); while (!(c == '~' || c == '<' || c == '>' || c == '=' || c == '(' || c == ')')) { position++; if (!Character.isWhitespace(c)) end = position; c = filterString.charAt(position); } if (end == begin) throw syntaxException(Messages.filter_missing_attr); return factory.member(self, filterString.substring(begin, end)); } private IExpression parseFilter() { IExpression filter; skipWhiteSpace(); if (filterString.charAt(position) != '(') throw syntaxException(Messages.filter_missing_leftparen); position++; filter = parseFiltercomp(); skipWhiteSpace(); if (filterString.charAt(position) != ')') throw syntaxException(Messages.filter_missing_rightparen); position++; skipWhiteSpace(); return filter; } private IExpression parseFiltercomp() { skipWhiteSpace(); char c = filterString.charAt(position); switch (c) { case '&' : { position++; return parseAnd(); } case '|' : { position++; return parseOr(); } case '!' : { position++; return parseNot(); } } return parseItem(); } private IExpression parseItem() { IExpression attr = parseAttr(); skipWhiteSpace(); String value; boolean[] hasWild = {false}; char c = filterString.charAt(position); switch (c) { case '~' : case '>' : case '<' : if (filterString.charAt(position + 1) != '=') throw syntaxException(Messages.filter_invalid_operator); position += 2; int savePos = position; value = parseValue(hasWild); if (hasWild[0]) { // Unescaped wildcard found. This is not legal for the given operator position = savePos; throw syntaxException(Messages.filter_invalid_value); } switch (c) { case '>' : return factory.greaterEqual(attr, factory.constant(value)); case '<' : return factory.lessEqual(attr, factory.constant(value)); } return factory.matches(attr, factory.constant(new LDAPApproximation(value))); case '=' : position++; value = parseValue(hasWild); return hasWild[0] ? factory.matches(attr, factory.constant(SimplePattern.compile(value))) : factory.equals(attr, factory.constant(value)); } throw syntaxException(Messages.filter_invalid_operator); } private IExpression parseNot() { skipWhiteSpace(); if (filterString.charAt(position) != '(') throw syntaxException(Messages.filter_missing_leftparen); return factory.not(parseFilter()); } private IExpression parseOr() { skipWhiteSpace(); char c = filterString.charAt(position); if (c != '(') throw syntaxException(Messages.filter_missing_leftparen); ArrayList<IExpression> operands = new ArrayList<IExpression>(); while (c == '(') { IExpression child = parseFilter(); operands.add(child); c = filterString.charAt(position); } // int sz = operands.size(); // return sz == 1 ? operands.get(0) : factory.or(operands.toArray(new IExpression[sz])); return factory.normalize(operands, IExpression.TYPE_OR); } private static int hexValue(char c) { int v; if (c <= '9') v = c - '0'; else if (c <= 'F') v = (c - 'A') + 10; else v = (c - 'a') + 10; return v; } private String parseValue(boolean[] hasWildBin) { sb.setLength(0); int savePos = position; boolean hasEscapedWild = false; parseloop: while (true) { char c = filterString.charAt(position); switch (c) { case '*' : if (hasEscapedWild && !hasWildBin[0]) { // We must redo the parse. position = savePos; hasWildBin[0] = true; return parseValue(hasWildBin); } hasWildBin[0] = true; sb.append(c); position++; break; case ')' : break parseloop; case '(' : throw syntaxException(Messages.filter_invalid_value); case '\\' : c = filterString.charAt(++position); if (c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f' && position + 1 < filterString.length()) { char nc = filterString.charAt(position + 1); if (nc >= '0' && nc <= '9' || nc >= 'A' && nc <= 'F' || nc >= 'a' && nc <= 'f') { // Assume proper \xx escape where xx are hex digits ++position; c = (char) (((hexValue(c) << 4) & 0xf0) | (hexValue(nc) & 0x0f)); if (c == '*' && hasWildBin != null) { hasEscapedWild = true; if (hasWildBin[0]) sb.append('\\'); } } } /* fall through into default */ default : sb.append(c); position++; break; } } if (sb.length() == 0) throw syntaxException(Messages.filter_missing_value); return sb.toString(); } private void skipWhiteSpace() { for (int top = filterString.length(); position < top; ++position) if (!Character.isWhitespace(filterString.charAt(position))) break; } protected ExpressionParseException syntaxException(String message) { return new ExpressionParseException(NLS.bind(message, filterString, Integer.toString(position))); } }