Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression')
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/All.java42
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/And.java50
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/At.java96
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Binary.java135
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CoercingComparator.java392
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CollectionFilter.java85
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Compare.java60
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Equals.java52
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/EvaluationContext.java161
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Exists.java42
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Expression.java341
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/ExpressionFactory.java125
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IExpressionConstants.java58
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IRepeatableIterator.java25
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPApproximation.java77
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPFilter.java69
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LambdaExpression.java72
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Literal.java123
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MatchExpression.java83
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Matches.java95
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java178
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MemberProvider.java139
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/NAry.java59
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Not.java48
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Or.java50
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Parameter.java64
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/RepeatableIterator.java237
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Unary.java57
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Variable.java79
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/ExpressionParser.java617
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/LDAPFilterParser.java268
31 files changed, 3979 insertions, 0 deletions
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/All.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/All.java
new file mode 100644
index 000000000..4de975525
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/All.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import java.util.Iterator;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * A collection filter that yields true if the <code>filter</code> yields true for
+ * all of the elements of the <code>collection</code>
+ */
+final class All extends CollectionFilter {
+ All(Expression collection, LambdaExpression lambda) {
+ super(collection, lambda);
+ }
+
+ protected Object evaluate(IEvaluationContext context, Iterator<?> itor) {
+ Variable variable = lambda.getItemVariable();
+ while (itor.hasNext()) {
+ variable.setValue(context, itor.next());
+ if (lambda.evaluate(context) != Boolean.TRUE)
+ return Boolean.FALSE;
+ }
+ return Boolean.TRUE;
+ }
+
+ public int getExpressionType() {
+ return TYPE_ALL;
+ }
+
+ public String getOperator() {
+ return KEYWORD_ALL;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/And.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/And.java
new file mode 100644
index 000000000..7e8adf20e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/And.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * 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;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * n-ary AND operator. The full evaluation is <code>true</code> if all its operands evaluate to
+ * <code>true</code>.
+ */
+final class And extends NAry {
+ And(Expression[] operands) {
+ super(assertLength(operands, 2, OPERATOR_AND));
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ for (int idx = 0; idx < operands.length; ++idx) {
+ if (operands[idx].evaluate(context) != Boolean.TRUE)
+ return Boolean.FALSE;
+ }
+ return Boolean.TRUE;
+ }
+
+ public int getExpressionType() {
+ return TYPE_AND;
+ }
+
+ public String getOperator() {
+ return OPERATOR_AND;
+ }
+
+ public int getPriority() {
+ return PRIORITY_AND;
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ buf.append("(&"); //$NON-NLS-1$
+ for (int idx = 0; idx < operands.length; ++idx)
+ operands[idx].toLDAPString(buf);
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/At.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/At.java
new file mode 100644
index 000000000..e089c1dbc
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/At.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import java.util.*;
+import org.eclipse.equinox.internal.p2.metadata.expression.Member.DynamicMember;
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * This class represents indexed or keyed access to an indexed collection
+ * or a map.
+ */
+public class At extends Binary {
+ protected At(Expression lhs, Expression rhs) {
+ super(lhs, rhs);
+ }
+
+ protected Object handleMember(IEvaluationContext context, Member member, Object instance, boolean[] handled) {
+ if (instance instanceof IInstallableUnit) {
+ if ("properties".equals(member.getName())) { //$NON-NLS-1$
+ // Avoid full copy of the properties map just to get one member
+ handled[0] = true;
+ return ((IInstallableUnit) instance).getProperty((String) rhs.evaluate(context));
+ }
+ }
+ return null;
+ }
+
+ public Object evaluate(org.eclipse.equinox.p2.metadata.expression.IEvaluationContext context) {
+ Object lval;
+ if (lhs instanceof DynamicMember) {
+ DynamicMember lm = (DynamicMember) lhs;
+ Object instance = lm.operand.evaluate(context);
+ boolean[] handled = new boolean[] {false};
+ Object result = handleMember(context, lm, instance, handled);
+ if (handled[0])
+ return result;
+ lval = lm.invoke(instance);
+ } else
+ lval = lhs.evaluate(context);
+
+ Object rval = rhs.evaluate(context);
+ if (lval == null)
+ throw new IllegalArgumentException("Unable to use [] on null"); //$NON-NLS-1$
+
+ if (lval instanceof Map<?, ?>)
+ return ((Map<?, ?>) lval).get(rval);
+
+ if (rval instanceof Number) {
+ if (lval instanceof List<?>)
+ return ((List<?>) lval).get(((Number) rval).intValue());
+ if (lval != null && lval.getClass().isArray())
+ return ((Object[]) lval)[((Number) rval).intValue()];
+ }
+
+ if (lval instanceof Dictionary<?, ?>)
+ return ((Dictionary<?, ?>) lval).get(rval);
+
+ throw new IllegalArgumentException("Unable to use [] on a " + lval.getClass().getName()); //$NON-NLS-1$
+ }
+
+ public Iterator<?> evaluateAsIterator(IEvaluationContext context) {
+ Object value = evaluate(context);
+ if (!(value instanceof Iterator<?>))
+ value = RepeatableIterator.create(value);
+ return (Iterator<?>) value;
+ }
+
+ public int getExpressionType() {
+ return TYPE_AT;
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ appendOperand(bld, rootVariable, lhs, getPriority());
+ bld.append('[');
+ appendOperand(bld, rootVariable, rhs, PRIORITY_MAX);
+ bld.append(']');
+ }
+
+ public String getOperator() {
+ return OPERATOR_AT;
+ }
+
+ public int getPriority() {
+ return PRIORITY_MEMBER;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Binary.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Binary.java
new file mode 100644
index 000000000..f4f7da504
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Binary.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * 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;
+
+import org.eclipse.equinox.internal.p2.metadata.expression.Member.DynamicMember;
+import org.eclipse.equinox.p2.metadata.Version;
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * The abstract base class for all binary operations
+ */
+public abstract class Binary extends Expression {
+ public final Expression lhs;
+
+ public final Expression rhs;
+
+ protected Binary(Expression lhs, Expression rhs) {
+ this.lhs = lhs;
+ this.rhs = rhs;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return super.accept(visitor) && lhs.accept(visitor) && rhs.accept(visitor);
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0) {
+ Binary be = (Binary) e;
+ cmp = lhs.compareTo(be.lhs);
+ if (cmp == 0)
+ cmp = rhs.compareTo(be.rhs);
+ }
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ if (super.equals(o)) {
+ Binary bo = (Binary) o;
+ return lhs.equals(bo.lhs) && rhs.equals(bo.rhs);
+ }
+ return false;
+ }
+
+ public int getPriority() {
+ return PRIORITY_BINARY; // Default priority
+ }
+
+ public int hashCode() {
+ int result = 31 + lhs.hashCode();
+ return 31 * result + rhs.hashCode();
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ appendOperand(bld, rootVariable, lhs, getPriority());
+ bld.append(' ');
+ bld.append(getOperator());
+ bld.append(' ');
+ appendOperand(bld, rootVariable, rhs, getPriority());
+ }
+
+ /**
+ * Appends the LDAP filter attribute name from the lhs expression if
+ * possible.
+ * @throws UnsupportedOperationException when this expression does not conform to an
+ * LDAP filter binary expression
+ */
+ void appendLDAPAttribute(StringBuffer buf) {
+ if (lhs instanceof DynamicMember) {
+ DynamicMember attr = (DynamicMember) lhs;
+ if (attr.operand instanceof Variable) {
+ buf.append(attr.getName());
+ return;
+ }
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ private static char hexChar(int value) {
+ return (char) (value < 10 ? ('0' + value) : ('a' + (value - 10)));
+ }
+
+ static void appendLDAPEscaped(StringBuffer bld, String str) {
+ appendLDAPEscaped(bld, str, true);
+ }
+
+ static void appendLDAPEscaped(StringBuffer bld, String str, boolean escapeWild) {
+ int top = str.length();
+ for (int idx = 0; idx < top; ++idx) {
+ char c = str.charAt(idx);
+ if (!escapeWild) {
+ if (c == '*') {
+ bld.append(c);
+ continue;
+ } else if (c == '\\' && idx + 1 < top && str.charAt(idx + 1) == '*') {
+ bld.append("\\2a"); //$NON-NLS-1$
+ ++idx;
+ continue;
+ }
+ }
+ if (c == '(' || c == ')' || c == '*' || c == '\\' || c < ' ' || c > 127) {
+ short cs = (short) c;
+ bld.append('\\');
+ bld.append(hexChar((cs & 0x00f0) >> 4));
+ bld.append(hexChar(cs & 0x000f));
+ } else
+ bld.append(c);
+ }
+ }
+
+ /**
+ * Appends the LDAP filter value from the rhs expression if
+ * possible.
+ * @throws UnsupportedOperationException when this expression does not conform to an
+ * LDAP filter binary expression
+ */
+ void appendLDAPValue(StringBuffer buf) {
+ if (rhs instanceof Literal) {
+ Object value = rhs.evaluate(null);
+ if (value instanceof String || value instanceof Version) {
+ appendLDAPEscaped(buf, value.toString());
+ return;
+ }
+ }
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CoercingComparator.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CoercingComparator.java
new file mode 100644
index 000000000..c69e75e32
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CoercingComparator.java
@@ -0,0 +1,392 @@
+/*******************************************************************************
+ * 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;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Comparator;
+import org.eclipse.equinox.internal.p2.metadata.MetadataActivator;
+import org.eclipse.equinox.p2.metadata.Version;
+
+/**
+ * A comparator that performs coercion if needed before comparison.
+ * @param <T> The type for the comparator.
+ */
+public abstract class CoercingComparator<T> {
+ static class BooleanCoercer extends CoercingComparator<Boolean> {
+ public int compare(Boolean o1, Boolean o2) {
+ return o1.booleanValue() == o2.booleanValue() ? 0 : (o1.booleanValue() ? 1 : -1);
+ }
+
+ @Override
+ Boolean coerce(Object v) {
+ if (v instanceof Boolean)
+ return (Boolean) v;
+ if (v instanceof String) {
+ String sv = ((String) v).trim();
+ if (sv.equalsIgnoreCase("true")) //$NON-NLS-1$
+ return Boolean.TRUE;
+ if (sv.equalsIgnoreCase("false")) //$NON-NLS-1$
+ return Boolean.FALSE;
+ }
+ throw uncoercable(v);
+ }
+
+ @Override
+ Class<Boolean> getCoerceClass() {
+ return Boolean.class;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 7;
+ }
+ }
+
+ static class ClassCoercer extends CoercingComparator<Class<?>> {
+ public int compare(Class<?> o1, Class<?> o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+
+ @Override
+ Class<?> coerce(Object v) {
+ if (v instanceof Class<?>)
+ return (Class<?>) v;
+ if (v instanceof String) {
+ try {
+ return MetadataActivator.context.getBundle().loadClass(((String) v).trim());
+ } catch (Exception e) {
+ //
+ }
+ }
+ throw uncoercable(v);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ Class<Class<?>> getCoerceClass() {
+ Class<?> cls = Class.class;
+ return (Class<Class<?>>) cls;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 11;
+ }
+ }
+
+ static class FromStringCoercer<T extends Comparable<Object>> extends CoercingComparator<T> {
+ private final Class<T> coerceClass;
+ private final Constructor<T> constructor;
+
+ public FromStringCoercer(Class<T> coerceClass, Constructor<T> constructor) {
+ this.coerceClass = coerceClass;
+ this.constructor = constructor;
+ }
+
+ @Override
+ T coerce(Object v) {
+ if (v instanceof String) {
+ try {
+ return constructor.newInstance(new Object[] {((String) v).trim()});
+ } catch (Exception e) {
+ //
+ }
+ }
+ throw uncoercable(v);
+ }
+
+ @Override
+ int compare(T o1, T o2) {
+ return o1.compareTo(o2);
+ }
+
+ @Override
+ Class<T> getCoerceClass() {
+ return coerceClass;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 0;
+ }
+ }
+
+ static class IntegerCoercer extends CoercingComparator<Integer> {
+ public int compare(Integer o1, Integer o2) {
+ return o1.compareTo(o2);
+ }
+
+ @Override
+ Integer coerce(Object v) {
+ if (v instanceof Integer)
+ return (Integer) v;
+ if (v instanceof Number)
+ return new Integer(((Number) v).intValue());
+ if (v instanceof String) {
+ try {
+ return Integer.valueOf(((String) v).trim());
+ } catch (NumberFormatException e) {
+ //
+ }
+ }
+ throw uncoercable(v);
+ }
+
+ @Override
+ Class<Integer> getCoerceClass() {
+ return Integer.class;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 6;
+ }
+ }
+
+ static class LongCoercer extends CoercingComparator<Long> {
+ public int compare(Long o1, Long o2) {
+ return o1.compareTo(o2);
+ }
+
+ @Override
+ Long coerce(Object v) {
+ if (v instanceof Long)
+ return (Long) v;
+ if (v instanceof Number)
+ return new Long(((Number) v).longValue());
+ if (v instanceof String) {
+ try {
+ return Long.valueOf(((String) v).trim());
+ } catch (NumberFormatException e) {
+ //
+ }
+ }
+ throw uncoercable(v);
+ }
+
+ @Override
+ Class<Long> getCoerceClass() {
+ return Long.class;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 5;
+ }
+ }
+
+ static class StringCoercer extends CoercingComparator<String> {
+ public int compare(String o1, String o2) {
+ return o1.compareTo(o2);
+ }
+
+ @Override
+ String coerce(Object v) {
+ if (v instanceof Class<?>)
+ return ((Class<?>) v).getName();
+ return v.toString();
+ }
+
+ @Override
+ Class<String> getCoerceClass() {
+ return String.class;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 10;
+ }
+ }
+
+ static class VersionCoercer extends CoercingComparator<Version> {
+ public int compare(Version o1, Version o2) {
+ return o1.compareTo(o2);
+ }
+
+ boolean canCoerceTo(Class<?> cls) {
+ return Version.class.isAssignableFrom(cls);
+ }
+
+ @Override
+ Version coerce(Object v) {
+ if (v instanceof Version)
+ return (Version) v;
+ if (v instanceof String)
+ return Version.create((String) v);
+ if (v instanceof String) {
+ try {
+ return Version.create((String) v);
+ } catch (NumberFormatException e) {
+ //
+ }
+ }
+ throw uncoercable(v);
+ }
+
+ @Override
+ Class<Version> getCoerceClass() {
+ return Version.class;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 1;
+ }
+ }
+
+ private static class SetAccessibleAction implements PrivilegedAction<Object> {
+ private final AccessibleObject accessible;
+
+ SetAccessibleAction(AccessibleObject accessible) {
+ this.accessible = accessible;
+ }
+
+ public Object run() {
+ accessible.setAccessible(true);
+ return null;
+ }
+ }
+
+ private static CoercingComparator<?>[] coercers = {new ClassCoercer(), new BooleanCoercer(), new LongCoercer(), new IntegerCoercer(), new VersionCoercer(), new StringCoercer()};
+
+ private static final Class<?>[] constructorType = new Class<?>[] {String.class};
+
+ /**
+ * Finds the comparator for <code>a</code> and <code>b</code> and delegates the coercion/comparison to the comparator
+ * according to priority.
+ * @param o1 the first object to be compared.
+ * @param o2 the second object to be compared.
+ * @return The result of the comparison
+ * @throws IllegalArgumentException if no comparator was found or if coercion was impossible
+ * @see Comparator#compare(Object, Object)
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public static <TA extends Object, TB extends Object> int coerceAndCompare(TA o1, TB o2) throws IllegalArgumentException {
+ if (o1 == null || o2 == null)
+ throw new IllegalArgumentException("Cannot compare null to anything"); //$NON-NLS-1$
+
+ if (o1 instanceof Comparable && o1.getClass().isAssignableFrom(o2.getClass()))
+ return ((Comparable) o1).compareTo(o2);
+
+ if (o2 instanceof Comparable && o2.getClass().isAssignableFrom(o1.getClass()))
+ return -((Comparable) o2).compareTo(o1);
+
+ CoercingComparator<TA> ca = getComparator(o1, o2);
+ CoercingComparator<TB> cb = getComparator(o2, o1);
+ return ca.getCoercePrio() <= cb.getCoercePrio() ? ca.compare(o1, ca.coerce(o2)) : cb.compare(cb.coerce(o1), o2);
+ }
+
+ /**
+ * Finds the comparator for <code>a</code> and <code>b</code> and delegates the coercion/equal to the comparator
+ * according to priority.
+ * @param o1 the first object to be compared.
+ * @param o2 the second object to be compared.
+ * @return The result of the equality test
+ * @throws IllegalArgumentException if no comparator was found or if coercion was impossible
+ * @see Object#equals(Object)
+ */
+ public static <TA extends Object, TB extends Object> boolean coerceAndEquals(TA o1, TB o2) throws IllegalArgumentException {
+ if (o1 == o2)
+ return true;
+
+ if (o1 == null || o2 == null)
+ return false;
+
+ if (o1.getClass() != o2.getClass()) {
+ if (o1.getClass().isAssignableFrom(o2.getClass()))
+ return o1.equals(o2);
+ if (o2.getClass().isAssignableFrom(o1.getClass()))
+ return o2.equals(o1);
+ try {
+ CoercingComparator<TA> ca = getComparator(o1, o2);
+ CoercingComparator<TB> cb = getComparator(o2, o1);
+ return ca.getCoercePrio() <= cb.getCoercePrio() ? o1.equals(ca.coerce(o2)) : o2.equals(cb.coerce(o1));
+ } catch (IllegalArgumentException e) {
+ //
+ }
+ }
+ return o1.equals(o2);
+ }
+
+ /**
+ * Obtains the coercing comparator for the given <code>value</code>.
+ * @param value The value
+ * @return The coercing comparator
+ */
+ @SuppressWarnings("unchecked")
+ public static <V extends Object> CoercingComparator<V> getComparator(V value, Object v2) {
+ Class<V> vClass = (Class<V>) value.getClass();
+ CoercingComparator<?>[] carr = coercers;
+ int idx = carr.length;
+ while (--idx >= 0) {
+ CoercingComparator<?> c = carr[idx];
+ if (c.canCoerceTo(vClass)) {
+ CoercingComparator<V> cv = (CoercingComparator<V>) c;
+ return cv;
+ }
+ }
+
+ if (value instanceof Comparable<?> && v2 instanceof String) {
+ Class<Comparable<Object>> cClass = (Class<Comparable<Object>>) vClass;
+ Constructor<Comparable<Object>> constructor;
+ try {
+ constructor = cClass.getConstructor(constructorType);
+ if (!constructor.isAccessible())
+ AccessController.doPrivileged(new SetAccessibleAction(constructor));
+ synchronized (CoercingComparator.class) {
+ int top = coercers.length;
+ CoercingComparator<?>[] nc = new CoercingComparator<?>[top + 1];
+ System.arraycopy(coercers, 0, nc, 1, top);
+ CoercingComparator<V> cv = (CoercingComparator<V>) new FromStringCoercer<Comparable<Object>>(cClass, constructor);
+ nc[0] = cv;
+ coercers = nc;
+ return cv;
+ }
+ } catch (Exception e) {
+ //
+ }
+ }
+ throw new IllegalArgumentException("No comparator for " + vClass.getName()); //$NON-NLS-1$
+ }
+
+ protected IllegalArgumentException uncoercable(Object v) {
+ StringBuffer sb = new StringBuffer("Cannot coerce "); //$NON-NLS-1$
+ if (v instanceof String) {
+ sb.append('\'');
+ sb.append(v);
+ sb.append('\'');
+ } else if (v instanceof Number) {
+ sb.append("number "); //$NON-NLS-1$
+ sb.append(v);
+ } else {
+ sb.append("an object of instance "); //$NON-NLS-1$
+ sb.append(v.getClass().getName());
+ }
+ sb.append(" into a "); //$NON-NLS-1$
+ sb.append(getCoerceClass().getName());
+ return new IllegalArgumentException(sb.toString());
+ }
+
+ boolean canCoerceTo(Class<?> cls) {
+ return cls == getCoerceClass();
+ }
+
+ abstract T coerce(Object v);
+
+ abstract int compare(T o1, T o2);
+
+ abstract Class<T> getCoerceClass();
+
+ abstract int getCoercePrio();
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CollectionFilter.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CollectionFilter.java
new file mode 100644
index 000000000..bb845d373
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CollectionFilter.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import java.util.Iterator;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * Some kind of operation that is performed for each element of a collection. I.e.
+ * <code>x.&lt;operation&gt;(y | &lt;expression&rt;)</code>
+ */
+public abstract class CollectionFilter extends Unary {
+ public static void appendProlog(StringBuffer bld, Variable rootVariable, Expression lhs, String operator) {
+ if (lhs != rootVariable) {
+ appendOperand(bld, rootVariable, lhs, PRIORITY_COLLECTION);
+ bld.append('.');
+ }
+ bld.append(operator);
+ bld.append('(');
+ }
+
+ public final LambdaExpression lambda;
+
+ protected CollectionFilter(Expression collection, LambdaExpression lambda) {
+ super(collection);
+ this.lambda = lambda;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return super.accept(visitor) && lambda.accept(visitor);
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = lambda.compareTo(((CollectionFilter) e).lambda);
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && lambda.equals(((CollectionFilter) o).lambda);
+ }
+
+ public final Object evaluate(IEvaluationContext context) {
+ Iterator<?> lval = operand.evaluateAsIterator(context);
+ context = lambda.prolog(context);
+ return evaluate(context, lval);
+ }
+
+ public final Iterator<?> evaluateAsIterator(IEvaluationContext context) {
+ Iterator<?> lval = operand.evaluateAsIterator(context);
+ context = lambda.prolog(context);
+ return evaluateAsIterator(context, lval);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ appendProlog(bld, rootVariable, operand, getOperator());
+ appendOperand(bld, rootVariable, lambda, PRIORITY_LAMBDA);
+ bld.append(')');
+ }
+
+ public int hashCode() {
+ int result = 31 + operand.hashCode();
+ return 31 * result + lambda.hashCode();
+ }
+
+ public int getPriority() {
+ return PRIORITY_COLLECTION;
+ }
+
+ protected abstract Object evaluate(final IEvaluationContext context, Iterator<?> iterator);
+
+ protected Iterator<?> evaluateAsIterator(IEvaluationContext context, Iterator<?> iterator) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Compare.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Compare.java
new file mode 100644
index 000000000..7e958e41d
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Compare.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * Comparisons for magnitude.
+ */
+final class Compare extends Binary {
+ static IllegalArgumentException uncomparable(Object lval, Object rval) {
+ return new IllegalArgumentException("Cannot compare a " + lval.getClass().getName() + " to a " + rval.getClass().getName()); //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ final boolean compareLess;
+
+ final boolean equalOK;
+
+ Compare(Expression lhs, Expression rhs, boolean compareLess, boolean equalOK) {
+ super(lhs, rhs);
+ this.compareLess = compareLess;
+ this.equalOK = equalOK;
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ int cmpResult = CoercingComparator.coerceAndCompare(lhs.evaluate(context), rhs.evaluate(context));
+ return Boolean.valueOf(cmpResult == 0 ? equalOK : (cmpResult < 0 ? compareLess : !compareLess));
+ }
+
+ public int getExpressionType() {
+ return compareLess ? (equalOK ? TYPE_LESS_EQUAL : TYPE_LESS) : (equalOK ? TYPE_GREATER_EQUAL : TYPE_GREATER);
+ }
+
+ public String getOperator() {
+ return compareLess ? (equalOK ? OPERATOR_LT_EQUAL : OPERATOR_LT) : (equalOK ? OPERATOR_GT_EQUAL : OPERATOR_GT);
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ if (!equalOK)
+ buf.append("(!"); //$NON-NLS-1$
+ buf.append('(');
+ appendLDAPAttribute(buf);
+ if (equalOK)
+ buf.append(compareLess ? OPERATOR_LT_EQUAL : OPERATOR_GT_EQUAL);
+ else
+ buf.append(compareLess ? OPERATOR_GT_EQUAL : OPERATOR_LT_EQUAL);
+ appendLDAPValue(buf);
+ buf.append(')');
+ if (!equalOK)
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Equals.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Equals.java
new file mode 100644
index 000000000..7452c3165
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Equals.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * An expression that performs the == and != comparisons
+ */
+final class Equals extends Binary {
+ final boolean negate;
+
+ Equals(Expression lhs, Expression rhs, boolean negate) {
+ super(lhs, rhs);
+ this.negate = negate;
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ boolean result = CoercingComparator.coerceAndEquals(lhs.evaluate(context), rhs.evaluate(context));
+ if (negate)
+ result = !result;
+ return Boolean.valueOf(result);
+ }
+
+ public int getExpressionType() {
+ return negate ? TYPE_NOT_EQUALS : TYPE_EQUALS;
+ }
+
+ public String getOperator() {
+ return negate ? OPERATOR_NOT_EQUALS : OPERATOR_EQUALS;
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ if (negate)
+ buf.append("(!"); //$NON-NLS-1$
+ buf.append('(');
+ appendLDAPAttribute(buf);
+ buf.append('=');
+ appendLDAPValue(buf);
+ buf.append(')');
+ if (negate)
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/EvaluationContext.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/EvaluationContext.java
new file mode 100644
index 000000000..8fb3665bc
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/EvaluationContext.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.IExpression;
+
+/**
+ * Highly specialized evaluation contexts optimized for misc purposes
+ */
+public class EvaluationContext implements IEvaluationContext {
+ public static class Parameters extends EvaluationContext {
+ private static final Object[] noParameters = new Object[0];
+
+ private final Object[] parameters;
+
+ public Parameters(IEvaluationContext parentContext, Object[] parameters) {
+ super(parentContext);
+ this.parameters = parameters == null ? noParameters : parameters;
+ }
+
+ public Object getParameter(int position) {
+ return position >= 0 && position < parameters.length ? parameters[position] : super.getParameter(position);
+ }
+ }
+
+ public static class SingleVariableContext implements IEvaluationContext {
+ private final IEvaluationContext parentContext;
+
+ private Object value;
+
+ private final IExpression variable;
+
+ public SingleVariableContext(IEvaluationContext parentContext, IExpression variable) {
+ this.parentContext = parentContext;
+ this.variable = variable;
+ }
+
+ public Object getParameter(int position) {
+ return parentContext.getParameter(position);
+ }
+
+ public Object getValue(IExpression var) {
+ return variable == var ? value : parentContext.getValue(var);
+ }
+
+ public void setValue(IExpression var, Object val) {
+ if (variable == var)
+ value = val;
+ else
+ parentContext.setValue(var, val);
+ }
+ }
+
+ static class MultiVariableContext implements IEvaluationContext {
+ private final IEvaluationContext parentContext;
+
+ private final Object[] values;
+
+ public MultiVariableContext(IEvaluationContext parentContext, IExpression[] variables) {
+ this.parentContext = parentContext;
+ values = new Object[variables.length * 2];
+ for (int idx = 0, ndx = 0; ndx < variables.length; ++ndx, idx += 2)
+ values[idx] = variables[ndx];
+ }
+
+ public Object getParameter(int position) {
+ return parentContext.getParameter(position);
+ }
+
+ public Object getValue(IExpression variable) {
+ for (int idx = 0; idx < values.length; ++idx)
+ if (values[idx++] == variable)
+ return values[idx];
+ return parentContext.getValue(variable);
+ }
+
+ public void setValue(IExpression variable, Object value) {
+ for (int idx = 0; idx < values.length; ++idx)
+ if (values[idx++] == variable) {
+ values[idx] = value;
+ return;
+ }
+ parentContext.setValue(variable, value);
+ }
+ }
+
+ public static final EvaluationContext INSTANCE = new EvaluationContext(null);
+
+ public static IEvaluationContext create() {
+ return INSTANCE;
+ }
+
+ public static IEvaluationContext create(IEvaluationContext parent, IExpression variable) {
+ return new SingleVariableContext(parent, variable);
+ }
+
+ public static IEvaluationContext create(IEvaluationContext parent, IExpression[] variables) {
+ return variables.length == 1 ? new SingleVariableContext(parent, variables[0]) : new MultiVariableContext(parent, variables);
+ }
+
+ public static IEvaluationContext create(IEvaluationContext parent, Object[] parameters) {
+ return new Parameters(parent, parameters);
+ }
+
+ public static IEvaluationContext create(IExpression variable) {
+ return new SingleVariableContext(null, variable);
+ }
+
+ public static IEvaluationContext create(IExpression[] variables) {
+ if (variables == null || variables.length == 0)
+ return INSTANCE;
+ return variables.length == 1 ? create(variables[0]) : new MultiVariableContext(INSTANCE, variables);
+ }
+
+ public static IEvaluationContext create(Object[] parameters, IExpression variable) {
+ return parameters == null || parameters.length == 0 ? create(variable) : new SingleVariableContext(new Parameters(null, parameters), variable);
+ }
+
+ public static IEvaluationContext create(Object[] parameters, IExpression[] variables) {
+ if (parameters == null || parameters.length == 0)
+ return create(variables);
+
+ Parameters pctx = new Parameters(null, parameters);
+ if (variables == null || variables.length == 0)
+ return pctx;
+ return create(pctx, variables);
+ }
+
+ protected EvaluationContext(IEvaluationContext parentContext) {
+ this.parentContext = parentContext;
+ }
+
+ private final IEvaluationContext parentContext;
+
+ public Object getParameter(int position) {
+ if (parentContext == null)
+ throw new IllegalArgumentException("No such parameter: $" + position); //$NON-NLS-1$
+ return parentContext.getParameter(position);
+ }
+
+ public Object getValue(IExpression variable) {
+ if (parentContext == null)
+ throw new IllegalArgumentException("No such variable: " + variable); //$NON-NLS-1$
+ return parentContext.getValue(variable);
+ }
+
+ public void setValue(IExpression variable, Object value) {
+ if (parentContext == null)
+ throw new IllegalArgumentException("No such variable: " + variable); //$NON-NLS-1$
+ parentContext.setValue(variable, value);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Exists.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Exists.java
new file mode 100644
index 000000000..14fbc5df4
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Exists.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import java.util.Iterator;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * A collection filter that yields true if the <code>filter</code> yields true for
+ * any of the elements of the <code>collection</code>
+ */
+final class Exists extends CollectionFilter {
+ Exists(Expression collection, LambdaExpression lambda) {
+ super(collection, lambda);
+ }
+
+ protected Object evaluate(IEvaluationContext context, Iterator<?> itor) {
+ Variable variable = lambda.getItemVariable();
+ while (itor.hasNext()) {
+ variable.setValue(context, itor.next());
+ if (lambda.evaluate(context) == Boolean.TRUE)
+ return Boolean.TRUE;
+ }
+ return Boolean.FALSE;
+ }
+
+ public int getExpressionType() {
+ return TYPE_EXISTS;
+ }
+
+ public String getOperator() {
+ return KEYWORD_EXISTS;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Expression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Expression.java
new file mode 100644
index 000000000..75f47dd94
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Expression.java
@@ -0,0 +1,341 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import java.util.*;
+import org.eclipse.equinox.p2.metadata.expression.*;
+
+/**
+ * The base class of the expression tree.
+ */
+public abstract class Expression implements IExpression, Comparable<Expression>, IExpressionConstants {
+
+ static final Expression[] emptyArray = new Expression[0];
+
+ public static void appendOperand(StringBuffer bld, Variable rootVariable, Expression operand, int priority) {
+ if (priority < operand.getPriority()) {
+ bld.append('(');
+ operand.toString(bld, rootVariable);
+ bld.append(')');
+ } else
+ operand.toString(bld, rootVariable);
+ }
+
+ public static Expression[] assertLength(Expression[] operands, int minLength, int maxLength, String operand) {
+ if (operands == null)
+ operands = emptyArray;
+ if (operands.length < minLength)
+ throw new IllegalArgumentException("Not enough operands for " + operand); //$NON-NLS-1$
+ if (operands.length > maxLength)
+ throw new IllegalArgumentException("Too many operands for " + operand); //$NON-NLS-1$
+ return operands;
+ }
+
+ public static Expression[] assertLength(Expression[] operands, int length, String operand) {
+ if (operands == null)
+ operands = emptyArray;
+ if (operands.length < length)
+ throw new IllegalArgumentException("Not enough operands for " + operand); //$NON-NLS-1$
+ return operands;
+ }
+
+ public static int compare(Expression[] arr1, Expression[] arr2) {
+ int max = arr1.length;
+ if (max > arr2.length)
+ max = arr2.length;
+ for (int idx = 0; idx < max; ++idx) {
+ int cmp = arr1[idx].compareTo(arr2[idx]);
+ if (cmp != 0)
+ return cmp;
+ }
+ if (max == arr2.length) {
+ if (max < arr1.length)
+ return 1;
+ return 0;
+ }
+ return -1;
+ }
+
+ public static boolean equals(Expression[] arr1, Expression[] arr2) {
+ int idx = arr1.length;
+ if (idx != arr2.length)
+ return false;
+ while (--idx >= 0)
+ if (!arr1[idx].equals(arr2[idx]))
+ return false;
+ return true;
+ }
+
+ public static int hashCode(Expression[] arr) {
+ int idx = arr.length;
+ int result = 1;
+ while (--idx >= 0)
+ result = 31 * result + arr[idx].hashCode();
+ return result;
+ }
+
+ public static void elementsToString(StringBuffer bld, Variable rootVariable, Expression[] elements) {
+ int top = elements.length;
+ if (top > 0) {
+ elements[0].toString(bld, rootVariable);
+ for (int idx = 1; idx < top; ++idx) {
+ bld.append(", "); //$NON-NLS-1$
+ appendOperand(bld, rootVariable, elements[idx], PRIORITY_MAX);
+ }
+ }
+ }
+
+ /**
+ * Let the visitor visit this instance and all expressions that this
+ * instance contains.
+ * @param visitor The visiting visitor.
+ * @return <code>true</code> if the visitor should continue visiting, <code>false</code> otherwise.
+ */
+ public boolean accept(IExpressionVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = getPriority() - e.getPriority();
+ if (cmp == 0) {
+ int e1 = getExpressionType();
+ int e2 = e.getExpressionType();
+ cmp = e1 > e2 ? 1 : (e1 == e2 ? 0 : -1);
+ }
+ return cmp;
+ }
+
+ public boolean equals(Object e) {
+ if (e == this)
+ return true;
+ if (e == null || getClass() != e.getClass())
+ return false;
+ return getExpressionType() == ((Expression) e).getExpressionType();
+ }
+
+ /**
+ * Evaluate this expression with given context and variables.
+ * @param context The evaluation context
+ * @return The result of the evaluation.
+ */
+ public abstract Object evaluate(IEvaluationContext context);
+
+ public Iterator<?> evaluateAsIterator(IEvaluationContext context) {
+ Object value = evaluate(context);
+ if (!(value instanceof Iterator<?>))
+ value = RepeatableIterator.create(value);
+ return (Iterator<?>) value;
+ }
+
+ public abstract String getOperator();
+
+ public abstract int getPriority();
+
+ public boolean isRootVariable() {
+ return false;
+ }
+
+ public final String toLDAPString() {
+ StringBuffer bld = new StringBuffer();
+ toLDAPString(bld);
+ return bld.toString();
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ throw new UnsupportedOperationException();
+ }
+
+ public final String toString() {
+ StringBuffer bld = new StringBuffer();
+ toString(bld);
+ return bld.toString();
+ }
+
+ public void toString(StringBuffer bld) {
+ toString(bld, ExpressionFactory.THIS);
+ }
+
+ public abstract void toString(StringBuffer bld, Variable rootVariable);
+
+ private static class Compacter {
+ private Expression base;
+
+ private List<Expression> parts;
+
+ private int op;
+
+ Compacter(Expression base, int op) {
+ this.base = base;
+ this.op = op;
+ }
+
+ Expression getResultingFilter() {
+ if (parts == null)
+ return base;
+
+ int partsOp = op == TYPE_AND ? TYPE_OR : TYPE_AND;
+ return addFilter(base, normalize(parts, partsOp), op);
+ }
+
+ boolean merge(Expression b) {
+ Expression[] aArr;
+ Expression[] bArr;
+ if (base.getExpressionType() == op)
+ aArr = getFilterImpls(base);
+ else
+ aArr = new Expression[] {base};
+
+ if (b.getExpressionType() == op)
+ bArr = getFilterImpls(b);
+ else
+ bArr = new Expression[] {b};
+
+ List<Expression> common = null;
+ List<Expression> onlyA = null;
+
+ int atop = aArr.length;
+ int btop = bArr.length;
+ int aidx;
+ int bidx;
+ for (aidx = 0; aidx < atop; ++aidx) {
+ Expression af = aArr[aidx];
+ for (bidx = 0; bidx < btop; ++bidx) {
+ Expression bf = bArr[bidx];
+ if (af.equals(bf)) {
+ if (common == null)
+ common = new ArrayList<Expression>();
+ common.add(af);
+ break;
+ }
+ }
+ if (bidx == btop) {
+ if (onlyA == null)
+ onlyA = new ArrayList<Expression>();
+ onlyA.add(af);
+ }
+ }
+ if (common == null)
+ // Nothing in common
+ return false;
+
+ if (onlyA == null && parts == null)
+ return true;
+
+ List<Expression> onlyB = null;
+ for (bidx = 0; bidx < btop; ++bidx) {
+ Expression bf = bArr[bidx];
+ for (aidx = 0; aidx < atop; ++aidx)
+ if (bf.equals(aArr[aidx]))
+ break;
+ if (aidx == atop) {
+ if (onlyB == null)
+ onlyB = new ArrayList<Expression>();
+ onlyB.add(bf);
+ }
+ }
+
+ if (onlyB == null && parts == null) {
+ // All of B is already covered by base
+ base = b;
+ return true;
+ }
+
+ if (parts == null)
+ parts = new ArrayList<Expression>();
+
+ if (onlyA != null) {
+ base = normalize(common, op);
+ Expression af = normalize(onlyA, op);
+ if (!parts.contains(af))
+ parts.add(af);
+ }
+ Expression bf = normalize(onlyB, op);
+ if (!parts.contains(bf))
+ parts.add(bf);
+ return true;
+ }
+ }
+
+ static Expression addFilter(Expression base, Expression subFilter, int expressionType) {
+ if (base.equals(subFilter))
+ return base;
+
+ ArrayList<Expression> filters = new ArrayList<Expression>(2);
+ filters.add(base);
+ filters.add(subFilter);
+ return normalize(filters, expressionType);
+ }
+
+ static Expression normalize(List<Expression> operands, int op) {
+ int top = operands.size();
+ if (top == 1)
+ return operands.get(0);
+
+ // a | (b | c) becomes a | b | c
+ // a & (b & c) becomes a & b & c
+ //
+ for (int idx = 0; idx < top; ++idx) {
+ Expression f = operands.get(idx);
+ if (f.getExpressionType() != op)
+ continue;
+
+ Expression[] sfs = getFilterImpls(f);
+ operands.remove(idx);
+ --top;
+ for (int ndx = 0; ndx < sfs.length; ++ndx) {
+ Expression nf = sfs[ndx];
+ if (!operands.contains(nf))
+ operands.add(nf);
+ }
+ }
+ top = operands.size();
+ if (top == 1)
+ return operands.get(0);
+
+ Collections.sort(operands);
+ List<Compacter> splits = new ArrayList<Compacter>();
+ int reverseOp = op == TYPE_AND ? TYPE_OR : TYPE_AND;
+
+ for (int idx = 0; idx < top; ++idx)
+ merge(splits, operands.get(idx), reverseOp);
+
+ operands.clear();
+ top = splits.size();
+ for (int idx = 0; idx < top; ++idx) {
+ Expression filter = splits.get(idx).getResultingFilter();
+ if (!operands.contains(filter))
+ operands.add(filter);
+ }
+ top = operands.size();
+ if (top == 1)
+ return operands.get(0);
+
+ Collections.sort(operands);
+ Expression[] expArray = operands.toArray(new Expression[top]);
+ return op == TYPE_AND ? new And(expArray) : new Or(expArray);
+ }
+
+ static void merge(List<Compacter> splits, Expression base, int op) {
+ int top = splits.size();
+ for (int idx = 0; idx < top; ++idx) {
+ Compacter split = splits.get(idx);
+ if (split.merge(base))
+ return;
+ }
+ splits.add(new Compacter(base, op));
+ }
+
+ static Expression[] getFilterImpls(Expression expression) {
+ if (expression instanceof NAry)
+ return ((NAry) expression).operands;
+ throw new IllegalArgumentException();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/ExpressionFactory.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/ExpressionFactory.java
new file mode 100644
index 000000000..a9e70e311
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/ExpressionFactory.java
@@ -0,0 +1,125 @@
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.List;
+import org.eclipse.equinox.p2.metadata.expression.*;
+
+public class ExpressionFactory implements IExpressionFactory, IExpressionConstants {
+ public static final IExpressionFactory INSTANCE = new ExpressionFactory();
+ public static final Variable THIS = new Variable(VARIABLE_THIS);
+
+ protected static Expression[] convertArray(IExpression[] operands) {
+ Expression[] ops = new Expression[operands.length];
+ System.arraycopy(operands, 0, ops, 0, operands.length);
+ return ops;
+ }
+
+ protected ExpressionFactory() {
+ // Maintain singleton
+ }
+
+ public IExpression all(IExpression collection, IExpression lambda) {
+ return new All((Expression) collection, (LambdaExpression) lambda);
+ }
+
+ public IExpression and(IExpression... operands) {
+ return new And(convertArray(operands));
+ }
+
+ public IExpression at(IExpression target, IExpression key) {
+ return new At((Expression) target, (Expression) key);
+ }
+
+ @SuppressWarnings("unchecked")
+ public IExpression normalize(List<? extends IExpression> operands, int expressionType) {
+ return Expression.normalize((List<Expression>) operands, expressionType);
+ }
+
+ public IExpression constant(Object value) {
+ return Literal.create(value);
+ }
+
+ public IEvaluationContext createContext(Object... parameters) {
+ return EvaluationContext.create(parameters, (Variable[]) null);
+ }
+
+ public IEvaluationContext createContext(IExpression[] variables, Object... parameters) {
+ return EvaluationContext.create(parameters, variables);
+ }
+
+ public IFilterExpression filterExpression(IExpression expression) {
+ return new LDAPFilter((Expression) expression);
+ }
+
+ public IExpression equals(IExpression lhs, IExpression rhs) {
+ return new Equals((Expression) lhs, (Expression) rhs, false);
+ }
+
+ public IExpression exists(IExpression collection, IExpression lambda) {
+ return new Exists((Expression) collection, (LambdaExpression) lambda);
+ }
+
+ public IExpression greater(IExpression lhs, IExpression rhs) {
+ return new Compare((Expression) lhs, (Expression) rhs, false, false);
+ }
+
+ public IExpression greaterEqual(IExpression lhs, IExpression rhs) {
+ return new Compare((Expression) lhs, (Expression) rhs, false, true);
+ }
+
+ public IExpression indexedParameter(int index) {
+ return new Parameter(index);
+ }
+
+ public IExpression lambda(IExpression variable, IExpression body) {
+ return new LambdaExpression((Variable) variable, (Expression) body);
+ }
+
+ public IExpression less(IExpression lhs, IExpression rhs) {
+ return new Compare((Expression) lhs, (Expression) rhs, true, false);
+ }
+
+ public IExpression lessEqual(IExpression lhs, IExpression rhs) {
+ return new Compare((Expression) lhs, (Expression) rhs, true, true);
+ }
+
+ public IExpression matches(IExpression lhs, IExpression rhs) {
+ return new Matches((Expression) lhs, (Expression) rhs);
+ }
+
+ public <T> IMatchExpression<T> matchExpression(IExpression expression, Object... parameters) {
+ return new MatchExpression<T>((Expression) expression, parameters);
+ }
+
+ public IExpression member(IExpression target, String name) {
+ return new Member.DynamicMember((Expression) target, name);
+ }
+
+ public IExpression not(IExpression operand) {
+ if (operand instanceof Equals) {
+ Equals eq = (Equals) operand;
+ return new Equals(eq.lhs, eq.rhs, !eq.negate);
+ }
+ if (operand instanceof Compare) {
+ Compare cmp = (Compare) operand;
+ return new Compare(cmp.lhs, cmp.rhs, !cmp.compareLess, !cmp.equalOK);
+ }
+ if (operand instanceof Not)
+ return ((Not) operand).operand;
+
+ return new Not((Expression) operand);
+ }
+
+ public IExpression or(IExpression... operands) {
+ return new Or(convertArray(operands));
+ }
+
+ public IExpression thisVariable() {
+ return THIS;
+ }
+
+ public IExpression variable(String name) {
+ if (VARIABLE_THIS.equals(name))
+ return THIS;
+ return new Variable(name);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IExpressionConstants.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IExpressionConstants.java
new file mode 100644
index 000000000..3da1dbfac
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IExpressionConstants.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+public interface IExpressionConstants {
+ String KEYWORD_ALL = "all"; //$NON-NLS-1$
+
+ String KEYWORD_BOOLEAN = "boolean"; //$NON-NLS-1$
+ String KEYWORD_CLASS = "class"; //$NON-NLS-1$
+ String KEYWORD_EXISTS = "exists"; //$NON-NLS-1$
+ String KEYWORD_FALSE = "false"; //$NON-NLS-1$
+ String KEYWORD_FILTER = "filter"; //$NON-NLS-1$
+ String KEYWORD_NULL = "null"; //$NON-NLS-1$
+ String KEYWORD_RANGE = "range"; //$NON-NLS-1$
+ String KEYWORD_TRUE = "true"; //$NON-NLS-1$
+
+ String KEYWORD_VERSION = "version"; //$NON-NLS-1$
+ String OPERATOR_AND = "&&"; //$NON-NLS-1$
+ String OPERATOR_AT = "[]"; //$NON-NLS-1$
+ String OPERATOR_EQUALS = "=="; //$NON-NLS-1$
+ String OPERATOR_GT = ">"; //$NON-NLS-1$
+ String OPERATOR_GT_EQUAL = ">="; //$NON-NLS-1$
+ String OPERATOR_LT = "<"; //$NON-NLS-1$
+ String OPERATOR_LT_EQUAL = "<="; //$NON-NLS-1$
+ String OPERATOR_MATCHES = "~="; //$NON-NLS-1$
+ String OPERATOR_MEMBER = "."; //$NON-NLS-1$
+ String OPERATOR_NOT = "!"; //$NON-NLS-1$
+ String OPERATOR_NOT_EQUALS = "!="; //$NON-NLS-1$
+
+ String OPERATOR_OR = "||"; //$NON-NLS-1$
+ String OPERATOR_PARAMETER = "$"; //$NON-NLS-1$
+
+ int PRIORITY_LITERAL = 1;
+ int PRIORITY_VARIABLE = 1;
+ int PRIORITY_FUNCTION = 2; // for extend query expressions
+ int PRIORITY_MEMBER = 3;
+ int PRIORITY_COLLECTION = 4;
+ int PRIORITY_NOT = 5;
+ int PRIORITY_BINARY = 6;
+ int PRIORITY_AND = 7;
+ int PRIORITY_OR = 8;
+ int PRIORITY_CONDITION = 9;
+ int PRIORITY_ASSIGNMENT = 10;
+ int PRIORITY_LAMBDA = 11;
+ int PRIORITY_COMMA = 12;
+ int PRIORITY_MAX = 20;
+
+ String VARIABLE_EVERYTHING = "everything"; //$NON-NLS-1$
+ String VARIABLE_THIS = "this"; //$NON-NLS-1$
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IRepeatableIterator.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IRepeatableIterator.java
new file mode 100644
index 000000000..f20e266bf
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IRepeatableIterator.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import java.util.Iterator;
+
+public interface IRepeatableIterator<T> extends Iterator<T> {
+ /**
+ * Returns a copy that will iterate over the same elements
+ * as this iterator. The contents or position of this iterator
+ * is left unchanged.
+ * @return A re-initialized copy of this iterator.
+ */
+ IRepeatableIterator<T> getCopy();
+
+ Object getIteratorProvider();
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPApproximation.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPApproximation.java
new file mode 100644
index 000000000..291a6ef6c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPApproximation.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * 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;
+
+import java.io.Serializable;
+
+/**
+ * Map a string for an LDAP APPROX (~=) comparison.
+ * This implementation removes white spaces and transforms everything to lower case.
+ */
+public final class LDAPApproximation implements Serializable, Comparable<LDAPApproximation> {
+ private static final long serialVersionUID = 4782295637798543587L;
+ private final String pattern;
+ private transient String approxPattern;
+
+ public LDAPApproximation(String pattern) {
+ this.pattern = pattern;
+ }
+
+ public int compareTo(LDAPApproximation o) {
+ return pattern.compareTo(o.pattern);
+ }
+
+ public boolean equals(Object o) {
+ return o == this || (o instanceof LDAPApproximation && ((LDAPApproximation) o).pattern.equals(pattern));
+ }
+
+ public int hashCode() {
+ return 3 * pattern.hashCode();
+ }
+
+ /**
+ * Matches the <code>value</code> with the compiled expression. The value
+ * is considered matching if all characters are matched by the expression. A
+ * partial match is not enough.
+ * @param value The value to match
+ * @return <code>true</code> if the value was a match.
+ */
+ public boolean isMatch(CharSequence value) {
+ if (value == null)
+ return false;
+ if (approxPattern == null)
+ approxPattern = approxString(pattern);
+ return approxString(value).equals(approxPattern);
+ }
+
+ public String toString() {
+ return pattern;
+ }
+
+ private static String approxString(CharSequence input) {
+ boolean changed = false;
+ char[] output = new char[input.length()];
+ int cursor = 0;
+ for (int i = 0, length = output.length; i < length; i++) {
+ char c = input.charAt(i);
+ if (Character.isWhitespace(c)) {
+ changed = true;
+ continue;
+ }
+ if (Character.isUpperCase(c)) {
+ changed = true;
+ c = Character.toLowerCase(c);
+ }
+ output[cursor++] = c;
+ }
+ return changed ? new String(output, 0, cursor) : input.toString();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPFilter.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPFilter.java
new file mode 100644
index 000000000..607334bea
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPFilter.java
@@ -0,0 +1,69 @@
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.Dictionary;
+import java.util.Map;
+import org.eclipse.equinox.p2.metadata.expression.*;
+import org.osgi.framework.Filter;
+import org.osgi.framework.ServiceReference;
+
+public class LDAPFilter extends Unary implements IFilterExpression {
+
+ LDAPFilter(Expression expression) {
+ super(expression);
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return operand.accept(visitor);
+ }
+
+ public boolean equals(Object o) {
+ return (o instanceof Filter && !(o instanceof LDAPFilter)) ? equals(ExpressionUtil.parseLDAP(o.toString())) : super.equals(o);
+ }
+
+ @Override
+ public String getOperator() {
+ return operand.getOperator();
+ }
+
+ @Override
+ public int getPriority() {
+ return operand.getPriority();
+ }
+
+ public int getExpressionType() {
+ return 0;
+ }
+
+ public boolean match(Map<String, ? extends Object> map) {
+ return isMatch(MemberProvider.create(map, true));
+ }
+
+ @SuppressWarnings("rawtypes")
+ public boolean match(Dictionary dictionary) {
+ return isMatch(dictionary == null ? MemberProvider.emptyProvider() : MemberProvider.create(dictionary, true));
+ }
+
+ private boolean isMatch(Object candidate) {
+ Variable self = ExpressionFactory.THIS;
+ IEvaluationContext ctx = EvaluationContext.create(self);
+ self.setValue(ctx, candidate);
+ return Boolean.TRUE == operand.evaluate(ctx);
+ }
+
+ public boolean match(ServiceReference reference) {
+ return isMatch(reference == null ? MemberProvider.emptyProvider() : MemberProvider.create(reference, true));
+ }
+
+ public boolean matchCase(Map<String, ? extends Object> map) {
+ return isMatch(map == null ? MemberProvider.emptyProvider() : MemberProvider.create(map, false));
+ }
+
+ @SuppressWarnings("rawtypes")
+ public boolean matchCase(Dictionary dictionary) {
+ return isMatch(dictionary == null ? MemberProvider.emptyProvider() : MemberProvider.create(dictionary, false));
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ operand.toLDAPString(bld);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LambdaExpression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LambdaExpression.java
new file mode 100644
index 000000000..c5bacf0ab
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LambdaExpression.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * A function that executes some code
+ */
+public class LambdaExpression extends Unary {
+ protected final Variable each;
+
+ protected LambdaExpression(Variable each, Expression body) {
+ super(body);
+ this.each = each;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return super.accept(visitor) && each.accept(visitor);
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = each.compareTo(((LambdaExpression) e).each);
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && each.equals(((LambdaExpression) o).each);
+ }
+
+ public int hashCode() {
+ int result = 31 + operand.hashCode();
+ return 31 * result + each.hashCode();
+ }
+
+ public int getExpressionType() {
+ return TYPE_LAMBDA;
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ each.toString(bld, rootVariable);
+ bld.append(" | "); //$NON-NLS-1$
+ appendOperand(bld, rootVariable, operand, IExpressionConstants.PRIORITY_COMMA);
+ }
+
+ public Variable getItemVariable() {
+ return each;
+ }
+
+ public String getOperator() {
+ return "|"; //$NON-NLS-1$
+ }
+
+ public int getPriority() {
+ return IExpressionConstants.PRIORITY_LAMBDA;
+ }
+
+ public IEvaluationContext prolog(IEvaluationContext context) {
+ return EvaluationContext.create(context, each);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Literal.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Literal.java
new file mode 100644
index 000000000..2884fb757
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Literal.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.Version;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.SimplePattern;
+import org.osgi.framework.Filter;
+
+/**
+ * An expression that represents a constant value.
+ */
+public final class Literal extends Expression {
+ public static final Literal FALSE_CONSTANT = new Literal(Boolean.FALSE);
+
+ public static final Literal NULL_CONSTANT = new Literal(null);
+
+ public static final Literal TRUE_CONSTANT = new Literal(Boolean.TRUE);
+
+ static Literal create(Object value) {
+ if (value == null)
+ return NULL_CONSTANT;
+ if (value == Boolean.TRUE)
+ return TRUE_CONSTANT;
+ if (value == Boolean.FALSE)
+ return FALSE_CONSTANT;
+ return new Literal(value);
+ }
+
+ public final Object value;
+
+ private Literal(Object value) {
+ this.value = value;
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp != 0)
+ return cmp;
+
+ Object eValue = ((Literal) e).value;
+ if (value == null)
+ return eValue == null ? 0 : -1;
+
+ if (eValue == null)
+ return 1;
+
+ if (eValue.getClass() == value.getClass())
+ return ((Comparable) value).compareTo(eValue);
+
+ return eValue.getClass().getName().compareTo(value.getClass().getName());
+ }
+
+ public boolean equals(Object o) {
+ if (super.equals(o)) {
+ Literal bo = (Literal) o;
+ return value == null ? bo.value == null : value.equals(bo.value);
+ }
+ return false;
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return value;
+ }
+
+ public int getExpressionType() {
+ return TYPE_LITERAL;
+ }
+
+ public String getOperator() {
+ return "<literal>"; //$NON-NLS-1$
+ }
+
+ public int getPriority() {
+ return PRIORITY_LITERAL;
+ }
+
+ public int hashCode() {
+ return 31 + value.hashCode();
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ if (!(value instanceof Filter))
+ throw new UnsupportedOperationException();
+ buf.append(value.toString());
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ if (value == null)
+ bld.append("null"); //$NON-NLS-1$
+ else if (value instanceof String || value instanceof Version) {
+ String str = value.toString();
+ char sep = str.indexOf('\'') >= 0 ? '"' : '\'';
+ bld.append(sep);
+ bld.append(str);
+ bld.append(sep);
+ } else if (value instanceof SimplePattern) {
+ appendEscaped(bld, '/', value.toString());
+ } else
+ bld.append(value);
+ }
+
+ private void appendEscaped(StringBuffer bld, char delimiter, String str) {
+ bld.append(delimiter);
+ int top = str.length();
+ for (int idx = 0; idx < top; ++idx) {
+ char c = str.charAt(idx);
+ if (c == delimiter)
+ bld.append('\\');
+ bld.append(c);
+ }
+ bld.append(delimiter);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MatchExpression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MatchExpression.java
new file mode 100644
index 000000000..efb636bb9
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MatchExpression.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import java.util.Arrays;
+import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils;
+import org.eclipse.equinox.p2.metadata.expression.*;
+
+/**
+ * The MatchExpression is a wrapper for an {@link IExpression} that is expected
+ * to return a boolean value. The wrapper provides the evaluation context needed
+ * to evaluate the expression.
+ */
+class MatchExpression<T> extends Unary implements IMatchExpression<T> {
+ private static final Object[] noParams = new Object[0];
+ private final Object[] parameters;
+
+ MatchExpression(Expression expression, Object[] parameters) {
+ super(expression);
+ this.parameters = parameters == null ? noParams : parameters;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return operand.accept(visitor);
+ }
+
+ public IEvaluationContext createContext() {
+ return EvaluationContext.create(parameters, ExpressionFactory.THIS);
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && Arrays.equals(parameters, ((MatchExpression<?>) o).parameters);
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return operand.evaluate(parameters.length == 0 ? context : EvaluationContext.create(context, parameters));
+ }
+
+ public int getExpressionType() {
+ return 0;
+ }
+
+ public String getOperator() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object[] getParameters() {
+ return parameters;
+ }
+
+ public int getPriority() {
+ return operand.getPriority();
+ }
+
+ public int hashCode() {
+ return operand.hashCode() * 31 + CollectionUtils.hashCode(parameters);
+ }
+
+ public boolean isMatch(IEvaluationContext context, T value) {
+ ExpressionFactory.THIS.setValue(context, value);
+ return Boolean.TRUE == operand.evaluate(context);
+ }
+
+ public boolean isMatch(T value) {
+ return isMatch(createContext(), value);
+ }
+
+ public void toLDAPString(StringBuffer bld) {
+ operand.toLDAPString(bld);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ operand.toString(bld, rootVariable);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Matches.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Matches.java
new file mode 100644
index 000000000..da041b398
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Matches.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * 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;
+
+import org.eclipse.equinox.p2.metadata.Version;
+import org.eclipse.equinox.p2.metadata.VersionRange;
+import org.eclipse.equinox.p2.metadata.expression.*;
+
+/**
+ * <p>A class that performs &quot;matching&quot; The actual algorithm used for
+ * performing the match varies depending on the types of the items to match.</p>
+ * <p>The following things can be matched:</p>
+ * <table border="1" cellpadding="3">
+ * <tr><th>LHS</th><th>RHS</th><th>Implemented as</th></tr>
+ * <tr><td>{@link String}</td><td>{@link SimplePattern}</td><td>rhs.isMatch(lhs)</td></tr>
+ * <tr><td>{@link String}</td><td>{@link LDAPApproximation}</td><td>rhs.isMatch(lhs)</td></tr>
+ * <tr><td>&lt;any&gt;</td><td>{@link Class}</td><td>rhs.isInstance(lhs)</td></tr>
+ * <tr><td>{@link Class}</td><td>{@link Class}</td><td>rhs.isAssignableFrom(lhs)</td></tr>
+ * </table>
+ */
+public class Matches extends Binary {
+ protected Matches(Expression lhs, Expression rhs) {
+ super(lhs, rhs);
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return Boolean.valueOf(match(lhs.evaluate(context), rhs.evaluate(context)));
+ }
+
+ protected boolean match(Object lval, Object rval) {
+ if (rval instanceof VersionRange) {
+ VersionRange range = (VersionRange) rval;
+ if (lval instanceof Version)
+ return range.isIncluded((Version) lval);
+ if (lval instanceof String)
+ return range.isIncluded(Version.create((String) lval));
+ }
+ if (rval instanceof SimplePattern) {
+ if (lval instanceof CharSequence)
+ return ((SimplePattern) rval).isMatch((CharSequence) lval);
+ if (lval instanceof Character || lval instanceof Number || lval instanceof Boolean)
+ return ((SimplePattern) rval).isMatch(lval.toString());
+
+ } else if (rval instanceof LDAPApproximation) {
+ if (lval instanceof CharSequence)
+ return ((LDAPApproximation) rval).isMatch((CharSequence) lval);
+ if (lval instanceof Character || lval instanceof Number || lval instanceof Boolean)
+ return ((LDAPApproximation) rval).isMatch(lval.toString());
+
+ } else if (rval instanceof Class<?>) {
+ Class<?> rclass = (Class<?>) rval;
+ return lval instanceof Class<?> ? rclass.isAssignableFrom((Class<?>) lval) : rclass.isInstance(lval);
+ }
+
+ if (lval == null || rval == null)
+ return false;
+
+ throw new IllegalArgumentException("Cannot match a " + lval.getClass().getName() + " with a " + rval.getClass().getName()); //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ public int getExpressionType() {
+ return TYPE_MATCHES;
+ }
+
+ public String getOperator() {
+ return OPERATOR_MATCHES;
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ if (!(rhs instanceof Literal))
+ throw new UnsupportedOperationException();
+
+ boolean escapeWild = true;
+ Object val = rhs.evaluate(null);
+ buf.append('(');
+ appendLDAPAttribute(buf);
+ if (val instanceof LDAPApproximation) {
+ buf.append(getOperator());
+ } else if (val instanceof SimplePattern) {
+ buf.append('=');
+ escapeWild = false;
+ } else
+ throw new UnsupportedOperationException();
+ appendLDAPEscaped(buf, val.toString(), escapeWild);
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java
new file mode 100644
index 000000000..5d442f62c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import java.lang.reflect.*;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * <p>An expression that performs member calls to obtain some value
+ * from some object instance. It uses standard bean semantics so
+ * that an attempt to obtain &quot;value&quot; will cause an
+ * attempt to call <code>getValue()</code> and if no such method
+ * exists, <code>isValue()</code> and if that doesn't work either,
+ * <code>value()</code>.</p>
+ */
+public abstract class Member extends Unary {
+
+ public static final class DynamicMember extends Member {
+ private static final String GET_PREFIX = "get"; //$NON-NLS-1$
+ private static final String IS_PREFIX = "is"; //$NON-NLS-1$
+ private static final Class<?>[] NO_ARG_TYPES = new Class[0];
+
+ private Class<?> lastClass;
+
+ private Method method;
+ private String methodName;
+
+ DynamicMember(Expression operand, String name) {
+ super(operand, name, Expression.emptyArray);
+ if (!(name.startsWith(GET_PREFIX) || name.startsWith(IS_PREFIX)))
+ name = GET_PREFIX + Character.toUpperCase(name.charAt(0)) + name.substring(1);
+ this.methodName = name;
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return invoke(operand.evaluate(context));
+ }
+
+ public Object invoke(Object self) {
+ if (self == null)
+ throw new IllegalArgumentException("Cannot access member \'" + name + "\' in null"); //$NON-NLS-1$//$NON-NLS-2$
+
+ if (self instanceof MemberProvider)
+ return ((MemberProvider) self).getMember(name);
+
+ Class<?> c = self.getClass();
+ if (lastClass == null || !lastClass.isAssignableFrom(c)) {
+ Method m;
+ for (;;) {
+ try {
+ m = c.getMethod(methodName, NO_ARG_TYPES);
+ if (!Modifier.isPublic(m.getModifiers()))
+ throw new NoSuchMethodException();
+ break;
+ } catch (NoSuchMethodException e) {
+ if (methodName.startsWith(GET_PREFIX))
+ // Switch from using getXxx() to isXxx()
+ methodName = IS_PREFIX + Character.toUpperCase(name.charAt(0)) + name.substring(1);
+ else if (methodName.startsWith(IS_PREFIX))
+ // Switch from using isXxx() to xxx()
+ methodName = name;
+ else
+ throw new IllegalArgumentException("Cannot find a public member \'" + name + "\' in a " + self.getClass().getName()); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ }
+
+ // Since we already checked that it's public. This will speed
+ // up the calls a bit.
+ m.setAccessible(true);
+ lastClass = c;
+ method = m;
+ }
+
+ Exception checked;
+ try {
+ return method.invoke(self, NO_ARGS);
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (IllegalAccessException e) {
+ checked = e;
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getTargetException();
+ if (cause instanceof RuntimeException)
+ throw (RuntimeException) cause;
+ if (cause instanceof Error)
+ throw (Error) cause;
+ checked = (Exception) cause;
+ }
+ throw new RuntimeException("Problem invoking " + methodName + " on a " + self.getClass().getName(), checked); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ static final Object[] NO_ARGS = new Object[0];
+
+ static Member createDynamicMember(Expression operand, String name) {
+ return new DynamicMember(operand, name);
+ }
+
+ protected final Expression[] argExpressions;
+
+ final String name;
+
+ protected Member(Expression operand, String name, Expression[] args) {
+ super(operand);
+ this.name = name;
+ this.argExpressions = args;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ if (super.accept(visitor))
+ for (int idx = 0; idx < argExpressions.length; ++idx)
+ if (!argExpressions[idx].accept(visitor))
+ return false;
+ return true;
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0) {
+ cmp = name.compareTo(((Member) e).name);
+ if (cmp == 0)
+ cmp = compare(argExpressions, ((Member) e).argExpressions);
+ }
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ if (super.equals(o)) {
+ Member mo = (Member) o;
+ return name.equals(mo.name) && equals(argExpressions, mo.argExpressions);
+ }
+ return false;
+ }
+
+ public int getExpressionType() {
+ return TYPE_MEMBER;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getOperator() {
+ return OPERATOR_MEMBER;
+ }
+
+ public int getPriority() {
+ return PRIORITY_MEMBER;
+ }
+
+ public int hashCode() {
+ int result = 31 + name.hashCode();
+ result = 31 * result + operand.hashCode();
+ return 31 * result + hashCode(argExpressions);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ if (operand != rootVariable) {
+ appendOperand(bld, rootVariable, operand, getPriority());
+ bld.append('.');
+ }
+ bld.append(name);
+ if (argExpressions.length > 0) {
+ bld.append('(');
+ elementsToString(bld, rootVariable, argExpressions);
+ bld.append(')');
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MemberProvider.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MemberProvider.java
new file mode 100644
index 000000000..9eb460a8d
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MemberProvider.java
@@ -0,0 +1,139 @@
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.*;
+import java.util.Map.Entry;
+import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils;
+import org.osgi.framework.ServiceReference;
+
+public abstract class MemberProvider {
+
+ static class DictionaryMemberProvider extends MemberProvider {
+ private final Dictionary<String, ? extends Object> dictionary;
+
+ public DictionaryMemberProvider(Dictionary<String, ? extends Object> dictionary) {
+ this.dictionary = dictionary;
+ }
+
+ @Override
+ public Object getMember(String memberName) {
+ return dictionary.get(memberName);
+ }
+ }
+
+ static class CIDictionaryMemberProvider extends DictionaryMemberProvider {
+ public CIDictionaryMemberProvider(Dictionary<String, ? extends Object> dictionary) {
+ super(lowerCaseKeys(dictionary));
+ }
+
+ @Override
+ public Object getMember(String memberName) {
+ return super.getMember(memberName == null ? null : memberName.toLowerCase());
+ }
+
+ private static Dictionary<String, ? extends Object> lowerCaseKeys(Dictionary<String, ? extends Object> dictionary) {
+ boolean hasUpperCase = false;
+ for (Enumeration<String> keys = dictionary.keys(); keys.hasMoreElements();) {
+ String key = keys.nextElement();
+ if (key.toLowerCase() != key) {
+ hasUpperCase = true;
+ break;
+ }
+ }
+ if (!hasUpperCase)
+ return dictionary;
+
+ Dictionary<String, Object> lcMap = new Hashtable<String, Object>(dictionary.size());
+ for (Enumeration<String> keys = dictionary.keys(); keys.hasMoreElements();) {
+ String key = keys.nextElement();
+ if (lcMap.put(key.toLowerCase(), dictionary.get(key)) != null)
+ throw new IllegalArgumentException("case variants of the same key name: '" + key + '\''); //$NON-NLS-1$
+ }
+ return lcMap;
+ }
+ }
+
+ static class MapMemberProvider extends MemberProvider {
+ private final Map<String, ? extends Object> map;
+
+ public MapMemberProvider(Map<String, ? extends Object> map) {
+ this.map = map;
+ }
+
+ @Override
+ public Object getMember(String memberName) {
+ return map.get(memberName);
+ }
+ }
+
+ static class CIMapMemberProvider extends MapMemberProvider {
+ public CIMapMemberProvider(Map<String, ? extends Object> map) {
+ super(lowerCaseKeys(map));
+ }
+
+ @Override
+ public Object getMember(String memberName) {
+ return super.getMember(memberName == null ? null : memberName.toLowerCase());
+ }
+
+ private static Map<String, ? extends Object> lowerCaseKeys(Map<String, ? extends Object> map) {
+ boolean hasUpperCase = false;
+ Set<? extends Entry<String, ? extends Object>> entrySet = map.entrySet();
+ for (Entry<String, ?> entry : entrySet) {
+ String key = entry.getKey();
+ String lowKey = key.toLowerCase();
+ if (key != lowKey) {
+ hasUpperCase = true;
+ break;
+ }
+ }
+ if (!hasUpperCase)
+ return map;
+
+ Map<String, Object> lcMap = new HashMap<String, Object>(map.size());
+ for (Entry<String, ?> entry : entrySet) {
+ if (lcMap.put(entry.getKey().toLowerCase(), entry.getValue()) != null)
+ throw new IllegalArgumentException("case variants of the same key name: '" + entry.getKey() + '\''); //$NON-NLS-1$
+ }
+ return lcMap;
+ }
+ }
+
+ static class ServiceRefMemberProvider extends MemberProvider {
+ private final ServiceReference serviceRef;
+
+ public ServiceRefMemberProvider(ServiceReference serviceRef) {
+ this.serviceRef = serviceRef;
+ }
+
+ @Override
+ public Object getMember(String memberName) {
+ return serviceRef.getProperty(memberName);
+ }
+ }
+
+ private static final MemberProvider emptyProvider = create(CollectionUtils.emptyMap(), false);
+
+ /**
+ * Create a new member provider on the given value. The value can be an instance of a {@link Map}, {@link Dictionary},
+ * or {@link ServiceReference}.
+ * @param value The value that provides the members
+ * @param caseInsensitive <code>true</code> if the members should be retrievable in a case insensitive way.
+ * @return A member provided that is backed by <code>value</code>.
+ */
+ @SuppressWarnings("unchecked")
+ public static MemberProvider create(Object value, boolean caseInsensitive) {
+ if (value instanceof Map<?, ?>)
+ return caseInsensitive ? new CIMapMemberProvider((Map<String, ?>) value) : new MapMemberProvider((Map<String, ?>) value);
+ if (value instanceof Dictionary<?, ?>)
+ return caseInsensitive ? new CIDictionaryMemberProvider((Dictionary<String, ?>) value) : new DictionaryMemberProvider((Dictionary<String, ?>) value);
+ if (value instanceof ServiceReference)
+ return new ServiceRefMemberProvider((ServiceReference) value);
+ throw new IllegalArgumentException();
+ }
+
+ public abstract Object getMember(String memberName);
+
+ public static MemberProvider emptyProvider() {
+ return emptyProvider;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/NAry.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/NAry.java
new file mode 100644
index 000000000..805cff2ab
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/NAry.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * 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;
+
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * The abstract baseclass for all N-ary expressions
+ */
+public abstract class NAry extends Expression {
+ public final Expression[] operands;
+
+ protected NAry(Expression[] operands) {
+ this.operands = operands;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ if (super.accept(visitor))
+ for (int idx = 0; idx < operands.length; ++idx)
+ if (!operands[idx].accept(visitor))
+ return false;
+ return true;
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = compare(operands, ((NAry) e).operands);
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && equals(operands, ((NAry) o).operands);
+ }
+
+ public abstract String getOperator();
+
+ public int hashCode() {
+ return hashCode(operands);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ appendOperand(bld, rootVariable, operands[0], getPriority());
+ for (int idx = 1; idx < operands.length; ++idx) {
+ bld.append(' ');
+ bld.append(getOperator());
+ bld.append(' ');
+ appendOperand(bld, rootVariable, operands[idx], getPriority());
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Not.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Not.java
new file mode 100644
index 000000000..5246859a3
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Not.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * An expression that yields <code>true</code> when its operand does not.
+ */
+final class Not extends Unary {
+ Not(Expression operand) {
+ super(operand);
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return Boolean.valueOf(operand.evaluate(context) != Boolean.TRUE);
+ }
+
+ public int getExpressionType() {
+ return TYPE_NOT;
+ }
+
+ public String getOperator() {
+ return OPERATOR_NOT;
+ }
+
+ public int getPriority() {
+ return PRIORITY_NOT;
+ }
+
+ public int hashCode() {
+ return 3 * operand.hashCode();
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ buf.append("(!"); //$NON-NLS-1$
+ operand.toLDAPString(buf);
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Or.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Or.java
new file mode 100644
index 000000000..32805042e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Or.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * n-ary OR operator. The full evaluation is <code>false</code> if none of its operands
+ * evaluate to <code>true</code>.
+ */
+final class Or extends NAry {
+ public Or(Expression[] operands) {
+ super(assertLength(operands, 2, OPERATOR_OR));
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ for (int idx = 0; idx < operands.length; ++idx) {
+ if (operands[idx].evaluate(context) == Boolean.TRUE)
+ return Boolean.TRUE;
+ }
+ return Boolean.FALSE;
+ }
+
+ public int getExpressionType() {
+ return TYPE_OR;
+ }
+
+ public String getOperator() {
+ return OPERATOR_OR;
+ }
+
+ public int getPriority() {
+ return PRIORITY_OR;
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ buf.append("(|"); //$NON-NLS-1$
+ for (int idx = 0; idx < operands.length; ++idx)
+ operands[idx].toLDAPString(buf);
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Parameter.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Parameter.java
new file mode 100644
index 000000000..b4c56346e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Parameter.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * The abstract base class for the indexed and keyed parameters
+ */
+public class Parameter extends Expression {
+ final int position;
+
+ Parameter(int position) {
+ this.position = position;
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = position - ((Parameter) e).position;
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+ if (o == null)
+ return false;
+ return getClass() == o.getClass() && position == ((Parameter) o).position;
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return context.getParameter(position);
+ }
+
+ public int getExpressionType() {
+ return TYPE_PARAMETER;
+ }
+
+ public String getOperator() {
+ return OPERATOR_PARAMETER;
+ }
+
+ public int getPriority() {
+ return PRIORITY_VARIABLE;
+ }
+
+ public int hashCode() {
+ return 31 * (1 + position);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ bld.append('$');
+ bld.append(position);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/RepeatableIterator.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/RepeatableIterator.java
new file mode 100644
index 000000000..e44a4e507
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/RepeatableIterator.java
@@ -0,0 +1,237 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import java.util.*;
+import org.eclipse.equinox.p2.query.IQueryResult;
+
+public class RepeatableIterator<T> implements IRepeatableIterator<T> {
+ private final List<T> values;
+ private int position = -1;
+
+ @SuppressWarnings("unchecked")
+ public static <T> IRepeatableIterator<T> create(Object unknown) {
+ if (unknown.getClass().isArray())
+ return create((T[]) unknown);
+ if (unknown instanceof Iterator<?>)
+ return create((Iterator<T>) unknown);
+ if (unknown instanceof List<?>)
+ return create((List<T>) unknown);
+ if (unknown instanceof Collection<?>)
+ return create((Collection<T>) unknown);
+ if (unknown instanceof Map<?, ?>)
+ return create((Set<T>) ((Map<?, ?>) unknown).entrySet());
+ if (unknown instanceof IQueryResult<?>)
+ return create((IQueryResult<T>) unknown);
+ throw new IllegalArgumentException("Cannot convert a " + unknown.getClass().getName() + " into an iterator"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ public static <T> IRepeatableIterator<T> create(Iterator<T> iterator) {
+ return iterator instanceof IRepeatableIterator<?> ? ((IRepeatableIterator<T>) iterator).getCopy() : new ElementRetainingIterator<T>(iterator);
+ }
+
+ public static <T> IRepeatableIterator<T> create(List<T> values) {
+ return new RepeatableIterator<T>(values);
+ }
+
+ public static <T> IRepeatableIterator<T> create(Collection<T> values) {
+ return new CollectionIterator<T>(values);
+ }
+
+ public static <T> IRepeatableIterator<T> create(IQueryResult<T> values) {
+ return new QueryResultIterator<T>(values);
+ }
+
+ public static <T> IRepeatableIterator<T> create(T[] values) {
+ return new ArrayIterator<T>(values);
+ }
+
+ RepeatableIterator(List<T> values) {
+ this.values = values;
+ }
+
+ public IRepeatableIterator<T> getCopy() {
+ return new RepeatableIterator<T>(values);
+ }
+
+ public boolean hasNext() {
+ return position + 1 < values.size();
+ }
+
+ public T next() {
+ if (++position == values.size()) {
+ --position;
+ throw new NoSuchElementException();
+ }
+ return values.get(position);
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object getIteratorProvider() {
+ return values;
+ }
+
+ void setPosition(int position) {
+ this.position = position;
+ }
+
+ List<T> getValues() {
+ return values;
+ }
+
+ static class ArrayIterator<T> implements IRepeatableIterator<T> {
+ private final T[] array;
+ private int position = -1;
+
+ public ArrayIterator(T[] array) {
+ this.array = array;
+ }
+
+ public Object getIteratorProvider() {
+ return array;
+ }
+
+ public boolean hasNext() {
+ return position + 1 < array.length;
+ }
+
+ public T next() {
+ if (++position >= array.length)
+ throw new NoSuchElementException();
+ return array[position];
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public IRepeatableIterator<T> getCopy() {
+ return new ArrayIterator<T>(array);
+ }
+ }
+
+ static class CollectionIterator<T> implements IRepeatableIterator<T> {
+ private final Collection<T> collection;
+
+ private final Iterator<T> iterator;
+
+ CollectionIterator(Collection<T> collection) {
+ this.collection = collection;
+ this.iterator = collection.iterator();
+ }
+
+ public IRepeatableIterator<T> getCopy() {
+ return new CollectionIterator<T>(collection);
+ }
+
+ public Object getIteratorProvider() {
+ return collection;
+ }
+
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ public T next() {
+ return iterator.next();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ static class QueryResultIterator<T> implements IRepeatableIterator<T> {
+ private final IQueryResult<T> queryResult;
+
+ private final Iterator<T> iterator;
+
+ QueryResultIterator(IQueryResult<T> queryResult) {
+ this.queryResult = queryResult;
+ this.iterator = queryResult.iterator();
+ }
+
+ public IRepeatableIterator<T> getCopy() {
+ return new QueryResultIterator<T>(queryResult);
+ }
+
+ public Object getIteratorProvider() {
+ return queryResult;
+ }
+
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ public T next() {
+ return iterator.next();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ static class ElementRetainingIterator<T> extends RepeatableIterator<T> {
+
+ private Iterator<T> innerIterator;
+
+ ElementRetainingIterator(Iterator<T> iterator) {
+ super(new ArrayList<T>());
+ innerIterator = iterator;
+ }
+
+ public synchronized boolean hasNext() {
+ if (innerIterator != null) {
+ if (innerIterator.hasNext())
+ return true;
+ innerIterator = null;
+ setPosition(getValues().size());
+ }
+ return super.hasNext();
+ }
+
+ public synchronized T next() {
+ if (innerIterator != null) {
+ T val = innerIterator.next();
+ getValues().add(val);
+ return val;
+ }
+ return super.next();
+ }
+
+ public synchronized IRepeatableIterator<T> getCopy() {
+ // If the current iterator still exists, we must exhaust it first
+ //
+ exhaustInnerIterator();
+ return super.getCopy();
+ }
+
+ public synchronized Object getIteratorProvider() {
+ exhaustInnerIterator();
+ return super.getIteratorProvider();
+ }
+
+ private void exhaustInnerIterator() {
+ if (innerIterator != null) {
+ List<T> values = getValues();
+ int savePos = values.size() - 1;
+ while (innerIterator.hasNext())
+ values.add(innerIterator.next());
+ innerIterator = null;
+ setPosition(savePos);
+ }
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Unary.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Unary.java
new file mode 100644
index 000000000..9c6b7d8a5
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Unary.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * The abstract base class for all unary expressions
+ */
+public abstract class Unary extends Expression {
+ public final Expression operand;
+
+ protected Unary(Expression operand) {
+ this.operand = operand;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return super.accept(visitor) && operand.accept(visitor);
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = operand.compareTo(((Unary) e).operand);
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && operand.equals(((Unary) o).operand);
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return operand.evaluate(context);
+ }
+
+ public int hashCode() {
+ return operand.hashCode() * 3 + operand.getExpressionType();
+ }
+
+ public Expression getOperand() {
+ return operand;
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ bld.append(getOperator());
+ appendOperand(bld, rootVariable, operand, getPriority());
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Variable.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Variable.java
new file mode 100644
index 000000000..a7adfb738
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Variable.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * 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.p2.metadata.expression;
+
+import java.util.Iterator;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * An expression representing a variable stack in the current thread.
+ */
+public class Variable extends Expression {
+
+ private final String name;
+
+ public Variable(String name) {
+ this.name = name;
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = name.compareTo(((Variable) e).name);
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && name.equals(((Variable) o).name);
+ }
+
+ public final Object evaluate(IEvaluationContext context) {
+ return context.getValue(this);
+ }
+
+ public Iterator<?> evaluateAsIterator(IEvaluationContext context) {
+ Object value = context.getValue(this);
+ if (value instanceof IRepeatableIterator<?>)
+ return ((IRepeatableIterator<?>) value).getCopy();
+
+ Iterator<?> itor = RepeatableIterator.create(value);
+ setValue(context, itor);
+ return itor;
+ }
+
+ public int getExpressionType() {
+ return TYPE_VARIABLE;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getOperator() {
+ return "<variable>"; //$NON-NLS-1$
+ }
+
+ public int getPriority() {
+ return PRIORITY_VARIABLE;
+ }
+
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ public final void setValue(IEvaluationContext context, Object value) {
+ context.setValue(this, value);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ bld.append(name);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/ExpressionParser.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/ExpressionParser.java
new file mode 100644
index 000000000..905d0927c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/ExpressionParser.java
@@ -0,0 +1,617 @@
+/*******************************************************************************
+ * 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 org.eclipse.equinox.internal.p2.metadata.expression.LDAPApproximation;
+
+import java.util.*;
+import org.eclipse.equinox.internal.p2.metadata.expression.IExpressionConstants;
+import org.eclipse.equinox.p2.metadata.expression.*;
+
+public class ExpressionParser extends Stack<IExpression> implements IExpressionConstants, IExpressionParser {
+ private static final long serialVersionUID = 5481439062356612378L;
+
+ protected static final int TOKEN_OR = 1;
+ protected static final int TOKEN_AND = 2;
+
+ protected static final int TOKEN_EQUAL = 10;
+ protected static final int TOKEN_NOT_EQUAL = 11;
+ protected static final int TOKEN_LESS = 12;
+ protected static final int TOKEN_LESS_EQUAL = 13;
+ protected static final int TOKEN_GREATER = 14;
+ protected static final int TOKEN_GREATER_EQUAL = 15;
+ protected static final int TOKEN_MATCHES = 16;
+
+ protected static final int TOKEN_NOT = 20;
+ protected static final int TOKEN_DOT = 21;
+ protected static final int TOKEN_COMMA = 22;
+ protected static final int TOKEN_PIPE = 23;
+ protected static final int TOKEN_DOLLAR = 24;
+ protected static final int TOKEN_IF = 25;
+ protected static final int TOKEN_ELSE = 26;
+
+ protected static final int TOKEN_LP = 30;
+ protected static final int TOKEN_RP = 31;
+ protected static final int TOKEN_LB = 32;
+ protected static final int TOKEN_RB = 33;
+ protected static final int TOKEN_LC = 34;
+ protected static final int TOKEN_RC = 35;
+
+ protected static final int TOKEN_IDENTIFIER = 40;
+ protected static final int TOKEN_LITERAL = 41;
+
+ protected static final int TOKEN_NULL = 50;
+ protected static final int TOKEN_TRUE = 51;
+ protected static final int TOKEN_FALSE = 52;
+
+ private static final int TOKEN_ALL = 60;
+ private static final int TOKEN_EXISTS = 61;
+
+ protected static final int TOKEN_END = 0;
+ protected static final int TOKEN_ERROR = -1;
+
+ protected static final Map<String, Integer> keywords;
+ static {
+ keywords = new HashMap<String, Integer>();
+ keywords.put(KEYWORD_FALSE, new Integer(TOKEN_FALSE));
+ keywords.put(KEYWORD_NULL, new Integer(TOKEN_NULL));
+ keywords.put(KEYWORD_TRUE, new Integer(TOKEN_TRUE));
+ keywords.put(KEYWORD_ALL, new Integer(TOKEN_ALL));
+ keywords.put(KEYWORD_EXISTS, new Integer(TOKEN_EXISTS));
+ }
+
+ protected final IExpressionFactory factory;
+
+ protected String expression;
+ protected int tokenPos;
+ protected int currentToken;
+ protected int lastTokenPos;
+ protected Object tokenValue;
+ protected String rootVariable;
+
+ public ExpressionParser(IExpressionFactory factory) {
+ this.factory = factory;
+ }
+
+ public synchronized IExpression parse(String exprString) {
+ expression = exprString;
+ tokenPos = 0;
+ currentToken = 0;
+ tokenValue = null;
+ IExpression thisVariable = factory.thisVariable();
+ rootVariable = ExpressionUtil.getName(thisVariable);
+ push(thisVariable);
+ try {
+ nextToken();
+ IExpression expr = currentToken == TOKEN_END ? factory.constant(Boolean.TRUE) : parseCondition();
+ assertToken(TOKEN_END);
+ return expr;
+ } finally {
+ if (thisVariable != null)
+ popVariable(); // pop item
+ }
+ }
+
+ protected Map<String, Integer> keywordToTokenMap() {
+ return keywords;
+ }
+
+ protected IExpression parseCondition() {
+ // Just a hook in this parser. Conditions are not supported
+ return parseOr();
+ }
+
+ protected IExpression parseOr() {
+ IExpression expr = parseAnd();
+ if (currentToken != TOKEN_OR)
+ return expr;
+
+ ArrayList<IExpression> exprs = new ArrayList<IExpression>();
+ exprs.add(expr);
+ do {
+ nextToken();
+ exprs.add(parseAnd());
+ } while (currentToken == TOKEN_OR);
+ return factory.or(exprs.toArray(new IExpression[exprs.size()]));
+ }
+
+ protected IExpression parseAnd() {
+ IExpression expr = parseBinary();
+ if (currentToken != TOKEN_AND)
+ return expr;
+
+ ArrayList<IExpression> exprs = new ArrayList<IExpression>();
+ exprs.add(expr);
+ do {
+ nextToken();
+ exprs.add(parseBinary());
+ } while (currentToken == TOKEN_AND);
+ return factory.and(exprs.toArray(new IExpression[exprs.size()]));
+ }
+
+ protected IExpression parseBinary() {
+ IExpression expr = parseNot();
+ for (;;) {
+ switch (currentToken) {
+ case TOKEN_OR :
+ case TOKEN_AND :
+ case TOKEN_RP :
+ case TOKEN_RB :
+ case TOKEN_RC :
+ case TOKEN_COMMA :
+ case TOKEN_IF :
+ case TOKEN_ELSE :
+ case TOKEN_END :
+ break;
+ case TOKEN_EQUAL :
+ case TOKEN_NOT_EQUAL :
+ case TOKEN_GREATER :
+ case TOKEN_GREATER_EQUAL :
+ case TOKEN_LESS :
+ case TOKEN_LESS_EQUAL :
+ case TOKEN_MATCHES :
+ int realToken = currentToken;
+ nextToken();
+ IExpression rhs;
+ if (realToken == TOKEN_MATCHES && currentToken == TOKEN_LITERAL && tokenValue instanceof String)
+ rhs = factory.constant(new LDAPApproximation((String) tokenValue));
+ else
+ rhs = parseNot();
+ switch (realToken) {
+ case TOKEN_EQUAL :
+ expr = factory.equals(expr, rhs);
+ break;
+ case TOKEN_NOT_EQUAL :
+ expr = factory.not(factory.equals(expr, rhs));
+ break;
+ case TOKEN_GREATER :
+ expr = factory.greater(expr, rhs);
+ break;
+ case TOKEN_GREATER_EQUAL :
+ expr = factory.greaterEqual(expr, rhs);
+ break;
+ case TOKEN_LESS :
+ expr = factory.less(expr, rhs);
+ break;
+ case TOKEN_LESS_EQUAL :
+ expr = factory.lessEqual(expr, rhs);
+ break;
+ default :
+ expr = factory.matches(expr, rhs);
+ }
+ continue;
+ default :
+ throw syntaxError();
+ }
+ break;
+ }
+ return expr;
+ }
+
+ protected IExpression parseNot() {
+ if (currentToken == TOKEN_NOT) {
+ nextToken();
+ IExpression expr = parseNot();
+ return factory.not(expr);
+ }
+ return parseCollectionExpression();
+ }
+
+ protected IExpression parseCollectionExpression() {
+ IExpression expr = parseCollectionLHS();
+ if (expr == null) {
+ expr = parseMember();
+ if (currentToken != TOKEN_DOT)
+ return expr;
+ nextToken();
+ }
+ for (;;) {
+ int funcToken = currentToken;
+ nextToken();
+ assertToken(TOKEN_LP);
+ nextToken();
+ expr = parseCollectionRHS(expr, funcToken);
+ if (currentToken != TOKEN_DOT)
+ break;
+ nextToken();
+ }
+ return expr;
+ }
+
+ protected IExpression parseCollectionLHS() {
+ IExpression expr = null;
+ switch (currentToken) {
+ case TOKEN_EXISTS :
+ case TOKEN_ALL :
+ expr = getVariableOrRootMember(rootVariable);
+ break;
+ }
+ return expr;
+ }
+
+ protected IExpression parseCollectionRHS(IExpression expr, int funcToken) {
+ switch (funcToken) {
+ case TOKEN_EXISTS :
+ expr = factory.exists(expr, parseLambdaDefinition());
+ break;
+ case TOKEN_ALL :
+ expr = factory.all(expr, parseLambdaDefinition());
+ break;
+ default :
+ throw syntaxError();
+ }
+ return expr;
+ }
+
+ protected IExpression parseLambdaDefinition() {
+ assertToken(TOKEN_IDENTIFIER);
+ IExpression each = factory.variable((String) tokenValue);
+ push(each);
+ try {
+ nextToken();
+ assertToken(TOKEN_PIPE);
+ nextToken();
+ IExpression body = parseCondition();
+ assertToken(TOKEN_RP);
+ nextToken();
+ return factory.lambda(each, body);
+ } finally {
+ pop();
+ }
+ }
+
+ protected IExpression parseMember() {
+ IExpression expr = parseUnary();
+ String name;
+ while (currentToken == TOKEN_DOT || currentToken == TOKEN_LB) {
+ int savePos = tokenPos;
+ int saveToken = currentToken;
+ Object saveTokenValue = tokenValue;
+ nextToken();
+ if (saveToken == TOKEN_DOT) {
+ switch (currentToken) {
+ case TOKEN_IDENTIFIER :
+ name = (String) tokenValue;
+ nextToken();
+ expr = factory.member(expr, name);
+ break;
+
+ default :
+ tokenPos = savePos;
+ currentToken = saveToken;
+ tokenValue = saveTokenValue;
+ return expr;
+ }
+ } else {
+ IExpression atExpr = parseMember();
+ assertToken(TOKEN_RB);
+ nextToken();
+ expr = factory.at(expr, atExpr);
+ }
+ }
+ return expr;
+ }
+
+ protected IExpression parseUnary() {
+ IExpression expr;
+ switch (currentToken) {
+ case TOKEN_LP :
+ nextToken();
+ expr = parseCondition();
+ assertToken(TOKEN_RP);
+ nextToken();
+ break;
+ case TOKEN_LITERAL :
+ expr = factory.constant(tokenValue);
+ nextToken();
+ break;
+ case TOKEN_IDENTIFIER :
+ expr = getVariableOrRootMember((String) tokenValue);
+ nextToken();
+ break;
+ case TOKEN_NULL :
+ expr = factory.constant(null);
+ nextToken();
+ break;
+ case TOKEN_TRUE :
+ expr = factory.constant(Boolean.TRUE);
+ nextToken();
+ break;
+ case TOKEN_FALSE :
+ expr = factory.constant(Boolean.FALSE);
+ nextToken();
+ break;
+ case TOKEN_DOLLAR :
+ expr = parseParameter();
+ break;
+ default :
+ throw syntaxError();
+ }
+ return expr;
+ }
+
+ private IExpression parseParameter() {
+ if (currentToken == TOKEN_DOLLAR) {
+ nextToken();
+ if (currentToken == TOKEN_LITERAL && tokenValue instanceof Integer) {
+ IExpression param = factory.indexedParameter(((Integer) tokenValue).intValue());
+ nextToken();
+ return param;
+ }
+ }
+ throw syntaxError();
+ }
+
+ protected IExpression[] parseArray() {
+ IExpression expr = parseCondition();
+ if (currentToken != TOKEN_COMMA)
+ return new IExpression[] {expr};
+
+ ArrayList<IExpression> operands = new ArrayList<IExpression>();
+ operands.add(expr);
+ do {
+ nextToken();
+ if (currentToken == TOKEN_LC)
+ // We don't allow lambdas in the array
+ break;
+ operands.add(parseCondition());
+ } while (currentToken == TOKEN_COMMA);
+ return operands.toArray(new IExpression[operands.size()]);
+ }
+
+ protected void assertToken(int token) {
+ if (currentToken != token)
+ throw syntaxError();
+ }
+
+ protected IExpression getVariableOrRootMember(String id) {
+ int idx = size();
+ while (--idx >= 0) {
+ IExpression v = get(idx);
+ if (id.equals(v.toString()))
+ return v;
+ }
+
+ if (rootVariable == null || rootVariable.equals(id))
+ throw syntaxError("No such variable: " + id); //$NON-NLS-1$
+
+ return factory.member(getVariableOrRootMember(rootVariable), id);
+ }
+
+ protected void nextToken() {
+ tokenValue = null;
+ int top = expression.length();
+ char c = 0;
+ while (tokenPos < top) {
+ c = expression.charAt(tokenPos);
+ if (!Character.isWhitespace(c))
+ break;
+ ++tokenPos;
+ }
+ if (tokenPos >= top) {
+ lastTokenPos = top;
+ currentToken = TOKEN_END;
+ return;
+ }
+
+ lastTokenPos = tokenPos;
+ switch (c) {
+ case '|' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '|') {
+ tokenValue = OPERATOR_OR;
+ currentToken = TOKEN_OR;
+ tokenPos += 2;
+ } else {
+ currentToken = TOKEN_PIPE;
+ ++tokenPos;
+ }
+ break;
+
+ case '&' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '&') {
+ tokenValue = OPERATOR_AND;
+ currentToken = TOKEN_AND;
+ tokenPos += 2;
+ } else
+ currentToken = TOKEN_ERROR;
+ break;
+
+ case '=' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
+ tokenValue = OPERATOR_EQUALS;
+ currentToken = TOKEN_EQUAL;
+ tokenPos += 2;
+ } else
+ currentToken = TOKEN_ERROR;
+ break;
+
+ case '!' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
+ tokenValue = OPERATOR_NOT_EQUALS;
+ currentToken = TOKEN_NOT_EQUAL;
+ tokenPos += 2;
+ } else {
+ currentToken = TOKEN_NOT;
+ ++tokenPos;
+ }
+ break;
+
+ case '~' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
+ tokenValue = OPERATOR_MATCHES;
+ currentToken = TOKEN_MATCHES;
+ tokenPos += 2;
+ } else
+ currentToken = TOKEN_ERROR;
+ break;
+
+ case '>' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
+ tokenValue = OPERATOR_GT_EQUAL;
+ currentToken = TOKEN_GREATER_EQUAL;
+ tokenPos += 2;
+ } else {
+ currentToken = TOKEN_GREATER;
+ ++tokenPos;
+ }
+ break;
+
+ case '<' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
+ tokenValue = OPERATOR_LT_EQUAL;
+ currentToken = TOKEN_LESS_EQUAL;
+ tokenPos += 2;
+ } else {
+ currentToken = TOKEN_LESS;
+ ++tokenPos;
+ }
+ break;
+
+ case '?' :
+ currentToken = TOKEN_IF;
+ ++tokenPos;
+ break;
+
+ case ':' :
+ currentToken = TOKEN_ELSE;
+ ++tokenPos;
+ break;
+
+ case '.' :
+ currentToken = TOKEN_DOT;
+ ++tokenPos;
+ break;
+
+ case '$' :
+ currentToken = TOKEN_DOLLAR;
+ ++tokenPos;
+ break;
+
+ case '{' :
+ currentToken = TOKEN_LC;
+ ++tokenPos;
+ break;
+
+ case '}' :
+ currentToken = TOKEN_RC;
+ ++tokenPos;
+ break;
+
+ case '(' :
+ currentToken = TOKEN_LP;
+ ++tokenPos;
+ break;
+
+ case ')' :
+ currentToken = TOKEN_RP;
+ ++tokenPos;
+ break;
+
+ case '[' :
+ currentToken = TOKEN_LB;
+ ++tokenPos;
+ break;
+
+ case ']' :
+ currentToken = TOKEN_RB;
+ ++tokenPos;
+ break;
+
+ case ',' :
+ currentToken = TOKEN_COMMA;
+ ++tokenPos;
+ break;
+
+ case '"' :
+ case '\'' : {
+ int start = ++tokenPos;
+ while (tokenPos < top && expression.charAt(tokenPos) != c)
+ ++tokenPos;
+ if (tokenPos == top) {
+ tokenPos = start - 1;
+ currentToken = TOKEN_ERROR;
+ } else {
+ tokenValue = expression.substring(start, tokenPos++);
+ currentToken = TOKEN_LITERAL;
+ }
+ break;
+ }
+
+ case '/' : {
+ int start = ++tokenPos;
+ StringBuffer buf = new StringBuffer();
+ while (tokenPos < top) {
+ c = expression.charAt(tokenPos);
+ if (c == '\\' && tokenPos + 1 < top) {
+ c = expression.charAt(++tokenPos);
+ if (c != '/')
+ buf.append('\\');
+ } else if (c == '/')
+ break;
+ buf.append(c);
+ ++tokenPos;
+ }
+ if (tokenPos == top) {
+ tokenPos = start - 1;
+ currentToken = TOKEN_ERROR;
+ } else {
+ tokenValue = SimplePattern.compile(expression.substring(start, tokenPos++));
+ currentToken = TOKEN_LITERAL;
+ }
+ break;
+ }
+
+ default :
+ if (Character.isDigit(c)) {
+ int start = tokenPos++;
+ while (tokenPos < top && Character.isDigit(expression.charAt(tokenPos)))
+ ++tokenPos;
+ tokenValue = Integer.valueOf(expression.substring(start, tokenPos));
+ currentToken = TOKEN_LITERAL;
+ break;
+ }
+ if (Character.isJavaIdentifierStart(c)) {
+ int start = tokenPos++;
+ while (tokenPos < top && Character.isJavaIdentifierPart(expression.charAt(tokenPos)))
+ ++tokenPos;
+ String word = expression.substring(start, tokenPos);
+ Integer token = keywordToTokenMap().get(word);
+ if (token == null)
+ currentToken = TOKEN_IDENTIFIER;
+ else
+ currentToken = token.intValue();
+ tokenValue = word;
+ break;
+ }
+ throw syntaxError();
+ }
+ }
+
+ protected void popVariable() {
+ if (isEmpty())
+ throw syntaxError();
+ pop();
+ }
+
+ protected ExpressionParseException syntaxError() {
+ Object tv = tokenValue;
+ if (tv == null) {
+ if (lastTokenPos >= expression.length())
+ return syntaxError("Unexpeced end of expression"); //$NON-NLS-1$
+ tv = expression.substring(lastTokenPos, lastTokenPos + 1);
+ }
+ return syntaxError("Unexpected token \"" + tv + '"'); //$NON-NLS-1$
+ }
+
+ protected ExpressionParseException syntaxError(String message) {
+ return new ExpressionParseException(expression, message, tokenPos);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/LDAPFilterParser.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/LDAPFilterParser.java
new file mode 100644
index 000000000..b5fd6714c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/LDAPFilterParser.java
@@ -0,0 +1,268 @@
+/*******************************************************************************
+ * 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.ArrayList;
+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 {
+ 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 synchronized IFilterExpression parse(String filter) {
+ filterString = filter;
+ position = 0;
+ try {
+ IExpression expr = parseFilter();
+ if (position != filterString.length())
+ throw syntaxException(Messages.filter_trailing_characters);
+ return factory.filterExpression(expr);
+ } 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)));
+ }
+}

Back to the top