diff options
author | Thomas Hallgren | 2009-12-12 00:19:20 +0000 |
---|---|---|
committer | Thomas Hallgren | 2009-12-12 00:19:20 +0000 |
commit | a37c758862531b18770cf25284a6dead2354a6a9 (patch) | |
tree | d882ec382771c87b2bdbdeb62dfd4ce27da698e7 /bundles/org.eclipse.equinox.p2.ql | |
parent | e5dd6d76fd742b19444a89fd132eb587835836ce (diff) | |
download | rt.equinox.p2-a37c758862531b18770cf25284a6dead2354a6a9.tar.gz rt.equinox.p2-a37c758862531b18770cf25284a6dead2354a6a9.tar.xz rt.equinox.p2-a37c758862531b18770cf25284a6dead2354a6a9.zip |
Bug 294691 - Create a QueryLanguage for p2 (attachment 153914)
Diffstat (limited to 'bundles/org.eclipse.equinox.p2.ql')
45 files changed, 4108 insertions, 0 deletions
diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/All.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/All.java new file mode 100644 index 000000000..73784486e --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/All.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.Iterator; +import org.eclipse.equinox.p2.ql.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); + } + + public int getExpressionType() { + return TYPE_ALL; + } + + 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; + } + + String getOperator() { + return KEYWORD_ALL; + } + + boolean isBoolean() { + return true; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/And.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/And.java new file mode 100644 index 000000000..4ecf87efe --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/And.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.*; +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * n-ary AND operator. This operator evaluates its first operand and then checks + * the class of the result. If the class is {@link Boolean} then it is assumed + * that all other operands also evaluates to a boolean and the full evaluation is + * <code>true</code> if all its operands evaluate to <code>true</code>. If the first + * result was not of class {@link Boolean}, then it is assumed that it can be accessed + * as an {@link Iterator} and that all other operands also evaluates to something that + * can be accessed as an {@link Iterator}. The AND operator will then function as a + * INTERSECT operator and the result is the set of elements that were found in all operands. + */ +final class And extends NAry { + And(Expression[] operands) { + super(assertLength(operands, 2, OPERATOR_AND)); + } + + public Object evaluate(IEvaluationContext context) { + Object firstValue = operands[0].evaluate(context); + + // Determine operation mode + if (firstValue instanceof Boolean) { + // The first value was boolean. Assume that the rest are too + if (!((Boolean) firstValue).booleanValue()) + return Boolean.FALSE; + + for (int idx = 1; idx < operands.length; ++idx) { + if (operands[idx].evaluate(context) != Boolean.TRUE) + return Boolean.FALSE; + } + return Boolean.TRUE; + } + + // Not a boolean. Assume that we can use an iterator on all values + Set resultSet = asSet(firstValue, false); // Safe since it will not be modified + for (int idx = 1; idx < operands.length && !resultSet.isEmpty(); ++idx) { + Iterator itor = operands[idx].evaluateAsIterator(context); + Set retained = new HashSet(); + while (itor.hasNext()) { + Object value = itor.next(); + if (resultSet.contains(value)) + retained.add(value); + } + resultSet = retained; + } + return resultSet; + } + + public int getExpressionType() { + return TYPE_AND; + } + + String getOperator() { + return OPERATOR_AND; + } + + int getPriority() { + return PRIORITY_AND; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Array.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Array.java new file mode 100644 index 000000000..0ec90059b --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Array.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import org.eclipse.equinox.internal.p2.ql.parser.IParserConstants; +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * An array of expressions + */ +final class Array extends NAry { + final class ArrayIterator implements Iterator { + private final IEvaluationContext context; + + private int pos = -1; + + public ArrayIterator(IEvaluationContext context) { + this.context = context; + } + + public boolean hasNext() { + return pos + 1 < operands.length; + } + + public Object next() { + if (++pos >= operands.length) { + --pos; + throw new NoSuchElementException(); + } + return operands[pos].evaluate(context); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + static void elementsToString(StringBuffer bld, Expression[] elements) { + int top = elements.length; + if (top > 0) { + elements[0].toString(bld); + for (int idx = 1; idx < top; ++idx) { + bld.append(", "); //$NON-NLS-1$ + appendOperand(bld, elements[idx], PRIORITY_COMMA); + } + } + } + + Array(Expression[] operands) { + super(assertLength(operands, 0, IParserConstants.OPERATOR_ARRAY)); + } + + public Object evaluate(IEvaluationContext context) { + return evaluateAsIterator(context); + } + + public Iterator evaluateAsIterator(IEvaluationContext context) { + return new ArrayIterator(context); + } + + public int getExpressionType() { + return TYPE_ARRAY; + } + + public void toString(StringBuffer bld) { + bld.append('['); + elementsToString(bld, operands); + bld.append(']'); + } + + String getOperator() { + return IParserConstants.OPERATOR_ARRAY; + } + + int getPriority() { + return PRIORITY_CONSTRUCTOR; + } + + boolean isBoolean() { + return false; + } + + boolean isCollection() { + return true; + } + + boolean isElementBoolean() { + return super.isBoolean(); + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Assignment.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Assignment.java new file mode 100644 index 000000000..23b69e8a7 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Assignment.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.ql.expression; + +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * An expression representing a variable stack in the current thread. + */ +class Assignment extends Binary { + Assignment(Variable variable, Expression expression) { + super(variable, expression); + } + + public final Object evaluate(IEvaluationContext context) { + Object value = rhs.evaluate(context); + context.setValue(lhs, value); + return value; + } + + public int getExpressionType() { + return TYPE_ASSIGNMENT; + } + + int getPriority() { + return PRIORITY_ASSIGNMENT; + } + + String getOperator() { + return OPERATOR_ASSIGN; + } + + boolean isBoolean() { + return rhs.isBoolean(); + } + + boolean isCollection() { + return rhs.isCollection(); + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/At.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/At.java new file mode 100644 index 000000000..f4ae7004c --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/At.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.ql.expression; + +import java.util.*; +import org.eclipse.equinox.internal.p2.ql.expression.Member.DynamicMember; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.ql.IEvaluationContext; +import org.eclipse.equinox.p2.ql.ITranslationSupport; + +/** + * This class represents indexed or keyed access to an indexed collection + * or a map. + */ +final class At extends Binary { + At(Expression lhs, Expression rhs) { + super(lhs, rhs); + } + + public Object evaluate(IEvaluationContext context) { + Object lval; + if (lhs instanceof DynamicMember) { + DynamicMember lm = (DynamicMember) lhs; + Object instance = lm.operand.evaluate(context); + if (instance instanceof IInstallableUnit) { + if ("properties".equals(lm.name)) //$NON-NLS-1$ + // Avoid full copy of the properties map just to get one member + return ((IInstallableUnit) instance).getProperty((String) rhs.evaluate(context)); + + if (VARIABLE_TRANSLATIONS.equals(lm.name)) { + ITranslationSupport ts = (ITranslationSupport) Variable.TRANSLATIONS.evaluate(context); + return ts.getIUProperty((IInstallableUnit) instance, (String) rhs.evaluate(context)); + } + } + 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 int getExpressionType() { + return TYPE_AT; + } + + public void toString(StringBuffer bld) { + appendOperand(bld, lhs, getPriority()); + bld.append('['); + appendOperand(bld, rhs, PRIORITY_COMMA); + bld.append(']'); + } + + String getOperator() { + return OPERATOR_AT; + } + + int getPriority() { + return PRIORITY_MEMBER; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Binary.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Binary.java new file mode 100644 index 000000000..ac17e0d49 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Binary.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * 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.ql.expression; + +import org.eclipse.equinox.p2.ql.IExpressionVisitor; + +/** + * The abstract base class for all binary operations + */ +abstract class Binary extends Expression { + final Expression lhs; + + final Expression rhs; + + 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 void toString(StringBuffer bld) { + appendOperand(bld, lhs, getPriority()); + bld.append(' '); + bld.append(getOperator()); + bld.append(' '); + appendOperand(bld, rhs, getPriority()); + } + + int countReferenceToEverything() { + return lhs.countReferenceToEverything() + rhs.countReferenceToEverything(); + } + + int getPriority() { + return PRIORITY_BINARY; // Default priority + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/BooleanFunction.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/BooleanFunction.java new file mode 100644 index 000000000..10268eb36 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/BooleanFunction.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * 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.ql.expression; + +/** + * A function that obtains a class based on a String + */ +final class BooleanFunction extends Function { + + public BooleanFunction(Expression[] operands) { + super(assertLength(operands, 1, 1, KEYWORD_BOOLEAN)); + assertNotCollection(operands[0], "parameter"); //$NON-NLS-1$ + } + + boolean assertSingleArgumentClass(Object v) { + return v instanceof String || v instanceof Boolean; + } + + Object createInstance(Object arg) { + if (arg instanceof String) + return Boolean.valueOf("true".equalsIgnoreCase((String) arg)); //$NON-NLS-1$ + if (arg instanceof Boolean) + return arg; + return Boolean.FALSE; + } + + String getOperator() { + return KEYWORD_BOOLEAN; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/CapabilityIndexFunction.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/CapabilityIndexFunction.java new file mode 100644 index 000000000..bd01ae714 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/CapabilityIndexFunction.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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.ql.expression; + +import org.eclipse.equinox.internal.p2.ql.CapabilityIndex; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * A function that creates a {@link CapabilityIndex} based on a collection of + * {@link IInstallableUnit} instances. + */ +final class CapabilityIndexFunction extends Function { + + public CapabilityIndexFunction(Expression[] operands) { + super(assertLength(operands, 1, 1, KEYWORD_CAPABILITY_INDEX)); + assertNotBoolean(operands[0], "parameter"); //$NON-NLS-1$ + } + + public Object evaluate(IEvaluationContext context) { + return new CapabilityIndex(operands[0].evaluateAsIterator(context)); + } + + String getOperator() { + return KEYWORD_CAPABILITY_INDEX; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/ClassFunction.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/ClassFunction.java new file mode 100644 index 000000000..5fe40c0ac --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/ClassFunction.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * 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.ql.expression; + +/** + * A function that obtains a class based on a String + */ +final class ClassFunction extends Function { + + public ClassFunction(Expression[] operands) { + super(assertLength(operands, 1, 1, KEYWORD_CLASS)); + assertNotBoolean(operands[0], "parameter"); //$NON-NLS-1$ + assertNotCollection(operands[0], "parameter"); //$NON-NLS-1$ + } + + boolean assertSingleArgumentClass(Object v) { + return v instanceof String; + } + + Object createInstance(Object arg) { + try { + return Class.forName((String) arg); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + String getOperator() { + return KEYWORD_CLASS; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Collect.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Collect.java new file mode 100644 index 000000000..1e858bcaf --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Collect.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.Iterator; +import org.eclipse.equinox.p2.ql.IEvaluationContext; +import org.eclipse.equinox.p2.ql.IExpression; + +/** + */ +final class Collect extends CollectionFilter { + final class CollectIterator implements Iterator { + private final IEvaluationContext context; + + private final IExpression variable; + + private final Iterator innerIterator; + + public CollectIterator(IEvaluationContext context, Iterator iterator) { + this.context = context; + this.variable = lambda.getItemVariable(); + this.innerIterator = iterator; + } + + public boolean hasNext() { + return innerIterator.hasNext(); + } + + public Object next() { + context.setValue(variable, innerIterator.next()); + return lambda.evaluate(context); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + Collect(Expression collection, LambdaExpression lambda) { + super(collection, lambda); + } + + Object evaluate(IEvaluationContext context, Iterator itor) { + return evaluateAsIterator(context, itor); + } + + Iterator evaluateAsIterator(IEvaluationContext context, Iterator itor) { + return new CollectIterator(context, itor); + } + + public int getExpressionType() { + return TYPE_COLLECT; + } + + String getOperator() { + return KEYWORD_COLLECT; + } + + boolean isCollection() { + return true; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/CollectionFilter.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/CollectionFilter.java new file mode 100644 index 000000000..325f43f74 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/CollectionFilter.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.Iterator; +import org.eclipse.equinox.p2.ql.IEvaluationContext; +import org.eclipse.equinox.p2.ql.IExpressionVisitor; + +/** + * Some kind of operation that is performed for each element of a collection. I.e. + * <code>x.<operation>(y | <expression&rt;)</code> + */ +abstract class CollectionFilter extends Unary { + static void appendProlog(StringBuffer bld, Expression lhs, String operator) { + if (lhs != Variable.EVERYTHING && lhs != Variable.ITEM) { + appendOperand(bld, lhs, PRIORITY_COLLECTION); + bld.append('.'); + } + bld.append(operator); + bld.append('('); + } + + final LambdaExpression lambda; + + CollectionFilter(Expression collection, LambdaExpression lambda) { + super(collection); + this.lambda = lambda; + } + + public boolean accept(IExpressionVisitor visitor) { + return super.accept(visitor) && lambda.accept(visitor); + } + + 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) { + appendProlog(bld, operand, getOperator()); + appendOperand(bld, lambda, PRIORITY_LAMBDA); + bld.append(')'); + } + + int countReferenceToEverything() { + return super.countReferenceToEverything() + lambda.countReferenceToEverything(); + } + + abstract Object evaluate(final IEvaluationContext context, Iterator iterator); + + Iterator evaluateAsIterator(IEvaluationContext context, Iterator iterator) { + throw new UnsupportedOperationException(); + } + + int getPriority() { + return PRIORITY_COLLECTION; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Compare.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Compare.java new file mode 100644 index 000000000..f82f37a57 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Compare.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * 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.ql.expression; + +import org.eclipse.equinox.internal.provisional.p2.metadata.Version; +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * Comparisons for magnitude. + */ +final class Compare extends Binary { + final boolean compareLess; + final boolean equalOK; + + Compare(Expression lhs, Expression rhs, boolean compareLess, boolean equalOK) { + super(lhs, rhs); + this.compareLess = compareLess; + this.equalOK = equalOK; + assertNotCollection(lhs, "lhs"); //$NON-NLS-1$ + assertNotCollection(rhs, "rhs"); //$NON-NLS-1$ + } + + public Object evaluate(IEvaluationContext context) { + Object lval = lhs.evaluate(context); + Object rval = rhs.evaluate(context); + if (lval == null || rval == null) + throw new IllegalArgumentException("Cannot compare null to anything"); //$NON-NLS-1$ + + try { + + if (lval.getClass() != rval.getClass()) { + if (lval instanceof Version && rval instanceof String) + rval = Version.create((String) rval); + else if (rval instanceof Version && lval instanceof String) + lval = Version.create((String) lval); + else if (lval instanceof String) + rval = rval.toString(); + else if (rval instanceof String) + lval = lval.toString(); + } + + if (lval instanceof Comparable) { + int cmpResult = ((Comparable) lval).compareTo(rval); + return Boolean.valueOf(cmpResult == 0 ? equalOK : (cmpResult < 0 ? compareLess : !compareLess)); + } + } catch (Exception e) { + // + } + throw new IllegalArgumentException("Cannot compare a " + lval.getClass().getName() + " to a " + rval.getClass().getName()); //$NON-NLS-1$//$NON-NLS-2$ + } + + public int getExpressionType() { + return compareLess ? (equalOK ? TYPE_LESS_EQUAL : TYPE_LESS) : (equalOK ? TYPE_GREATER_EQUAL : TYPE_GREATER); + } + + String getOperator() { + return compareLess ? (equalOK ? OPERATOR_LT_EQUAL : OPERATOR_LT) : (equalOK ? OPERATOR_GT_EQUAL : OPERATOR_GT); + } + + boolean isBoolean() { + return true; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Condition.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Condition.java new file mode 100644 index 000000000..543294be4 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Condition.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.ql.expression; + +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * Comparisons for magnitude. + */ +final class Condition extends Binary { + final Expression ifFalse; + + Condition(Expression test, Expression ifTrue, Expression ifFalse) { + super(test, ifTrue); + this.ifFalse = ifFalse; + assertNotCollection(test, "test"); //$NON-NLS-1$ + } + + public Object evaluate(IEvaluationContext context) { + return lhs.evaluate(context) == Boolean.TRUE ? rhs.evaluate(context) : ifFalse.evaluate(context); + } + + public int getExpressionType() { + return TYPE_CONDITION; + } + + public void toString(StringBuffer bld) { + super.toString(bld); + bld.append(' '); + bld.append(OPERATOR_ELSE); + bld.append(' '); + appendOperand(bld, ifFalse, getPriority()); + } + + String getOperator() { + return OPERATOR_IF; + } + + int getPriority() { + return PRIORITY_CONDITION; + } + + boolean isBoolean() { + return rhs.isBoolean() && ifFalse.isBoolean(); + } + + boolean isCollection() { + return rhs.isCollection() && ifFalse.isCollection(); + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Constant.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Constant.java new file mode 100644 index 000000000..fdb66aaa3 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Constant.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.*; +import org.eclipse.equinox.p2.ql.IEvaluationContext; +import org.eclipse.equinox.p2.ql.SimplePattern; + +/** + * An expression that represents a constant value. + */ +final class Constant extends Expression { + private final Object value; + + static final Constant NULL_CONSTANT = new Constant(null); + + static final Constant TRUE_CONSTANT = new Constant(Boolean.TRUE); + + static final Constant FALSE_CONSTANT = new Constant(Boolean.FALSE); + + static Constant 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 Constant(value); + } + + private Constant(Object value) { + this.value = value; + } + + public Object evaluate(IEvaluationContext context) { + return value; + } + + public int getExpressionType() { + return TYPE_LITERAL; + } + + public void toString(StringBuffer bld) { + if (value == null) + bld.append("null"); //$NON-NLS-1$ + else if (value instanceof String) { + String str = (String) value; + 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); + } + + int getPriority() { + return PRIORITY_LITERAL; + } + + 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); + } + + boolean isBoolean() { + return value instanceof Boolean; + } + + boolean isCollection() { + return value instanceof Collection || value instanceof Map || value instanceof Iterator || value != null && value.getClass().isArray(); + } + + String getOperator() { + return "<literal>"; //$NON-NLS-1$ + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/ContextExpression.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/ContextExpression.java new file mode 100644 index 000000000..f50b4043d --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/ContextExpression.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.Iterator; +import org.eclipse.equinox.internal.p2.ql.*; +import org.eclipse.equinox.p2.ql.*; + +/** + * The context expression is the top expression in context queries. It introduces the + * variable 'everything' and initialized it with the iterator that represents all + * available items. + */ +final class ContextExpression extends Unary implements IContextExpression { + + ContextExpression(Expression expression) { + super(expression); + } + + public void toString(StringBuffer bld) { + operand.toString(bld); + } + + public IEvaluationContext createContext(Class elementClass, Iterator iterator, Object[] params) { + IEvaluationContext context = new SingleVariableContext(new ParameterContext(params), Variable.EVERYTHING); + context.setValue(Variable.EVERYTHING, new Everything(elementClass, iterator, operand.needsRepeatedIterations())); + return context; + } + + public IEvaluationContext createContext(Class elementClass, Iterator iterator, Object[] params, ITranslationSupport ts) { + IEvaluationContext context = new MultiVariableContext(new ParameterContext(params), new IExpression[] {Variable.EVERYTHING, Variable.TRANSLATIONS}); + context.setValue(Variable.EVERYTHING, new Everything(elementClass, iterator, operand.needsRepeatedIterations())); + context.setValue(Variable.TRANSLATIONS, ts); + return context; + } + + public int getExpressionType() { + return operand.getExpressionType(); + } + + String getOperator() { + throw new UnsupportedOperationException(); + } + + int getPriority() { + return operand.getPriority(); + } + + Expression pipeFrom(Expression expr) { + return new ContextExpression(operand.pipeFrom(expr)); + } + + boolean isBoolean() { + return operand.isBoolean(); + } + + boolean isCollection() { + return operand.isCollection(); + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Equals.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Equals.java new file mode 100644 index 000000000..773fb75b4 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Equals.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * 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.ql.expression; + +import org.eclipse.equinox.p2.ql.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; + assertNotCollection(lhs, "lhs"); //$NON-NLS-1$ + assertNotCollection(rhs, "rhs"); //$NON-NLS-1$ + } + + public Object evaluate(IEvaluationContext context) { + Object lval = lhs.evaluate(context); + Object rval = rhs.evaluate(context); + boolean result; + if (lval == null || rval == null) + result = lval == rval; + else { + if (lval.getClass() != rval.getClass()) { + if (lval instanceof String) + rval = rval.toString(); + else if (rval instanceof String) + lval = lval.toString(); + } + result = lval.equals(rval); + } + if (negate) + result = !result; + return Boolean.valueOf(result); + } + + public int getExpressionType() { + return negate ? TYPE_NOT_EQUALS : TYPE_EQUALS; + } + + String getOperator() { + return negate ? OPERATOR_NOT_EQUALS : OPERATOR_EQUALS; + } + + boolean isBoolean() { + return true; + } + +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Exists.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Exists.java new file mode 100644 index 000000000..64a23d7f6 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Exists.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.Iterator; +import org.eclipse.equinox.p2.ql.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); + } + + public int getExpressionType() { + return TYPE_EXISTS; + } + + 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; + } + + String getOperator() { + return KEYWORD_EXISTS; + } + + boolean isBoolean() { + return true; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Expression.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Expression.java new file mode 100644 index 000000000..7992dda5b --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Expression.java @@ -0,0 +1,174 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.*; +import org.eclipse.equinox.internal.p2.ql.IRepeatableIterator; +import org.eclipse.equinox.internal.p2.ql.RepeatableIterator; +import org.eclipse.equinox.internal.p2.ql.parser.IParserConstants; +import org.eclipse.equinox.internal.provisional.p2.metadata.query.Collector; +import org.eclipse.equinox.p2.ql.*; + +/** + * The base class of the expression tree. + */ +abstract class Expression implements IExpression, IParserConstants { + + static final Expression[] emptyArray = new Expression[0]; + + static Set asSet(Object val, boolean forcePrivateCopy) { + if (val == null) + throw new IllegalArgumentException("Cannot convert null into an set"); //$NON-NLS-1$ + + if (val instanceof IRepeatableIterator) { + Object provider = ((IRepeatableIterator) val).getIteratorProvider(); + if (!forcePrivateCopy) { + if (provider instanceof Set) + return (Set) provider; + if (provider instanceof Collector) + return (Set) ((Collector) provider).toCollection(); + } + + if (provider instanceof Collection) + val = provider; + } else { + if (!forcePrivateCopy) { + if (val instanceof Set) + return (Set) val; + if (val instanceof Collector) + return (Set) ((Collector) val).toCollection(); + } + } + + HashSet result; + if (val instanceof Collection) + result = new HashSet((Collection) val); + else { + result = new HashSet(); + Iterator iterator = RepeatableIterator.create(val); + while (iterator.hasNext()) + result.add(iterator.next()); + } + return result; + } + + /** + * 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.accept(this); + } + + /** + * 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; + } + + /** + * Checks if the expression will make repeated requests for the 'everything' iterator. + * @return <code>true</code> if repeated requests will be made, <code>false</code> if not. + */ + public boolean needsRepeatedIterations() { + return countReferenceToEverything() > 1; + } + + public final boolean needsTranslations() { + final boolean[] translationSupportNeeded = new boolean[] {false}; + accept(new IExpressionVisitor() { + public boolean accept(IExpression expr) { + if (((Expression) expr).isReferencingTranslations()) { + translationSupportNeeded[0] = true; + return false; + } + return true; + } + }); + return translationSupportNeeded[0]; + } + + public String toString() { + StringBuffer bld = new StringBuffer(); + toString(bld); + return bld.toString(); + } + + public abstract void toString(StringBuffer bld); + + static void appendOperand(StringBuffer bld, Expression operand, int priority) { + if (priority < operand.getPriority()) { + bld.append('('); + operand.toString(bld); + bld.append(')'); + } else + operand.toString(bld); + } + + void assertNotCollection(Expression expr, String usage) { + if (expr.isCollection()) + throw new IllegalArgumentException("A collection cannot be used as " + usage + " in a " + getOperator()); //$NON-NLS-1$//$NON-NLS-2$ + } + + void assertNotBoolean(Expression expr, String usage) { + if (expr.isBoolean()) + throw new IllegalArgumentException("A boolean cannot be used as " + usage + " in a " + getOperator()); //$NON-NLS-1$//$NON-NLS-2$ + } + + int countReferenceToEverything() { + return 0; + } + + abstract String getOperator(); + + abstract int getPriority(); + + boolean isBoolean() { + return false; + } + + boolean isCollection() { + return false; + } + + boolean isElementBoolean() { + return false; + } + + boolean isReferencingTranslations() { + return false; + } + + boolean isPipeable() { + // TODO Auto-generated method stub + return false; + } + + Expression pipeFrom(Expression nxt) { + // TODO Auto-generated method stub + return null; + } + + boolean isElementCollection() { + // TODO Auto-generated method stub + return false; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/ExpressionFactory.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/ExpressionFactory.java new file mode 100644 index 000000000..e6dac860d --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/ExpressionFactory.java @@ -0,0 +1,228 @@ +package org.eclipse.equinox.internal.p2.ql.expression; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import org.eclipse.equinox.internal.p2.ql.parser.IParserConstants; +import org.eclipse.equinox.p2.ql.*; + +public class ExpressionFactory implements IExpressionFactory, IParserConstants { + public static final IExpressionFactory INSTANCE = new ExpressionFactory(); + + private static final Map functionMap; + + static { + Class[] args = new Class[] {Expression[].class}; + Map f = new HashMap(); + try { + f.put(KEYWORD_BOOLEAN, BooleanFunction.class.getConstructor(args)); + f.put(KEYWORD_FILTER, FilterFunction.class.getConstructor(args)); + f.put(KEYWORD_VERSION, VersionFunction.class.getConstructor(args)); + f.put(KEYWORD_RANGE, RangeFunction.class.getConstructor(args)); + f.put(KEYWORD_SET, SetFunction.class.getConstructor(args)); + f.put(KEYWORD_CLASS, ClassFunction.class.getConstructor(args)); + f.put(KEYWORD_IQUERY, WrappedIQuery.class.getConstructor(args)); + f.put(KEYWORD_CAPABILITY_INDEX, CapabilityIndexFunction.class.getConstructor(args)); + functionMap = Collections.unmodifiableMap(f); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + private static Expression[] convertArray(IExpression[] operands) { + Expression[] ops = new Expression[operands.length]; + System.arraycopy(operands, 0, ops, 0, operands.length); + return ops; + } + + private 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 array(IExpression[] operands) { + return new Array(convertArray(operands)); + } + + public IExpression assignment(IExpression variable, IExpression expression) { + return new Assignment((Variable) variable, (Expression) expression); + } + + public IExpression at(IExpression target, IExpression key) { + return new At((Expression) target, (Expression) key); + } + + public IExpression collect(IExpression collection, IExpression lambda) { + return new Collect((Expression) collection, (LambdaExpression) lambda); + } + + public IExpression condition(IExpression test, IExpression ifTrue, IExpression ifFalse) { + return new Condition((Expression) test, (Expression) ifTrue, (Expression) ifFalse); + } + + public IExpression constant(Object value) { + return Constant.create(value); + } + + public IContextExpression contextExpression(IExpression expr) { + return new ContextExpression((Expression) expr); + } + + public IExpression dynamicMember(IExpression target, String name) { + return new Member.DynamicMember((Expression) target, name); + } + + 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 first(IExpression collection, IExpression lambda) { + return new First((Expression) collection, (LambdaExpression) lambda); + } + + public IExpression flatten(IExpression collection) { + return new Flatten((Expression) collection); + } + + public IExpression function(Object function, IExpression[] args) { + try { + return (IExpression) ((Constructor) function).newInstance(new Object[] {convertArray(args)}); + } catch (IllegalArgumentException e) { + throw e; + } catch (InvocationTargetException e) { + Throwable t = e.getCause(); + if (t instanceof RuntimeException) + throw (RuntimeException) t; + throw new RuntimeException(t); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public Map getFunctionMap() { + return functionMap; + } + + public IExpression greater(IExpression lhs, IExpression rhs) { + return new Compare((Expression) lhs, (Expression) rhs, false, false); + } + + public IExpression indexedParameter(int index) { + return new Parameter.Indexed(index); + } + + public IExpression keyedParameter(String key) { + return new Parameter.Keyed(key); + } + + public IExpression lambda(IExpression variable, IExpression body) { + return new LambdaExpression((Variable) variable, (Expression) body); + } + + public IExpression lambda(IExpression variable, IExpression body, IExpression[] assignments) { + Assignment[] asgns = new Assignment[assignments.length]; + System.arraycopy(assignments, 0, asgns, 0, assignments.length); + return new LambdaExpression((Variable) variable, (Expression) body, asgns); + } + + public IExpression latest(IExpression collection) { + return new Latest((Expression) collection); + } + + public IExpression less(IExpression lhs, IExpression rhs) { + return new Compare((Expression) lhs, (Expression) rhs, true, false); + } + + public IExpression limit(IExpression collection, IExpression limit) { + return new Limit((Expression) collection, (Expression) limit); + } + + public IExpression matches(IExpression lhs, IExpression rhs) { + return new Matches((Expression) lhs, (Expression) rhs); + } + + public IMatchExpression matchExpression(IExpression expr) { + return new MatchExpression((Expression) expr); + } + + public IExpression member(IExpression target, String name) { + return new Member.DynamicMember((Expression) target, name); + } + + public IExpression memberCall(IExpression target, String name, IExpression[] args) { + if (args.length == 0) + return member(target, name); + + Expression[] eargs = convertArray(args); + if (KEYWORD_SATISFIES_ANY.equals(name)) + return new Member.CapabilityIndex_satisfiesAny((Expression) target, eargs); + if (KEYWORD_SATISFIES_ALL.equals(name)) + return new Member.CapabilityIndex_satisfiesAll((Expression) target, eargs); + + StringBuffer bld = new StringBuffer(); + bld.append("Don't know how to do a member call with "); //$NON-NLS-1$ + bld.append(name); + bld.append('('); + Array.elementsToString(bld, eargs); + bld.append(')'); + throw new IllegalArgumentException(bld.toString()); + } + + 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 pipe(IExpression[] operands) { + if (operands.length == 0) + return null; + + Expression pipe = (Expression) operands[0]; + for (int idx = 1; idx < operands.length; ++idx) + pipe = ((Expression) operands[idx]).pipeFrom(pipe); + return pipe; + } + + public IExpression select(IExpression collection, IExpression lambda) { + return new Select((Expression) collection, (LambdaExpression) lambda); + } + + public IExpression traverse(IExpression collection, IExpression lambda) { + return new Traverse((Expression) collection, (LambdaExpression) lambda); + } + + public IExpression unique(IExpression collection, IExpression cache) { + return new Unique((Expression) collection, (Expression) cache); + } + + public IExpression variable(String name) { + return Variable.create(name); + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/FilterFunction.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/FilterFunction.java new file mode 100644 index 000000000..5f0130c36 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/FilterFunction.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * 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.ql.expression; + +import org.eclipse.equinox.internal.p2.metadata.LDAPQuery; +import org.eclipse.equinox.internal.p2.ql.QLActivator; +import org.osgi.framework.InvalidSyntaxException; + +/** + * A function that creates an OSGi filter based on a String + */ +final class FilterFunction extends Function { + public FilterFunction(Expression[] operands) { + super(assertLength(operands, 1, 1, KEYWORD_FILTER)); + assertNotBoolean(operands[0], "parameter"); //$NON-NLS-1$ + assertNotCollection(operands[0], "parameter"); //$NON-NLS-1$ + } + + boolean assertSingleArgumentClass(Object v) { + return v instanceof LDAPQuery || v instanceof String; + } + + Object createInstance(Object arg) { + String str = (arg instanceof LDAPQuery) ? ((LDAPQuery) arg).getFilter() : (String) arg; + try { + return QLActivator.context.createFilter(str); + } catch (InvalidSyntaxException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + String getOperator() { + return KEYWORD_FILTER; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/First.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/First.java new file mode 100644 index 000000000..ee20a1466 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/First.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.Iterator; +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * A collection filter that yields the first element of the <code>collection</code> for which + * the <code>filter</code> yields <code>true</code> + */ +final class First extends CollectionFilter { + First(Expression collection, LambdaExpression lambda) { + super(collection, lambda); + } + + public int getExpressionType() { + return TYPE_FIRST; + } + + Object evaluate(IEvaluationContext context, Iterator itor) { + Variable variable = lambda.getItemVariable(); + while (itor.hasNext()) { + Object each = itor.next(); + variable.setValue(context, each); + if (lambda.evaluate(context) == Boolean.TRUE) + return each; + } + return null; + } + + String getOperator() { + return KEYWORD_FIRST; + } + + boolean isBoolean() { + return operand.isElementBoolean(); + } + + boolean isCollection() { + return operand.isElementCollection(); + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Flatten.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Flatten.java new file mode 100644 index 000000000..2aaf288ed --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Flatten.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.Iterator; +import org.eclipse.equinox.internal.p2.ql.FlattenIterator; +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * An expression that yields a new collection consisting of all elements of the + * <code>collection</code> for which the <code>filter</code> yields <code>true</code>. + */ +final class Flatten extends UnaryCollectionFilter { + Flatten(Expression collection) { + super(collection); + } + + public Iterator evaluateAsIterator(IEvaluationContext context) { + return new FlattenIterator(operand.evaluateAsIterator(context)); + } + + public int getExpressionType() { + return TYPE_FLATTEN; + } + + String getOperator() { + return KEYWORD_FLATTEN; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Function.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Function.java new file mode 100644 index 000000000..5497dca2b --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Function.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * 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.ql.expression; + +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * An expression that represents a function such as filter(<expr>) or version(<expr>) + */ +abstract class Function extends NAry { + + private Object instance; + + Function(Expression[] operands) { + super(operands); + } + + public Object evaluate(IEvaluationContext context) { + if (instance != null) + return instance; + + Expression operand = operands[0]; + Object arg = operand.evaluate(context); + if (assertSingleArgumentClass(arg)) { + Object result = createInstance(arg); + if (operand instanceof Constant || operand instanceof Parameter) + // operand won't change over time so we can cache this instance. + instance = result; + return result; + } + String what = arg == null ? "null" : ("a " + arg.getClass().getName()); //$NON-NLS-1$ //$NON-NLS-2$ + throw new IllegalArgumentException("Cannot create a " + getOperator() + " from " + what); //$NON-NLS-1$ //$NON-NLS-2$ + } + + boolean assertSingleArgumentClass(Object v) { + return true; + } + + public int getExpressionType() { + return TYPE_FUNCTION; + } + + public void toString(StringBuffer bld) { + bld.append(getOperator()); + bld.append('('); + Array.elementsToString(bld, operands); + bld.append(')'); + } + + final Object createInstance(String arg) { + throw new UnsupportedOperationException(); + } + + Object createInstance(Object arg) { + throw new UnsupportedOperationException(); + } + + int getPriority() { + return PRIORITY_CONSTRUCTOR; + } + + boolean isBoolean() { + return false; + } + + boolean isCollection() { + return false; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/LambdaExpression.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/LambdaExpression.java new file mode 100644 index 000000000..cdc1d7216 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/LambdaExpression.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * 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.ql.expression; + +import org.eclipse.equinox.internal.p2.ql.MultiVariableContext; +import org.eclipse.equinox.internal.p2.ql.SingleVariableContext; +import org.eclipse.equinox.p2.ql.IEvaluationContext; +import org.eclipse.equinox.p2.ql.IExpressionVisitor; + +/** + * A function that executes some code + */ +final class LambdaExpression extends Unary { + private static final Assignment[] emptyAssignmentArray = new Assignment[0]; + private final Assignment[] assignments; + private final Variable each; + + LambdaExpression(Variable each, Expression body, Assignment[] assignments) { + super(body); + this.each = each; + if (assignments == null) + assignments = emptyAssignmentArray; + this.assignments = assignments; + } + + LambdaExpression(Variable variable, Expression body) { + this(variable, body, null); + } + + public boolean accept(IExpressionVisitor visitor) { + if (super.accept(visitor) && each.accept(visitor)) { + for (int idx = 0; idx < assignments.length; ++idx) + if (!assignments[idx].accept(visitor)) + return false; + return true; + } + return false; + } + + public int getExpressionType() { + return TYPE_LAMBDA; + } + + public void toString(StringBuffer bld) { + int top = assignments.length; + if (top > 0) { + for (int idx = 0; idx < top; ++idx) { + appendOperand(bld, assignments[idx].rhs, PRIORITY_COMMA); + bld.append(", "); //$NON-NLS-1$ + } + bld.append(OPERATOR_EACH); + bld.append(", {"); //$NON-NLS-1$ + for (int idx = 0; idx < top; ++idx) { + appendOperand(bld, assignments[idx].lhs, PRIORITY_COMMA); + bld.append(", "); //$NON-NLS-1$ + } + } + each.toString(bld); + bld.append(" | "); //$NON-NLS-1$ + appendOperand(bld, operand, PRIORITY_COMMA); + if (top > 0) + bld.append('}'); + } + + int countReferenceToEverything() { + if (super.countReferenceToEverything() > 0) + return 2; + for (int idx = 0; idx < assignments.length; ++idx) + if (assignments[idx].countReferenceToEverything() > 0) + return 2; + return 0; + } + + Variable getItemVariable() { + return each; + } + + String getOperator() { + return "|"; //$NON-NLS-1$ + } + + int getPriority() { + return PRIORITY_LAMBDA; + } + + boolean isBoolean() { + return operand.isBoolean(); + } + + boolean isCollection() { + return operand.isCollection(); + } + + IEvaluationContext prolog(IEvaluationContext context) { + IEvaluationContext lambdaContext = new SingleVariableContext(context, each); + int top = assignments.length; + if (top > 0) { + if (top == 1) { + Assignment v = assignments[0]; + lambdaContext = new SingleVariableContext(lambdaContext, v.lhs); + lambdaContext.setValue(v.lhs, v.rhs.evaluate(context)); + } else { + Variable[] vars = new Variable[top]; + for (int idx = 0; idx < top; ++idx) + vars[idx] = (Variable) assignments[idx].lhs; + lambdaContext = new MultiVariableContext(lambdaContext, vars); + for (int idx = 0; idx < top; ++idx) + lambdaContext.setValue(vars[idx], assignments[idx].rhs.evaluate(context)); + } + } + return lambdaContext; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Latest.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Latest.java new file mode 100644 index 000000000..d4ddc0032 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Latest.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.*; +import org.eclipse.equinox.p2.metadata.IVersionedId; +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * An expression that is especially targeted towards {@link IVersionedId} instances. It will + * reject any objects that is not an <code>IVersionedId</code> and it will ensure that the + * resulting iterator only iterates over the latest version of any found id. + */ +final class Latest extends UnaryCollectionFilter { + + Latest(Expression collection) { + super(collection); + } + + public Iterator evaluateAsIterator(IEvaluationContext context) { + HashMap greatestIUVersion; + if (operand instanceof Select) { + // Inline element evaluation here so that we don't build a map that is + // larger then it has to be + Select select = (Select) operand; + Iterator iterator = select.operand.evaluateAsIterator(context); + if (!iterator.hasNext()) + return Collections.EMPTY_SET.iterator(); + + greatestIUVersion = new HashMap(); + LambdaExpression lambda = select.lambda; + context = lambda.prolog(context); + Variable variable = lambda.getItemVariable(); + while (iterator.hasNext()) { + Object next = iterator.next(); + if (!(next instanceof IVersionedId)) + continue; + + variable.setValue(context, next); + if (lambda.evaluate(context) != Boolean.TRUE) + continue; + + IVersionedId versionedID = (IVersionedId) next; + String id = versionedID.getId(); + IVersionedId prev = (IVersionedId) greatestIUVersion.put(id, versionedID); + if (prev == null) + continue; + if (prev.getVersion().compareTo(versionedID.getVersion()) > 0) + greatestIUVersion.put(id, prev); + } + } else { + Iterator iterator = operand.evaluateAsIterator(context); + if (iterator == null) + return null; + if (!iterator.hasNext()) + return Collections.EMPTY_SET.iterator(); + + greatestIUVersion = new HashMap(); + while (iterator.hasNext()) { + Object next = iterator.next(); + if (!(next instanceof IVersionedId)) + continue; + + IVersionedId versionedID = (IVersionedId) next; + String id = versionedID.getId(); + + IVersionedId prev = (IVersionedId) greatestIUVersion.put(id, versionedID); + if (prev == null) + continue; + + if (prev.getVersion().compareTo(versionedID.getVersion()) > 0) + greatestIUVersion.put(id, prev); + } + } + return greatestIUVersion.values().iterator(); + } + + public int getExpressionType() { + return TYPE_LATEST; + } + + String getOperator() { + return KEYWORD_LATEST; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Limit.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Limit.java new file mode 100644 index 000000000..5c0ae42a3 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Limit.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.ql.expression; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * A collection filter that limits the number of entries in the collection + */ +final class Limit extends Binary { + + /** + * An iterator that stops iterating after a given number of iterations. + */ + static final class CountingIterator implements Iterator { + private final Iterator innerIterator; + private int counter; + + public CountingIterator(Iterator iterator, int count) { + this.innerIterator = iterator; + this.counter = count; + } + + public boolean hasNext() { + return counter > 0 && innerIterator.hasNext(); + } + + public Object next() { + if (counter > 0) { + --counter; + return innerIterator.next(); + } + throw new NoSuchElementException(); + } + + public void remove() { + innerIterator.remove(); + } + } + + Limit(Expression operand, Expression param) { + super(operand, param); + assertNotBoolean(operand, "operand"); //$NON-NLS-1$ + assertNotCollection(param, "parameter"); //$NON-NLS-1$ + } + + Limit(Expression operand, int limit) { + this(operand, Constant.create(new Integer(limit))); + } + + public Object evaluate(IEvaluationContext context) { + Object rval = rhs.evaluate(context); + int limit = -1; + if (rval instanceof Integer) + limit = ((Integer) rval).intValue(); + if (limit <= 0) + throw new IllegalArgumentException("limit expression did not evalutate to a positive integer"); //$NON-NLS-1$ + return new CountingIterator(lhs.evaluateAsIterator(context), limit); + } + + public int getExpressionType() { + return TYPE_LIMIT; + } + + public void toString(StringBuffer bld) { + CollectionFilter.appendProlog(bld, lhs, getOperator()); + appendOperand(bld, rhs, PRIORITY_COMMA); + bld.append(')'); + } + + String getOperator() { + return KEYWORD_LIMIT; + } + + int getPriority() { + return PRIORITY_COLLECTION; + } + + boolean isCollection() { + return true; + } + + boolean isElementBoolean() { + return lhs.isElementBoolean(); + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/MatchExpression.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/MatchExpression.java new file mode 100644 index 000000000..dadd153bb --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/MatchExpression.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.ql.expression; + +import org.eclipse.equinox.internal.p2.ql.*; +import org.eclipse.equinox.p2.ql.*; + +/** + * The match expression is the top expression in item queries. It introduces the + * variable 'item' and initializes it with the item to match. + */ +final class MatchExpression extends Unary implements IMatchExpression { + + MatchExpression(Expression expression) { + super(expression); + } + + public boolean isMatch(IEvaluationContext context, Object value) { + Variable.ITEM.setValue(context, value); + return operand.evaluate(context) == Boolean.TRUE; + } + + public IEvaluationContext createContext(Object[] params) { + return new SingleVariableContext(new ParameterContext(params), Variable.ITEM); + } + + public IEvaluationContext createContext(Object[] params, ITranslationSupport ts) { + IEvaluationContext context = new MultiVariableContext(new ParameterContext(params), new IExpression[] {Variable.ITEM, Variable.TRANSLATIONS}); + context.setValue(Variable.TRANSLATIONS, ts); + return context; + } + + public int getExpressionType() { + return operand.getExpressionType(); + } + + public void toString(StringBuffer bld) { + operand.toString(bld); + } + + protected int getPriority() { + return operand.getPriority(); + } + + String getOperator() { + throw new UnsupportedOperationException(); + } + + Expression pipeFrom(Expression expr) { + return new MatchExpression(operand.pipeFrom(expr)); + } + + boolean isBoolean() { + return true; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Matches.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Matches.java new file mode 100644 index 000000000..b36aa1666 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Matches.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.*; +import org.eclipse.equinox.internal.p2.metadata.IRequiredCapability; +import org.eclipse.equinox.internal.provisional.p2.metadata.*; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.ql.IEvaluationContext; +import org.eclipse.equinox.p2.ql.SimplePattern; +import org.osgi.framework.Filter; + +/** + * <p>A class that performs "matching" 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>IProvidedCapability</td><td>IRequiredCapability</td><td>lhs.satisfies(rhs)</td></tr> + * <tr><td>IInstallableUnit</td><td>IRequiredCapability</td><td>lhs.satisfies(rhs)</td></tr> + * <tr><td>Version</td><td>VersionRange</td><td>rhs.isIncluded(lhs)</td></tr> + * <tr><td>IInstallableUnit</td><td>Filter</td><td>rhs.matches(lhs.properties)</td></tr> + * <tr><td>Map</td><td>Filter</td><td>rhs.match(lhs)</td></tr> + * <tr><td>String</td><td>Pattern</td><td>rhs.matcher(lhs).matches()</td></tr> + * <tr><td><any></td><td>Class</td><td>rhs.isInstance(lhs)</td></tr> + * <tr><td>Class</td><td>Class</td><td>rhs.isAssignableFrom(lhs)</td></tr> + * </table> + */ +final class Matches extends Binary { + Matches(Expression lhs, Expression rhs) { + super(lhs, rhs); + assertNotBoolean(lhs, "lhs"); //$NON-NLS-1$ + assertNotBoolean(rhs, "rhs"); //$NON-NLS-1$ + assertNotCollection(rhs, "rhs"); //$NON-NLS-1$ + } + + public Object evaluate(IEvaluationContext context) { + Object lval = lhs.evaluate(context); + Object rval = rhs.evaluate(context); + + if (rval instanceof IRequiredCapability) { + IRequiredCapability cap = (IRequiredCapability) rval; + if (lval instanceof IInstallableUnit) + return Boolean.valueOf(((IInstallableUnit) lval).satisfies(cap)); + if (lval instanceof IProvidedCapability) + return Boolean.valueOf(cap.satisfiedBy((IProvidedCapability) lval)); + + } else if (rval instanceof VersionRange) { + if (lval instanceof Version) + return Boolean.valueOf(((VersionRange) rval).isIncluded((Version) lval)); + + } else if (rval instanceof SimplePattern) { + if (lval instanceof CharSequence) + return Boolean.valueOf(((SimplePattern) rval).isMatch((CharSequence) lval)); + + } else if (rval instanceof IUpdateDescriptor) { + if (lval instanceof IInstallableUnit) + return Boolean.valueOf(((IUpdateDescriptor) rval).isUpdateOf((IInstallableUnit) lval)); + + } else if (rval instanceof Filter) { + if (lval instanceof IInstallableUnit) + return Boolean.valueOf(((Filter) rval).match(new Hashtable(((IInstallableUnit) lval).getProperties()))); + if (lval instanceof Dictionary) + return Boolean.valueOf(((Filter) rval).match((Dictionary) lval)); + if (lval instanceof Map) + return Boolean.valueOf(((Filter) rval).match(new Hashtable((Map) lval))); + + } else if (rval instanceof Locale) { + if (lval instanceof String) + return Boolean.valueOf(matchLocaleVariants((Locale) rval, (String) lval)); + + } else if (rval instanceof Class) { + Class rclass = (Class) rval; + return Boolean.valueOf(lval instanceof Class ? rclass.isAssignableFrom((Class) lval) : rclass.isInstance(lval)); + } + + if (lval == null || rval == null) + return Boolean.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; + } + + private static boolean equals(String a, String b, int startPos, int endPos) { + if (endPos - startPos != b.length()) + return false; + + int bidx = 0; + while (startPos < endPos) + if (a.charAt(startPos++) != b.charAt(bidx++)) + return false; + return true; + } + + private static boolean matchLocaleVariants(Locale rval, String lval) { + int uscore = lval.indexOf('_'); + if (uscore < 0) + // No country and no variant. Just match language + return lval.equals(rval.getLanguage()); + + if (!equals(lval, rval.getLanguage(), 0, uscore)) + // Language part doesn't match. Give up. + return false; + + // Check country and variant + int countryStart = uscore + 1; + uscore = lval.indexOf('_', countryStart); + return uscore < 0 ? equals(lval, rval.getCountry(), countryStart, lval.length()) // + : equals(lval, rval.getCountry(), countryStart, uscore) && equals(lval, rval.getVariant(), uscore + 1, lval.length()); + } + + String getOperator() { + return OPERATOR_MATCHES; + } + + boolean isBoolean() { + return true; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Member.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Member.java new file mode 100644 index 000000000..ff8efbc88 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Member.java @@ -0,0 +1,194 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.lang.reflect.*; +import java.util.Iterator; +import org.eclipse.equinox.p2.ql.*; + +/** + * <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 "value" 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> + */ +abstract class Member extends Unary { + + static Member createDynamicMember(Expression operand, String name) { + return new DynamicMember(operand, name); + } + + static final Object[] NO_ARGS = new Object[0]; + + final String name; + final Expression[] argExpressions; + + static abstract class CapabilityIndexMethod extends Member { + public CapabilityIndexMethod(Expression operand, String name, Expression[] args) { + super(operand, name, args); + } + + final ICapabilityIndex getSelf(IEvaluationContext context) { + Object self = operand.evaluate(context); + if (self instanceof ICapabilityIndex) + return (ICapabilityIndex) self; + throw new IllegalArgumentException("lhs of member expected to be an ICapabilityIndex implementation"); //$NON-NLS-1$ + } + + public final Object evaluate(IEvaluationContext context) { + return evaluateAsIterator(context); + } + + boolean isCollection() { + return true; + } + } + + static final class CapabilityIndex_satisfiesAny extends CapabilityIndexMethod { + + public CapabilityIndex_satisfiesAny(Expression operand, Expression[] argExpressions) { + super(operand, KEYWORD_SATISFIES_ANY, NAry.assertLength(argExpressions, 1, 1, KEYWORD_SATISFIES_ANY)); + } + + public Iterator evaluateAsIterator(IEvaluationContext context) { + return getSelf(context).satisfiesAny(argExpressions[0].evaluateAsIterator(context)); + } + } + + static final class CapabilityIndex_satisfiesAll extends CapabilityIndexMethod { + + public CapabilityIndex_satisfiesAll(Expression operand, Expression[] argExpressions) { + super(operand, KEYWORD_SATISFIES_ALL, NAry.assertLength(argExpressions, 1, 1, KEYWORD_SATISFIES_ALL)); + } + + public Iterator evaluateAsIterator(IEvaluationContext context) { + return getSelf(context).satisfiesAll(argExpressions[0].evaluateAsIterator(context)); + } + } + + 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; + } + + Member(Expression operand, String name, Expression[] args) { + super(operand); + this.name = name; + this.argExpressions = args; + } + + public int getExpressionType() { + return TYPE_MEMBER; + } + + public void toString(StringBuffer bld) { + if (operand == Variable.ITEM || operand == Variable.EVERYTHING) + bld.append(name); + else { + appendOperand(bld, operand, getPriority()); + bld.append('.'); + bld.append(name); + } + if (argExpressions.length > 0) { + bld.append('('); + Array.elementsToString(bld, argExpressions); + bld.append(')'); + } + } + + String getOperator() { + return OPERATOR_MEMBER; + } + + int getPriority() { + return PRIORITY_MEMBER; + } + + static final class DynamicMember extends Member { + private static final Class[] NO_ARG_TYPES = new Class[0]; + private static final String GET_PREFIX = "get"; //$NON-NLS-1$ + private static final String IS_PREFIX = "is"; //$NON-NLS-1$ + + 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; + } + + private Class lastClass; + private Method method; + private String methodName; + + public Object evaluate(IEvaluationContext context) { + return invoke(operand.evaluate(context)); + } + + boolean isReferencingTranslations() { + return VARIABLE_TRANSLATIONS.equals(name); + } + + Object invoke(Object self) { + if (self == null) + throw new IllegalArgumentException("Cannot access member " + name + " in null"); //$NON-NLS-1$//$NON-NLS-2$ + + 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$ + } + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/NAry.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/NAry.java new file mode 100644 index 000000000..22595deb7 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/NAry.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * 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.ql.expression; + +import org.eclipse.equinox.p2.ql.IExpressionVisitor; + +/** + * The abstract baseclass for all N-ary expressions + * + */ +abstract class NAry extends Expression { + 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; + } + + 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; + } + + final Expression[] operands; + + 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 void toString(StringBuffer bld) { + appendOperand(bld, operands[0], getPriority()); + for (int idx = 1; idx < operands.length; ++idx) { + bld.append(' '); + bld.append(getOperator()); + bld.append(' '); + appendOperand(bld, operands[idx], getPriority()); + } + } + + int countReferenceToEverything() { + int count = 0; + for (int idx = 0; count < 2 && idx < operands.length; ++idx) + count += operands[idx].countReferenceToEverything(); + return count; + } + + abstract String getOperator(); + + boolean isBoolean() { + int idx = operands.length; + while (--idx >= 0) + if (!operands[idx].isBoolean()) + return false; + return true; + } + + boolean isCollection() { + int idx = operands.length; + while (--idx >= 0) + if (!operands[idx].isCollection()) + return false; + return true; + } + +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Not.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Not.java new file mode 100644 index 000000000..ab7b31f53 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Not.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.ql.expression; + +import org.eclipse.equinox.p2.ql.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; + } + + String getOperator() { + return OPERATOR_NOT; + } + + int getPriority() { + return PRIORITY_NOT; + } + + boolean isBoolean() { + return true; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Or.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Or.java new file mode 100644 index 000000000..c4fad533a --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Or.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.Iterator; +import java.util.Set; +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * n-ary OR operator. This operator evaluates its first operand and then checks + * the class of the result. If the class is {@link Boolean} then it is assumed + * that all other operands also evaluates to a boolean and the full evaluation is + * <code>false</code> if none of its operands evaluate to <code>true</code>. If the + * first result was not of class {@link Boolean}, then it is assumed that it can + * be accessed as an {@link Iterator} and that all other operands also evaluates to + * something that can be accessed an {@link Iterator}. The OR operator will then + * function as a UNION operator and the result is the unique sum of all elements that + * were found in all operands. + */ +final class Or extends NAry { + public Or(Expression[] operands) { + super(assertLength(operands, 2, OPERATOR_OR)); + } + + public Object evaluate(IEvaluationContext context) { + Object firstValue = operands[0].evaluate(context); + + // Determine operation mode + if (firstValue instanceof Boolean) { + // The first value was boolean. Assume that the rest are too + if (((Boolean) firstValue).booleanValue()) + return Boolean.TRUE; + + for (int idx = 1; idx < operands.length; ++idx) { + if (operands[idx].evaluate(context) == Boolean.TRUE) + return Boolean.TRUE; + } + return Boolean.FALSE; + } + + // Not a boolean. Assume that we can use an iterator on all values + Set resultSet = asSet(firstValue, true); + for (int idx = 1; idx < operands.length; ++idx) { + Iterator itor = operands[idx].evaluateAsIterator(context); + while (itor.hasNext()) + resultSet.add(itor.next()); + } + return resultSet; + } + + public int getExpressionType() { + return TYPE_OR; + } + + String getOperator() { + return OPERATOR_OR; + } + + int getPriority() { + return PRIORITY_OR; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Parameter.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Parameter.java new file mode 100644 index 000000000..c6374ea2e --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/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.ql.expression; + +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * The abstract base class for the indexed and keyed parameters + */ +abstract class Parameter extends Expression { + public int getExpressionType() { + return TYPE_PARAMETER; + } + + int getPriority() { + return PRIORITY_PARAMETER; + } + + String getOperator() { + return OPERATOR_PARAMETER; + } + + static final class Indexed extends Parameter { + final int position; + + Indexed(int position) { + this.position = position; + } + + public Object evaluate(IEvaluationContext context) { + return context.getParameter(position); + } + + public void toString(StringBuffer bld) { + bld.append('$'); + bld.append(position); + } + } + + static final class Keyed extends Parameter { + final String key; + + public Keyed(String key) { + this.key = key; + } + + public Object evaluate(IEvaluationContext context) { + return context.getParameter(key); + } + + public void toString(StringBuffer bld) { + bld.append('$'); + bld.append(key); + } + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/RangeFunction.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/RangeFunction.java new file mode 100644 index 000000000..a749ac8cf --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/RangeFunction.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * 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.ql.expression; + +import org.eclipse.equinox.internal.provisional.p2.metadata.VersionRange; + +/** + * A function that creates a {@link VersionRange} from a String + */ +final class RangeFunction extends Function { + + public RangeFunction(Expression[] operands) { + super(assertLength(operands, 1, 1, KEYWORD_RANGE)); + assertNotBoolean(operands[0], "parameter"); //$NON-NLS-1$ + assertNotCollection(operands[0], "parameter"); //$NON-NLS-1$ + } + + boolean assertSingleArgumentClass(Object v) { + return v instanceof String; + } + + Object createInstance(Object arg) { + return new VersionRange((String) arg); + } + + String getOperator() { + return KEYWORD_RANGE; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Select.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Select.java new file mode 100644 index 000000000..76ee0cacd --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Select.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.ql.expression; + +import java.util.Iterator; +import org.eclipse.equinox.internal.p2.ql.MatchIteratorFilter; +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * An expression that yields a new collection consisting of all elements of the + * <code>collection</code> for which the <code>filter</code> yields <code>true</code>. + */ +final class Select extends CollectionFilter { + Select(Expression collection, LambdaExpression lambda) { + super(collection, lambda); + } + + public int getExpressionType() { + return TYPE_SELECT; + } + + Object evaluate(IEvaluationContext context, Iterator itor) { + return evaluateAsIterator(context, itor); + } + + Iterator evaluateAsIterator(final IEvaluationContext context, Iterator itor) { + return new MatchIteratorFilter(itor) { + protected boolean isMatch(Object val) { + lambda.getItemVariable().setValue(context, val); + return lambda.evaluate(context) == Boolean.TRUE; + } + }; + } + + String getOperator() { + return KEYWORD_SELECT; + } + + boolean isCollection() { + return true; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/SetFunction.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/SetFunction.java new file mode 100644 index 000000000..b8c83f10c --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/SetFunction.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.HashSet; +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +final class SetFunction extends Function { + + public SetFunction(Expression[] operands) { + super(operands); + } + + public Object evaluate(IEvaluationContext context) { + HashSet result = new HashSet(); + for (int idx = 0; idx < operands.length; ++idx) + result.add(operands[idx].evaluate(context)); + return result; + } + + String getOperator() { + return KEYWORD_SET; + } + + boolean isCollection() { + return true; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Traverse.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Traverse.java new file mode 100644 index 000000000..27fad3ebc --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Traverse.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.ql.expression; + +import java.util.*; +import org.eclipse.equinox.internal.p2.ql.SingleVariableContext; +import org.eclipse.equinox.internal.p2.ql.parser.IParserConstants; +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * An expression that will collect items recursively based on a <code>rule</code>. + * The <code>rule</code> is applied for each item in the <code>collection</code> and + * is supposed to create a new collection. The <code>rule</code> is then applied for each item + * in the new collection. All items are collected into a set and items that are already + * in that set will not be perused again. The set becomes the result of the traversal. + */ +final class Traverse extends CollectionFilter { + + Traverse(Expression collection, LambdaExpression lambda) { + super(collection, lambda); + } + + public int getExpressionType() { + return TYPE_TRAVERSE; + } + + Object evaluate(IEvaluationContext context, Iterator itor) { + return evaluateAsIterator(context, itor); + } + + Iterator evaluateAsIterator(IEvaluationContext context, Iterator iterator) { + HashSet collector = new HashSet(); + while (iterator.hasNext()) + traverse(collector, iterator.next(), context); + return collector.iterator(); + } + + String getOperator() { + return IParserConstants.KEYWORD_TRAVERSE; + } + + void traverse(Set collector, Object parent, IEvaluationContext context) { + if (collector.add(parent)) { + Variable variable = lambda.getItemVariable(); + context = new SingleVariableContext(context, variable); + variable.setValue(context, parent); + Iterator subIterator = lambda.evaluateAsIterator(context); + while (subIterator.hasNext()) + traverse(collector, subIterator.next(), context); + } + } + + boolean isCollection() { + return true; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Unary.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Unary.java new file mode 100644 index 000000000..ff4e017c6 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Unary.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.ql.expression; + +import org.eclipse.equinox.p2.ql.IEvaluationContext; +import org.eclipse.equinox.p2.ql.IExpressionVisitor; + +/** + * The abstract base class for all unary expressions + */ +abstract class Unary extends Expression { + public final Expression operand; + + Unary(Expression operand) { + this.operand = operand; + } + + public boolean accept(IExpressionVisitor visitor) { + return super.accept(visitor) && operand.accept(visitor); + } + + public Object evaluate(IEvaluationContext context) { + return operand.evaluate(context); + } + + public void toString(StringBuffer bld) { + bld.append(getOperator()); + appendOperand(bld, operand, getPriority()); + } + + public Expression getOperand() { + return operand; + } + + int countReferenceToEverything() { + return operand.countReferenceToEverything(); + } + + abstract String getOperator(); +}
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/UnaryCollectionFilter.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/UnaryCollectionFilter.java new file mode 100644 index 000000000..85d2a5527 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/UnaryCollectionFilter.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * 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.ql.expression; + +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +abstract class UnaryCollectionFilter extends Unary { + + UnaryCollectionFilter(Expression collection) { + super(collection); + } + + public Object evaluate(IEvaluationContext context) { + return evaluateAsIterator(context); + } + + public void toString(StringBuffer bld) { + if (operand instanceof Select) { + Select select = (Select) operand; + CollectionFilter.appendProlog(bld, select.operand, getOperator()); + appendOperand(bld, select.lambda, getPriority()); + } else + CollectionFilter.appendProlog(bld, operand, getOperator()); + bld.append(')'); + } + + int getPriority() { + return PRIORITY_COLLECTION; + } + + boolean isCollection() { + return true; + } + + boolean isElementBoolean() { + return operand.isElementBoolean(); + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Unique.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Unique.java new file mode 100644 index 000000000..1d592792a --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Unique.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.*; +import org.eclipse.equinox.internal.p2.ql.MatchIteratorFilter; +import org.eclipse.equinox.p2.ql.IEvaluationContext; + +/** + * An expression that ensures that the elements of its collection is only returned + * once throughout the whole query. + */ +final class Unique extends Binary { + static class UniqueIterator extends MatchIteratorFilter { + private final Set uniqueSet; + + public UniqueIterator(Iterator iterator, Set uniqueSet) { + super(iterator); + this.uniqueSet = uniqueSet; + } + + protected boolean isMatch(Object val) { + synchronized (uniqueSet) { + return uniqueSet.add(val); + } + } + } + + Unique(Expression collection, Expression explicitCache) { + super(collection, explicitCache); + assertNotBoolean(collection, "collection"); //$NON-NLS-1$ + assertNotBoolean(explicitCache, "cache"); //$NON-NLS-1$ + } + + public Object evaluate(IEvaluationContext context) { + Object explicitCache = rhs.evaluate(context); + Set uniqueSet; + if (explicitCache == null) + // No cache, we just ensure that the iteration is unique + uniqueSet = new HashSet(); + else { + if (!(explicitCache instanceof Set)) + throw new IllegalArgumentException("Unique cache must be a java.util.Set"); //$NON-NLS-1$ + uniqueSet = (Set) explicitCache; + } + return new UniqueIterator(lhs.evaluateAsIterator(context), uniqueSet); + } + + public int getExpressionType() { + return TYPE_UNIQUE; + } + + public void toString(StringBuffer bld) { + CollectionFilter.appendProlog(bld, lhs, getOperator()); + if (rhs != Constant.NULL_CONSTANT) + appendOperand(bld, rhs, PRIORITY_COMMA); + bld.append(')'); + } + + String getOperator() { + return KEYWORD_UNIQUE; + } + + int getPriority() { + return PRIORITY_COLLECTION; + } + + boolean isCollection() { + return true; + } + + boolean isElementBoolean() { + return lhs.isElementBoolean(); + } + +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Variable.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Variable.java new file mode 100644 index 000000000..fcc51ff09 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/Variable.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.Iterator; +import org.eclipse.equinox.internal.p2.ql.IRepeatableIterator; +import org.eclipse.equinox.internal.p2.ql.RepeatableIterator; +import org.eclipse.equinox.p2.ql.IEvaluationContext; +import org.eclipse.equinox.p2.ql.IExpression; + +/** + * An expression representing a variable stack in the current thread. + */ +class Variable extends Expression { + + static final Variable EVERYTHING = new Variable(VARIABLE_EVERYTHING); + + static final Variable TRANSLATIONS = new Variable(VARIABLE_TRANSLATIONS); + + static final Variable ITEM = new Variable(VARIABLE_ITEM); + + private final String name; + + public static Variable create(String name) { + if (VARIABLE_ITEM.equals(name)) + return ITEM; + if (VARIABLE_EVERYTHING.equals(name)) + return EVERYTHING; + if (VARIABLE_TRANSLATIONS.equals(name)) + return TRANSLATIONS; + return new Variable(name); + } + + Variable(String name) { + this.name = 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 String getName() { + return name; + } + + public final void setValue(IEvaluationContext context, Object value) { + context.setValue(this, value); + } + + public void toString(StringBuffer bld) { + bld.append(name); + } + + public int getExpressionType() { + return TYPE_VARIABLE; + } + + int countReferenceToEverything() { + return IExpression.VARIABLE_EVERYTHING.equals(name) ? 1 : 0; + } + + int getPriority() { + return PRIORITY_VARIABLE; + } + + String getOperator() { + return "<variable>"; //$NON-NLS-1$ + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/VersionFunction.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/VersionFunction.java new file mode 100644 index 000000000..7d2694e9e --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/VersionFunction.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * 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.ql.expression; + +import org.eclipse.equinox.internal.provisional.p2.metadata.Version; + +/** + * A function that creates a {@link Version} from a string + */ +final class VersionFunction extends Function { + + public VersionFunction(Expression[] operands) { + super(assertLength(operands, 1, 1, KEYWORD_VERSION)); + assertNotBoolean(operands[0], "parameter"); //$NON-NLS-1$ + assertNotCollection(operands[0], "parameter"); //$NON-NLS-1$ + } + + boolean assertSingleArgumentClass(Object v) { + return v instanceof String; + } + + Object createInstance(Object arg) { + return Version.create((String) arg); + } + + String getOperator() { + return KEYWORD_VERSION; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/WrappedIQuery.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/WrappedIQuery.java new file mode 100644 index 000000000..88fbbdddb --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/expression/WrappedIQuery.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.ql.expression; + +import java.util.Iterator; +import org.eclipse.equinox.internal.provisional.p2.metadata.query.Collector; +import org.eclipse.equinox.internal.provisional.p2.metadata.query.IMatchQuery; +import org.eclipse.equinox.p2.metadata.query.IQuery; +import org.eclipse.equinox.p2.ql.IEvaluationContext; +import org.eclipse.equinox.p2.ql.QLContextQuery; + +final class WrappedIQuery extends Function { + + public WrappedIQuery(Expression[] operands) { + super(assertLength(operands, 1, 3, KEYWORD_IQUERY)); + assertNotBoolean(operands[0], "parameter"); //$NON-NLS-1$ + assertNotCollection(operands[0], "parameter"); //$NON-NLS-1$ + } + + public Object evaluate(IEvaluationContext context) { + Object query = operands[0].evaluate(context); + + if (query instanceof IMatchQuery) { + if (operands.length > 2) + throw new IllegalArgumentException("iquery third argument cannot be combined with a match query"); //$NON-NLS-1$ + + Object value = null; + if (operands.length > 1) + value = operands[1].evaluate(context); + else + value = Variable.ITEM.evaluate(context); + return Boolean.valueOf(((IMatchQuery) query).isMatch(value)); + } + + if (!(query instanceof IQuery)) + throw new IllegalArgumentException("iquery first argument must be an IQuery instance"); //$NON-NLS-1$ + + Collector collector = null; + if (operands.length == 3) { + Object cobj = operands[2].evaluate(context); + if (cobj instanceof Collector) + collector = (Collector) cobj; + else if (cobj == null) + collector = new Collector(); + else + throw new IllegalArgumentException("iquery third argument must be a collector"); //$NON-NLS-1$ + } + + Iterator iterator = null; + if (operands.length > 1) + iterator = operands[1].evaluateAsIterator(context); + else + iterator = Variable.EVERYTHING.evaluateAsIterator(context); + + if (collector == null) { + if (query instanceof QLContextQuery) + return ((QLContextQuery) query).evaluate(iterator); + collector = new Collector(); + } + return ((IQuery) query).perform(iterator, collector); + } + + String getOperator() { + return KEYWORD_IQUERY; + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/parser/ExpressionParser.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/parser/ExpressionParser.java new file mode 100644 index 000000000..630b71983 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/parser/ExpressionParser.java @@ -0,0 +1,849 @@ +/******************************************************************************* + * 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.ql.parser; + +import org.eclipse.equinox.p2.ql.IExpression; + +import java.util.*; +import org.eclipse.equinox.p2.ql.*; + +public class ExpressionParser extends Stack implements IParserConstants, IExpressionParser { + private static final long serialVersionUID = 882034383978853143L; + + private static final int TOKEN_OR = 1; + private static final int TOKEN_AND = 2; + + private static final int TOKEN_EQUAL = 10; + private static final int TOKEN_NOT_EQUAL = 11; + private static final int TOKEN_LESS = 12; + private static final int TOKEN_LESS_EQUAL = 13; + private static final int TOKEN_GREATER = 14; + private static final int TOKEN_GREATER_EQUAL = 15; + private static final int TOKEN_MATCHES = 16; + + private static final int TOKEN_NOT = 20; + private static final int TOKEN_DOT = 21; + private static final int TOKEN_COMMA = 22; + private static final int TOKEN_PIPE = 23; + private static final int TOKEN_DOLLAR = 24; + private static final int TOKEN_IF = 25; + private static final int TOKEN_ELSE = 26; + + private static final int TOKEN_LP = 30; + private static final int TOKEN_RP = 31; + private static final int TOKEN_LB = 32; + private static final int TOKEN_RB = 33; + private static final int TOKEN_LC = 34; + private static final int TOKEN_RC = 35; + + private static final int TOKEN_IDENTIFIER = 40; + private static final int TOKEN_LITERAL = 41; + private static final int TOKEN_ANY = 42; + + private static final int TOKEN_NULL = 50; + private static final int TOKEN_TRUE = 51; + private static final int TOKEN_FALSE = 52; + + private static final int TOKEN_LATEST = 60; + private static final int TOKEN_LIMIT = 61; + private static final int TOKEN_FIRST = 62; + private static final int TOKEN_FLATTEN = 63; + private static final int TOKEN_UNIQUE = 64; + private static final int TOKEN_SELECT = 65; + private static final int TOKEN_COLLECT = 66; + private static final int TOKEN_TRAVERSE = 67; + private static final int TOKEN_EXISTS = 68; + private static final int TOKEN_ALL = 69; + + private static final int TOKEN_END = 0; + private static final int TOKEN_ERROR = -1; + + private static final Map keywords; + static { + keywords = new HashMap(); + keywords.put(KEYWORD_ALL, new Integer(TOKEN_ALL)); + keywords.put(KEYWORD_COLLECT, new Integer(TOKEN_COLLECT)); + keywords.put(KEYWORD_EXISTS, new Integer(TOKEN_EXISTS)); + keywords.put(KEYWORD_FALSE, new Integer(TOKEN_FALSE)); + keywords.put(KEYWORD_FIRST, new Integer(TOKEN_FIRST)); + keywords.put(KEYWORD_FLATTEN, new Integer(TOKEN_FLATTEN)); + keywords.put(KEYWORD_LATEST, new Integer(TOKEN_LATEST)); + keywords.put(KEYWORD_LIMIT, new Integer(TOKEN_LIMIT)); + keywords.put(KEYWORD_NULL, new Integer(TOKEN_NULL)); + keywords.put(KEYWORD_SELECT, new Integer(TOKEN_SELECT)); + keywords.put(KEYWORD_TRAVERSE, new Integer(TOKEN_TRAVERSE)); + keywords.put(KEYWORD_TRUE, new Integer(TOKEN_TRUE)); + keywords.put(KEYWORD_UNIQUE, new Integer(TOKEN_UNIQUE)); + keywords.put(OPERATOR_EACH, new Integer(TOKEN_ANY)); + } + + private final IExpressionFactory factory; + + private String expression; + private int tokenPos; + private int currentToken; + private int lastTokenPos; + private Object tokenValue; + private String rootVariable; + + public ExpressionParser(IExpressionFactory factory) { + this.factory = factory; + } + + public synchronized IMatchExpression parsePredicate(String exprString) { + expression = exprString; + tokenPos = 0; + currentToken = 0; + tokenValue = null; + rootVariable = IExpression.VARIABLE_ITEM; + IExpression itemVariable = factory.variable(IExpression.VARIABLE_ITEM); + push(itemVariable); + try { + nextToken(); + IExpression expr = currentToken == TOKEN_END ? factory.constant(Boolean.TRUE) : parseCondition(); + assertToken(TOKEN_END); + return factory.matchExpression(expr); + } finally { + popVariable(); // pop item + } + } + + public synchronized IContextExpression parseQuery(String exprString) { + expression = exprString; + tokenPos = 0; + currentToken = 0; + tokenValue = null; + rootVariable = IExpression.VARIABLE_EVERYTHING; + IExpression everythingVariable = factory.variable(IExpression.VARIABLE_EVERYTHING); + push(everythingVariable); + try { + nextToken(); + IExpression expr = parseCondition(); + assertToken(TOKEN_END); + return factory.contextExpression(expr); + } finally { + popVariable(); // pop context + } + } + + private IExpression parseCondition() { + IExpression expr = parseOr(); + if (currentToken == TOKEN_IF) { + nextToken(); + IExpression ifTrue = parseOr(); + assertToken(TOKEN_ELSE); + nextToken(); + expr = factory.condition(expr, ifTrue, parseOr()); + } + return expr; + } + + private IExpression parseOr() { + IExpression expr = parseAnd(); + if (currentToken != TOKEN_OR) + return expr; + + ArrayList exprs = new ArrayList(); + exprs.add(expr); + do { + nextToken(); + exprs.add(parseAnd()); + } while (currentToken == TOKEN_OR); + return factory.or((IExpression[]) exprs.toArray(new IExpression[exprs.size()])); + } + + private IExpression parseAnd() { + IExpression expr = parseBinary(); + if (currentToken != TOKEN_AND) + return expr; + + ArrayList exprs = new ArrayList(); + exprs.add(expr); + do { + nextToken(); + exprs.add(parseBinary()); + } while (currentToken == TOKEN_AND); + return factory.and((IExpression[]) exprs.toArray(new IExpression[exprs.size()])); + } + + private IExpression parseBinary() { + IExpression expr = parseNot(); + 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 = 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.not(factory.less(expr, rhs)); + break; + case TOKEN_LESS : + expr = factory.less(expr, rhs); + break; + case TOKEN_LESS_EQUAL : + expr = factory.not(factory.greater(expr, rhs)); + break; + default : + expr = factory.matches(expr, rhs); + } + break; + default : + throw syntaxError(); + } + return expr; + } + + private IExpression parseNot() { + if (currentToken == TOKEN_NOT) { + nextToken(); + IExpression expr = parseNot(); + return factory.not(expr); + } + return parseCollectionExpression(); + } + + private IExpression parseCollectionExpression() { + IExpression expr; + switch (currentToken) { + case TOKEN_SELECT : + case TOKEN_COLLECT : + case TOKEN_EXISTS : + case TOKEN_FIRST : + case TOKEN_FLATTEN : + case TOKEN_ALL : + case TOKEN_TRAVERSE : + case TOKEN_LATEST : + case TOKEN_LIMIT : + case TOKEN_UNIQUE : + expr = getVariableOrRootMember(rootVariable); + break; + default : + expr = parseMember(); + if (currentToken != TOKEN_DOT) + return expr; + nextToken(); + } + + for (;;) { + int filterToken = currentToken; + nextToken(); + assertToken(TOKEN_LP); + nextToken(); + switch (filterToken) { + case TOKEN_SELECT : + expr = factory.select(expr, parseLambdaDefinition()); + break; + case TOKEN_COLLECT : + expr = factory.collect(expr, parseLambdaDefinition()); + break; + case TOKEN_EXISTS : + expr = factory.exists(expr, parseLambdaDefinition()); + break; + case TOKEN_FIRST : + expr = factory.first(expr, parseLambdaDefinition()); + break; + case TOKEN_ALL : + expr = factory.all(expr, parseLambdaDefinition()); + break; + case TOKEN_TRAVERSE : + expr = factory.traverse(expr, parseLambdaDefinition()); + break; + case TOKEN_LATEST : + if (currentToken == TOKEN_RP) { + expr = factory.latest(expr); + assertToken(TOKEN_RP); + nextToken(); + } else + expr = factory.latest(factory.select(expr, parseLambdaDefinition())); + break; + case TOKEN_FLATTEN : + if (currentToken == TOKEN_RP) { + expr = factory.flatten(expr); + assertToken(TOKEN_RP); + nextToken(); + } else + expr = factory.flatten(factory.select(expr, parseLambdaDefinition())); + break; + case TOKEN_LIMIT : + expr = factory.limit(expr, parseCondition()); + assertToken(TOKEN_RP); + nextToken(); + break; + case TOKEN_UNIQUE : + if (currentToken == TOKEN_RP) + expr = factory.unique(expr, factory.constant(null)); + else { + expr = factory.unique(expr, parseMember()); + assertToken(TOKEN_RP); + nextToken(); + } + break; + default : + throw syntaxError(); + } + if (currentToken != TOKEN_DOT) + break; + nextToken(); + } + return expr; + } + + private IExpression parseMember() { + IExpression expr = parseConstructor(); + 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_SELECT : + case TOKEN_COLLECT : + case TOKEN_EXISTS : + case TOKEN_FIRST : + case TOKEN_FLATTEN : + case TOKEN_ALL : + case TOKEN_TRAVERSE : + case TOKEN_LATEST : + case TOKEN_LIMIT : + case TOKEN_UNIQUE : + tokenPos = savePos; + currentToken = saveToken; + tokenValue = saveTokenValue; + return expr; + + case TOKEN_IDENTIFIER : + name = (String) tokenValue; + nextToken(); + if (currentToken == TOKEN_LP) { + nextToken(); + IExpression[] callArgs = parseArray(); + assertToken(TOKEN_RP); + nextToken(); + expr = factory.memberCall(expr, name, callArgs); + } else + expr = factory.memberCall(expr, name, IExpressionFactory.NO_ARGS); + break; + + default : + throw syntaxError(); + } + } else { + IExpression atExpr = parseMember(); + assertToken(TOKEN_RB); + nextToken(); + expr = factory.at(expr, atExpr); + } + } + return expr; + } + + private IExpression parseLambdaDefinition() { + boolean endingRC = false; + int anyIndex = -1; + IExpression[] initializers = IExpressionFactory.NO_ARGS; + IExpression[] variables; + if (currentToken == TOKEN_LC) { + // Lambda starts without currying. + endingRC = true; + nextToken(); + anyIndex = 0; + variables = parseVariables(); + if (variables == null) + // empty means no pipe at the end. + throw syntaxError(); + } else { + anyIndex = 0; + variables = parseVariables(); + if (variables == null) { + anyIndex = -1; + initializers = parseArray(); + assertToken(TOKEN_LC); + nextToken(); + endingRC = true; + for (int idx = 0; idx < initializers.length; ++idx) { + IExpression initializer = initializers[idx]; + if (initializer.getExpressionType() == IExpression.TYPE_VARIABLE && OPERATOR_EACH.equals(initializer.toString())) { + if (anyIndex == -1) + anyIndex = idx; + else + anyIndex = -1; // Second Each. This is illegal + break; + } + } + if (anyIndex == -1) + throw new IllegalArgumentException("Exaclty one _ must be present among the currying expressions"); //$NON-NLS-1$ + + variables = parseVariables(); + if (variables == null) + // empty means no pipe at the end. + throw syntaxError(); + } + + } + nextToken(); + IExpression body = parseCondition(); + if (endingRC) { + assertToken(TOKEN_RC); + nextToken(); + } + + assertToken(TOKEN_RP); + nextToken(); + IExpression each; + IExpression[] assignments; + if (initializers.length == 0) { + if (variables.length != 1) + throw new IllegalArgumentException("Must have exactly one variable unless currying is used"); //$NON-NLS-1$ + each = variables[0]; + assignments = IExpressionFactory.NO_ARGS; + } else { + if (initializers.length != variables.length) + throw new IllegalArgumentException("Number of currying expressions and variables differ"); //$NON-NLS-1$ + + if (initializers.length == 1) { + // This is just a map from _ to some variable + each = variables[0]; + assignments = IExpressionFactory.NO_ARGS; + } else { + int idx; + each = variables[anyIndex]; + assignments = new IExpression[initializers.length - 1]; + for (idx = 0; idx < anyIndex; ++idx) + assignments[idx] = factory.assignment(variables[idx], initializers[idx]); + for (++idx; idx < initializers.length; ++idx) + assignments[idx] = factory.assignment(variables[idx], initializers[idx]); + } + } + return factory.lambda(each, body, assignments); + } + + private IExpression[] parseVariables() { + int savePos = tokenPos; + int saveToken = currentToken; + Object saveTokenValue = tokenValue; + List ids = null; + while (currentToken == TOKEN_IDENTIFIER) { + if (ids == null) + ids = new ArrayList(); + ids.add(tokenValue); + nextToken(); + if (currentToken == TOKEN_COMMA) { + nextToken(); + continue; + } + break; + } + + if (currentToken != TOKEN_PIPE) { + // This was not a variable list + tokenPos = savePos; + currentToken = saveToken; + tokenValue = saveTokenValue; + return null; + } + + if (ids == null) + // Empty list but otherwise OK + return IExpressionFactory.NO_ARGS; + + int top = ids.size(); + IExpression[] result = new IExpression[top]; + for (int idx = 0; idx < top; ++idx) { + String name = (String) ids.get(idx); + IExpression var = factory.variable(name); + push(var); + result[idx] = var; + } + return result; + } + + private IExpression parseConstructor() { + if (currentToken == TOKEN_IDENTIFIER) { + int savePos = tokenPos; + int saveToken = currentToken; + Object saveTokenValue = tokenValue; + + Object function = factory.getFunctionMap().get(tokenValue); + if (function != null) { + nextToken(); + if (currentToken == TOKEN_LP) { + nextToken(); + IExpression[] args = currentToken == TOKEN_RP ? IExpressionFactory.NO_ARGS : parseArray(); + assertToken(TOKEN_RP); + nextToken(); + return factory.function(function, args); + } + tokenPos = savePos; + currentToken = saveToken; + tokenValue = saveTokenValue; + } + } + return parseUnary(); + } + + private IExpression parseUnary() { + IExpression expr; + switch (currentToken) { + case TOKEN_LP : + nextToken(); + expr = parseCondition(); + assertToken(TOKEN_RP); + nextToken(); + break; + case TOKEN_LB : + nextToken(); + expr = factory.array(parseArray()); + assertToken(TOKEN_RB); + nextToken(); + break; + case TOKEN_LITERAL : + expr = factory.constant(tokenValue); + nextToken(); + break; + case TOKEN_DOLLAR : + expr = parseParameter(); + break; + case TOKEN_IDENTIFIER : + expr = getVariableOrRootMember((String) tokenValue); + nextToken(); + break; + case TOKEN_ANY : + expr = factory.variable(OPERATOR_EACH); + 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; + default : + throw syntaxError(); + } + return expr; + } + + private IExpression parseParameter() { + if (currentToken == TOKEN_DOLLAR) { + nextToken(); + + IExpression param = null; + if (currentToken == TOKEN_LITERAL && tokenValue instanceof Integer) + param = factory.indexedParameter(((Integer) tokenValue).intValue()); + else if (currentToken == TOKEN_IDENTIFIER) + param = factory.keyedParameter((String) tokenValue); + + if (param != null) { + nextToken(); + return param; + } + } + throw syntaxError(); + } + + private IExpression[] parseArray() { + IExpression expr = parseCondition(); + if (currentToken != TOKEN_COMMA) + return new IExpression[] {expr}; + + ArrayList operands = new ArrayList(); + 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 (IExpression[]) operands.toArray(new IExpression[operands.size()]); + } + + private void assertToken(int token) { + if (currentToken != token) + throw syntaxError(); + } + + private IExpression getVariableOrRootMember(String id) { + int idx = size(); + while (--idx >= 0) { + IExpression v = (IExpression) get(idx); + if (id.equals(v.toString())) + return v; + } + + if (rootVariable.equals(id)) + throw syntaxError("No such variable: " + id); //$NON-NLS-1$ + + return factory.memberCall(getVariableOrRootMember(rootVariable), id, IExpressionFactory.NO_ARGS); + } + + private 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_ARRAY; + 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 = IParserConstants.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 = (Integer) keywords.get(word); + if (token == null) + currentToken = TOKEN_IDENTIFIER; + else + currentToken = token.intValue(); + tokenValue = word; + break; + } + throw syntaxError(); + } + } + + private void popVariable() { + if (isEmpty()) + throw syntaxError(); + pop(); + } + + private QLParseException 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$ + } + + private QLParseException syntaxError(String message) { + return new QLParseException(expression, message, tokenPos); + } +} diff --git a/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/parser/IParserConstants.java b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/parser/IParserConstants.java new file mode 100644 index 000000000..381ad712b --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ql/src/org/eclipse/equinox/internal/p2/ql/parser/IParserConstants.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * 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.ql.parser; + +public interface IParserConstants { + + String KEYWORD_ALL = "all"; //$NON-NLS-1$ + String KEYWORD_BOOLEAN = "boolean"; //$NON-NLS-1$ + String KEYWORD_CAPABILITY_INDEX = "capabilityIndex"; //$NON-NLS-1$ + String KEYWORD_CLASS = "class"; //$NON-NLS-1$ + String KEYWORD_COLLECT = "collect"; //$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_FIRST = "first"; //$NON-NLS-1$ + String KEYWORD_FLATTEN = "flatten"; //$NON-NLS-1$ + String KEYWORD_IQUERY = "iquery"; //$NON-NLS-1$ + String KEYWORD_LATEST = "latest"; //$NON-NLS-1$ + String KEYWORD_LIMIT = "limit"; //$NON-NLS-1$ + String KEYWORD_LOCALIZED_KEYS = "localizedKeys"; //$NON-NLS-1$ + String KEYWORD_LOCALIZED_MAP = "localizedMap"; //$NON-NLS-1$ + String KEYWORD_LOCALIZED_PROPERTY = "localizedProperty"; //$NON-NLS-1$ + String KEYWORD_NULL = "null"; //$NON-NLS-1$ + String KEYWORD_RANGE = "range"; //$NON-NLS-1$ + String KEYWORD_SATISFIES_ALL = "satisfiesAll"; //$NON-NLS-1$ + String KEYWORD_SATISFIES_ANY = "satisfiesAny"; //$NON-NLS-1$ + String KEYWORD_SELECT = "select"; //$NON-NLS-1$ + String KEYWORD_SET = "set"; //$NON-NLS-1$ + String KEYWORD_TRAVERSE = "traverse"; //$NON-NLS-1$ + String KEYWORD_TRUE = "true"; //$NON-NLS-1$ + String KEYWORD_UNIQUE = "unique"; //$NON-NLS-1$ + String KEYWORD_VERSION = "version"; //$NON-NLS-1$ + + String OPERATOR_AND = "&&"; //$NON-NLS-1$ + String OPERATOR_ARRAY = "[]"; //$NON-NLS-1$ + String OPERATOR_ASSIGN = "="; //$NON-NLS-1$ + String OPERATOR_AT = "[]"; //$NON-NLS-1$ + String OPERATOR_EACH = "_"; //$NON-NLS-1$ + String OPERATOR_ELSE = ":"; //$NON-NLS-1$ + String OPERATOR_EQUALS = "=="; //$NON-NLS-1$ + String OPERATOR_GT = ">"; //$NON-NLS-1$ + String OPERATOR_GT_EQUAL = ">="; //$NON-NLS-1$ + String OPERATOR_IF = "?"; //$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_PARAMETER = 1; + int PRIORITY_VARIABLE = 1; + int PRIORITY_LITERAL = 1; + int PRIORITY_CONSTRUCTOR = 2; + 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; +} |