Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Hallgren2010-01-13 13:03:53 +0000
committerThomas Hallgren2010-01-13 13:03:53 +0000
commit521e788834ac2eccb042012c6080349f4c0bb6e3 (patch)
tree0582904865268f0a43e6cd9ab230b754d6ddf4bb
parentd743b627ab504ab6787a7cb560abdbe5d029e08e (diff)
downloadrt.equinox.p2-521e788834ac2eccb042012c6080349f4c0bb6e3.tar.gz
rt.equinox.p2-521e788834ac2eccb042012c6080349f4c0bb6e3.tar.xz
rt.equinox.p2-521e788834ac2eccb042012c6080349f4c0bb6e3.zip
298604 : Split the p2QL into one core simple Expression part and one Query part
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/META-INF/MANIFEST.MF3
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/IRequiredCapability.java10
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnit.java23
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/LDAPQuery.java39
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/Messages.java16
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ProvidedCapability.java19
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequiredCapability.java223
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequirementChange.java44
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ResolvedInstallableUnit.java11
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TranslationSupport.java9
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/All.java42
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/And.java50
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/At.java96
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Binary.java135
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CoercingComparator.java392
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CollectionFilter.java85
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Compare.java60
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Equals.java52
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/EvaluationContext.java161
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Exists.java42
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Expression.java341
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/ExpressionFactory.java125
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IExpressionConstants.java58
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IRepeatableIterator.java25
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPApproximation.java77
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPFilter.java69
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LambdaExpression.java72
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Literal.java123
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MatchExpression.java82
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Matches.java95
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java178
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MemberProvider.java139
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/NAry.java59
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Not.java48
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Or.java50
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Parameter.java64
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/RepeatableIterator.java237
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Unary.java57
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Variable.java79
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/ExpressionParser.java617
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/LDAPFilterParser.java268
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/messages.properties8
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/MetadataFactory.java15
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IInstallableUnit.java4
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IProvidedCapability.java20
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IRequirement.java20
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/ExpressionParseException.java27
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/ExpressionUtil.java179
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IEvaluationContext.java38
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpression.java67
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionFactory.java206
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionParser.java25
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionVisitor.java25
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IFilterExpression.java47
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IMatchExpression.java53
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/SimplePattern.java158
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/ExpressionQuery.java46
57 files changed, 5061 insertions, 252 deletions
diff --git a/bundles/org.eclipse.equinox.p2.metadata/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.metadata/META-INF/MANIFEST.MF
index 4d1bec1d1..d3106ee44 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.metadata/META-INF/MANIFEST.MF
@@ -18,6 +18,8 @@ Export-Package: org.eclipse.equinox.internal.p2.metadata;
org.eclipse.equinox.p2.director,
org.eclipse.equinox.p2.director.app,
org.eclipse.equinox.p2.installer",
+ org.eclipse.equinox.internal.p2.metadata.expression;x-friends:="org.eclipse.equinox.p2.ql",
+ org.eclipse.equinox.internal.p2.metadata.expression.parser;x-friends:="org.eclipse.equinox.p2.ql",
org.eclipse.equinox.internal.p2.metadata.query;
x-friends:="org.eclipse.equinox.p2.artifact.optimizers,
org.eclipse.equinox.p2.artifact.processors,
@@ -87,6 +89,7 @@ Export-Package: org.eclipse.equinox.internal.p2.metadata;
org.eclipse.equinox.p2.repository.tools,
org.eclipse.pde.core",
org.eclipse.equinox.p2.metadata,
+ org.eclipse.equinox.p2.metadata.expression,
org.eclipse.equinox.p2.metadata.query,
org.eclipse.equinox.p2.query
Import-Package: org.eclipse.osgi.service.localization;version="1.0.0",
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/IRequiredCapability.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/IRequiredCapability.java
index 5d1a853f2..ef92ee045 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/IRequiredCapability.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/IRequiredCapability.java
@@ -9,10 +9,7 @@
******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
-import org.eclipse.equinox.p2.metadata.VersionRange;
-
import org.eclipse.equinox.p2.metadata.*;
-import org.eclipse.equinox.p2.query.IMatchQuery;
/**
* A required capability represents some external constraint on an {@link IInstallableUnit}.
@@ -30,7 +27,7 @@ import org.eclipse.equinox.p2.query.IMatchQuery;
* @noimplement This interface is not intended to be implemented by clients.
* @noextend This interface is not intended to be extended by clients.
*/
-public interface IRequiredCapability extends IMatchQuery<IInstallableUnit>, IRequirement {
+public interface IRequiredCapability extends IRequirement {
// public String getFilter();
@@ -45,9 +42,4 @@ public interface IRequiredCapability extends IMatchQuery<IInstallableUnit>, IReq
* @return the range of versions that satisfy this required capability.
*/
public VersionRange getRange();
-
- public boolean equals(Object other);
-
- public boolean satisfiedBy(IProvidedCapability cap);
-
} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnit.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnit.java
index 7cee35eb8..018f26cb6 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnit.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnit.java
@@ -17,7 +17,8 @@ import java.util.*;
import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils;
import org.eclipse.equinox.internal.p2.core.helpers.OrderedProperties;
import org.eclipse.equinox.p2.metadata.*;
-import org.eclipse.equinox.p2.query.IQuery;
+import org.eclipse.equinox.p2.metadata.expression.ExpressionUtil;
+import org.osgi.framework.Filter;
public class InstallableUnit implements IInstallableUnit {
@@ -29,7 +30,7 @@ public class InstallableUnit implements IInstallableUnit {
private static final ILicense[] NO_LICENSE = new ILicense[0];
private IArtifactKey[] artifacts = NO_ARTIFACTS;
- private LDAPQuery filter;
+ private Filter filter;
private String id;
@@ -99,7 +100,7 @@ public class InstallableUnit implements IInstallableUnit {
return CollectionUtils.unmodifiableList(artifacts);
}
- public IQuery<Boolean> getFilter() {
+ public Filter getFilter() {
return filter;
}
@@ -194,8 +195,12 @@ public class InstallableUnit implements IInstallableUnit {
providedCapabilities = newCapabilities;
}
+ public void setFilter(Filter filter) {
+ this.filter = filter;
+ }
+
public void setFilter(String filter) {
- this.filter = filter == null ? null : new LDAPQuery(filter);
+ setFilter(filter == null ? null : ExpressionUtil.parseLDAP(filter));
}
public void setId(String id) {
@@ -281,15 +286,7 @@ public class InstallableUnit implements IInstallableUnit {
}
public boolean satisfies(IRequirement candidate) {
- if (candidate.getMatches() instanceof RequiredCapability) {
- for (int i = 0; i < providedCapabilities.length; i++) {
- if (((IRequiredCapability) candidate).satisfiedBy(providedCapabilities[i]))
- return true;
- }
- } else {
- throw new IllegalArgumentException();
- }
- return false;
+ return candidate.isMatch(this);
}
public Collection<IRequirement> getMetaRequiredCapabilities() {
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/LDAPQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/LDAPQuery.java
deleted file mode 100644
index c6c14bfae..000000000
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/LDAPQuery.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.eclipse.equinox.internal.p2.metadata;
-
-import java.util.Iterator;
-import org.eclipse.equinox.p2.query.IQuery;
-import org.eclipse.equinox.p2.query.IQueryResult;
-
-public class LDAPQuery implements IQuery<Boolean> {
- private String filter;
-
- public LDAPQuery(String filter) {
- this.filter = filter;
- }
-
- public String getFilter() {
- return filter;
- }
-
- public IQueryResult<Boolean> perform(Iterator<Boolean> iterator) {
- throw new IllegalStateException();
- }
-
- public String getId() {
- return null;
- }
-
- public Object getProperty(String property) {
- return null;
- }
-
- public void setFilter(String text) {
- filter = text;
- }
-
- public boolean equals(Object obj) {
- if (obj instanceof LDAPQuery)
- return filter.equals(((LDAPQuery) obj).getFilter());
- return false;
- }
-}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/Messages.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/Messages.java
index d03fc3816..0466d2676 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/Messages.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/Messages.java
@@ -51,6 +51,22 @@ public class Messages extends NLS {
public static String expected_slash_after_raw_vector_0;
+ public static String filter_trailing_characters;
+
+ public static String filter_missing_leftparen;
+
+ public static String filter_missing_rightparen;
+
+ public static String filter_invalid_operator;
+
+ public static String filter_missing_attr;
+
+ public static String filter_invalid_value;
+
+ public static String filter_missing_value;
+
+ public static String filter_premature_end;
+
public static String format_0_unable_to_parse_1;
public static String format_0_unable_to_parse_empty_version;
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ProvidedCapability.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ProvidedCapability.java
index 7ce42cb3f..06f8af8e1 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ProvidedCapability.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ProvidedCapability.java
@@ -11,11 +11,9 @@
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
-import org.eclipse.equinox.p2.metadata.Version;
-
import org.eclipse.core.runtime.Assert;
import org.eclipse.equinox.p2.metadata.IProvidedCapability;
-import org.eclipse.equinox.p2.metadata.IRequirement;
+import org.eclipse.equinox.p2.metadata.Version;
/**
* Describes a capability as exposed or required by an installable unit
@@ -62,22 +60,7 @@ public class ProvidedCapability implements IProvidedCapability {
return namespace.hashCode() * name.hashCode() * version.hashCode();
}
- /**
- * Returns whether this provided capability satisfies the given required capability.
- * @return <code>true</code> if this capability satisfies the given required
- * capability, and <code>false</code> otherwise.
- */
- public boolean satisfies(IRequiredCapability candidate) {
- return candidate.satisfiedBy(this);
- }
-
public String toString() {
return namespace + '/' + name + '/' + version;
}
-
- public boolean satisfies(IRequirement candidate) {
- // TODO Auto-generated method stub
- return false;
- }
-
}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequiredCapability.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequiredCapability.java
index ddee40c79..04d5b4ede 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequiredCapability.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequiredCapability.java
@@ -11,12 +11,10 @@
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
-import org.eclipse.equinox.p2.metadata.VersionRange;
-
import org.eclipse.core.runtime.Assert;
import org.eclipse.equinox.p2.metadata.*;
-import org.eclipse.equinox.p2.query.IQuery;
-import org.eclipse.equinox.p2.query.MatchQuery;
+import org.eclipse.equinox.p2.metadata.expression.*;
+import org.osgi.framework.Filter;
/**
* A required capability represents some external constraint on an {@link IInstallableUnit}.
@@ -31,80 +29,125 @@ import org.eclipse.equinox.p2.query.MatchQuery;
*
* @see IInstallableUnit#NAMESPACE_IU_ID
*/
-public class RequiredCapability extends MatchQuery<IInstallableUnit> implements IRequiredCapability, IRequirement {
- private LDAPQuery filter;
- private final String name;//never null
- private final String namespace;//never null
- private boolean greedy = true;
- private final VersionRange range;//never null
- private int min;
- private int max;
+public class RequiredCapability implements IRequiredCapability {
+ private final Filter filter;
+ private final boolean greedy;
+ private final IMatchExpression<IInstallableUnit> matchExpression;
+ private final int min;
+ private final int max;
+
+ private static final String MEMBER_NAME = "name"; //$NON-NLS-1$
+ private static final String MEMBER_NAMESPACE = "namespace"; //$NON-NLS-1$
+ private static final String MEMBER_VERSION = "version"; //$NON-NLS-1$
+ private static final String MEMBER_PROVIDED_CAPABILITIES = "providedCapabilities"; //$NON-NLS-1$
+
+ private static final IExpression allVersionsExpression;
+ private static final IExpression range_II_Expression;
+ private static final IExpression range_IN_Expression;
+ private static final IExpression range_NI_Expression;
+ private static final IExpression range_NN_Expression;
+ private static final IExpression strictVersionExpression;
+ private static final IExpression openEndedExpression;
+ private static final IExpression openEndedNonInclusiveExpression;
+
+ static {
+ IExpressionFactory factory = ExpressionUtil.getFactory();
+ IExpression xVar = factory.variable("x"); //$NON-NLS-1$
+ IExpression nameEqual = factory.equals(factory.member(xVar, MEMBER_NAME), factory.indexedParameter(0));
+ IExpression namespaceEqual = factory.equals(factory.member(xVar, MEMBER_NAMESPACE), factory.indexedParameter(1));
+
+ IExpression versionMember = factory.member(xVar, MEMBER_VERSION);
+
+ IExpression versionCmpLow = factory.indexedParameter(2);
+ IExpression versionEqual = factory.equals(versionMember, versionCmpLow);
+ IExpression versionGt = factory.greater(versionMember, versionCmpLow);
+ IExpression versionGtEqual = factory.greaterEqual(versionMember, versionCmpLow);
+
+ IExpression versionCmpHigh = factory.indexedParameter(3);
+ IExpression versionLt = factory.less(versionMember, versionCmpHigh);
+ IExpression versionLtEqual = factory.lessEqual(versionMember, versionCmpHigh);
+
+ IExpression pvMember = factory.member(factory.thisVariable(), MEMBER_PROVIDED_CAPABILITIES);
+ allVersionsExpression = factory.exists(pvMember, factory.lambda(xVar, factory.and(nameEqual, namespaceEqual)));
+ strictVersionExpression = factory.exists(pvMember, factory.lambda(xVar, factory.and(nameEqual, namespaceEqual, versionEqual)));
+ openEndedExpression = factory.exists(pvMember, factory.lambda(xVar, factory.and(nameEqual, namespaceEqual, versionGtEqual)));
+ openEndedNonInclusiveExpression = factory.exists(pvMember, factory.lambda(xVar, factory.and(nameEqual, namespaceEqual, versionGt)));
+ range_II_Expression = factory.exists(pvMember, factory.lambda(xVar, factory.and(nameEqual, namespaceEqual, versionGtEqual, versionLtEqual)));
+ range_IN_Expression = factory.exists(pvMember, factory.lambda(xVar, factory.and(nameEqual, namespaceEqual, versionGtEqual, versionLt)));
+ range_NI_Expression = factory.exists(pvMember, factory.lambda(xVar, factory.and(nameEqual, namespaceEqual, versionGt, versionLtEqual)));
+ range_NN_Expression = factory.exists(pvMember, factory.lambda(xVar, factory.and(nameEqual, namespaceEqual, versionGt, versionLt)));
+ }
/**
* TODO replace booleans with int options flag.
*/
public RequiredCapability(String namespace, String name, VersionRange range, String filter, boolean optional, boolean multiple) {
- Assert.isNotNull(namespace);
- Assert.isNotNull(name);
- this.namespace = namespace;
- this.name = name;
- this.range = range == null ? VersionRange.emptyRange : range;
- min = optional ? 0 : 1;
- max = 1;
- setFilter(filter);
+ this(namespace, name, range, filter, optional, multiple, true);
+ }
+
+ public RequiredCapability(String namespace, String name, VersionRange range, String filter, boolean optional, boolean multiple, boolean greedy) {
+ this(namespace, name, range, filter == null ? (Filter) null : ExpressionUtil.parseLDAP(filter), optional ? 0 : 1, multiple ? 1 : Integer.MAX_VALUE, greedy);
}
- public RequiredCapability(String namespace, String name, VersionRange range, IQuery<Boolean> filter, int min, int max, boolean greedy) {
+ public RequiredCapability(String namespace, String name, VersionRange range, Filter filter, int min, int max, boolean greedy) {
Assert.isNotNull(namespace);
Assert.isNotNull(name);
- this.namespace = namespace;
- this.name = name;
- this.range = range == null ? VersionRange.emptyRange : range;
- this.min = min;
- this.max = max;
- this.greedy = greedy;
- if (filter != null) {
- if (filter instanceof LDAPQuery) {
- this.filter = (LDAPQuery) filter;
+ IExpressionFactory factory = ExpressionUtil.getFactory();
+ if (range == null || range.equals(VersionRange.emptyRange)) {
+ matchExpression = factory.matchExpression(allVersionsExpression, name, namespace);
+ } else {
+ if (range.getMinimum().equals(range.getMaximum())) {
+ // Explicit version appointed
+ matchExpression = factory.matchExpression(strictVersionExpression, name, namespace, range.getMinimum());
} else {
- throw new IllegalArgumentException();
+ if (range.getMaximum().equals(Version.MAX_VERSION)) {
+ // Open ended
+ matchExpression = factory.matchExpression(range.getIncludeMinimum() ? openEndedExpression : openEndedNonInclusiveExpression, name, namespace, range.getMinimum());
+ } else {
+ matchExpression = factory.matchExpression(//
+ range.getIncludeMinimum() ? (range.getIncludeMaximum() ? range_II_Expression : range_IN_Expression) //
+ : (range.getIncludeMaximum() ? range_NI_Expression : range_NN_Expression), //
+ name, namespace, range.getMinimum(), range.getMaximum());
+ }
}
}
- }
-
- public RequiredCapability(String namespace, String name, VersionRange range, String filter, boolean optional, boolean multiple, boolean greedy) {
- this(namespace, name, range, filter, optional, multiple);
+ this.min = min;
+ this.max = max;
this.greedy = greedy;
+ this.filter = filter;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
- if (obj == null)
- return false;
- if (!(obj instanceof IRequiredCapability))
- return false;
- final IRequiredCapability other = (IRequiredCapability) obj;
- if (filter == null) {
- if (other.getFilter() != null)
+ if (obj instanceof RequiredCapability) {
+ RequiredCapability other = (RequiredCapability) obj;
+ if (filter == null) {
+ if (other.getFilter() != null)
+ return false;
+ } else if (!filter.equals(other.getFilter()))
return false;
- } else if (!filter.equals(other.getFilter()))
- return false;
- if (!name.equals(other.getName()))
- return false;
- if (!namespace.equals(other.getNamespace()))
- return false;
- if (!range.equals(other.getRange()))
- return false;
- return true;
+ return min == other.min && max == other.max && greedy == other.greedy && matchExpression.equals(other.matchExpression);
+ }
+ if (obj instanceof IRequiredCapability) {
+ // Some other type of RequiredCapability
+ IRequiredCapability other = (IRequiredCapability) obj;
+ if (filter == null) {
+ if (other.getFilter() != null)
+ return false;
+ } else if (!filter.equals(other.getFilter()))
+ return false;
+ return min == other.getMin() && max == other.getMax() && greedy == other.isGreedy() && getName().equals(other.getName()) && getNamespace().equals(other.getNamespace()) && getRange().equals(other.getRange());
+ }
+ return false;
}
public String getName() {
- return name;
+ return (String) matchExpression.getParameters()[0];
}
public String getNamespace() {
- return namespace;
+ return (String) matchExpression.getParameters()[1];
}
/**
@@ -114,28 +157,17 @@ public class RequiredCapability extends MatchQuery<IInstallableUnit> implements
* @return the range of versions that satisfy this required capability.
*/
public VersionRange getRange() {
- return range;
+ return extractRange(matchExpression);
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((filter == null) ? 0 : filter.hashCode());
- result = prime * result + name.hashCode();
- result = prime * result + namespace.hashCode();
- result = prime * result + range.hashCode();
+ result = prime * result + matchExpression.hashCode();
return result;
}
- /**
- * TODO This object shouldn't be mutable since it makes equality unstable, and
- * introduces lifecycle issues (how are the changes persisted, etc)
- */
- public void setFilter(String filter) {
- if (filter != null)
- this.filter = new LDAPQuery(filter);
- }
-
public boolean isGreedy() {
return greedy;
}
@@ -157,6 +189,7 @@ public class RequiredCapability extends MatchQuery<IInstallableUnit> implements
result.append(' ');
result.append(getName());
result.append(' ');
+ VersionRange range = getRange();
//for an exact version match, print a simpler expression
if (range.getMinimum().equals(range.getMaximum()))
result.append('[').append(range.getMinimum()).append(']');
@@ -165,18 +198,6 @@ public class RequiredCapability extends MatchQuery<IInstallableUnit> implements
return result.toString();
}
- public boolean isNegation() {
- return false;
- }
-
- public boolean satisfiedBy(IProvidedCapability cap) {
- if (getName() == null || !getName().equals(cap.getName()))
- return false;
- if (getNamespace() == null || !getNamespace().equals(cap.getNamespace()))
- return false;
- return getRange().isIncluded(cap.getVersion());
- }
-
public int getMin() {
return min;
}
@@ -185,17 +206,51 @@ public class RequiredCapability extends MatchQuery<IInstallableUnit> implements
return max;
}
- public IQuery<IInstallableUnit> getMatches() {
- return this;
+ public IMatchExpression<IInstallableUnit> getMatches() {
+ return matchExpression;
}
- public IQuery<Boolean> getFilter() {
+ public Filter getFilter() {
return filter;
}
public boolean isMatch(IInstallableUnit candidate) {
- if (!candidate.satisfies(this))
- return false;
- return true;
+ return matchExpression.isMatch(candidate);
+ }
+
+ public static boolean isVersionStrict(IMatchExpression<IInstallableUnit> matchExpression) {
+ return ExpressionUtil.getOperand(matchExpression) == strictVersionExpression;
+ }
+
+ public static String extractName(IMatchExpression<IInstallableUnit> matchExpression) {
+ assertValid(matchExpression);
+ return (String) matchExpression.getParameters()[0];
+ }
+
+ public static String extractNamespace(IMatchExpression<IInstallableUnit> matchExpression) {
+ assertValid(matchExpression);
+ return (String) matchExpression.getParameters()[1];
+ }
+
+ public static VersionRange extractRange(IMatchExpression<IInstallableUnit> matchExpression) {
+ IExpression expr = assertValid(matchExpression);
+ Object[] params = matchExpression.getParameters();
+ if (params.length < 3)
+ return VersionRange.emptyRange;
+ Version v = (Version) params[2];
+ if (params.length < 4) {
+ if (expr == strictVersionExpression)
+ return new VersionRange(v, true, v, true);
+ return new VersionRange(v, expr == openEndedExpression, Version.MAX_VERSION, true);
+ }
+ Version h = (Version) params[3];
+ return new VersionRange(v, expr == range_II_Expression || expr == range_IN_Expression, h, expr == range_II_Expression || expr == range_NI_Expression);
+ }
+
+ private static IExpression assertValid(IMatchExpression<IInstallableUnit> matchExpression) {
+ IExpression expr = ExpressionUtil.getOperand(matchExpression);
+ if (!(expr == allVersionsExpression || expr == range_II_Expression || expr == range_IN_Expression || expr == range_NI_Expression || expr == range_NN_Expression || expr == strictVersionExpression || expr == openEndedExpression || expr == openEndedNonInclusiveExpression))
+ throw new IllegalArgumentException();
+ return expr;
}
}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequirementChange.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequirementChange.java
index a08fe2639..d67a1ca39 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequirementChange.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequirementChange.java
@@ -10,9 +10,6 @@
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
-import org.eclipse.equinox.p2.metadata.Version;
-import org.eclipse.equinox.p2.metadata.VersionRange;
-
import org.eclipse.equinox.p2.metadata.IRequirementChange;
public class RequirementChange implements IRequirementChange {
@@ -42,46 +39,7 @@ public class RequirementChange implements IRequirementChange {
if (toMatch.getRange().equals(applyOn.getRange()))
return true;
- return intersect(toMatch.getRange(), applyOn.getRange()) == null ? false : true;
- }
-
- private VersionRange intersect(VersionRange r1, VersionRange r2) {
- Version resultMin = null;
- boolean resultMinIncluded = false;
- Version resultMax = null;
- boolean resultMaxIncluded = false;
-
- int minCompare = r1.getMinimum().compareTo(r2.getMinimum());
- if (minCompare < 0) {
- resultMin = r2.getMinimum();
- resultMinIncluded = r2.getIncludeMinimum();
- } else if (minCompare > 0) {
- resultMin = r1.getMinimum();
- resultMinIncluded = r1.getIncludeMinimum();
- } else if (minCompare == 0) {
- resultMin = r1.getMinimum();
- resultMinIncluded = r1.getIncludeMinimum() && r2.getIncludeMinimum();
- }
-
- int maxCompare = r1.getMaximum().compareTo(r2.getMaximum());
- if (maxCompare > 0) {
- resultMax = r2.getMaximum();
- resultMaxIncluded = r2.getIncludeMaximum();
- } else if (maxCompare < 0) {
- resultMax = r1.getMaximum();
- resultMaxIncluded = r1.getIncludeMaximum();
- } else if (maxCompare == 0) {
- resultMax = r1.getMaximum();
- resultMaxIncluded = r1.getIncludeMaximum() && r2.getIncludeMaximum();
- }
-
- int resultRangeComparison = resultMin.compareTo(resultMax);
- if (resultRangeComparison < 0)
- return new VersionRange(resultMin, resultMinIncluded, resultMax, resultMaxIncluded);
- else if (resultRangeComparison == 0 && resultMinIncluded == resultMaxIncluded)
- return new VersionRange(resultMin, resultMinIncluded, resultMax, resultMaxIncluded);
- else
- return null;
+ return toMatch.getRange().intersect(applyOn.getRange()) != null;
}
public int hashCode() {
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ResolvedInstallableUnit.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ResolvedInstallableUnit.java
index 5156f3111..580f5d5a4 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ResolvedInstallableUnit.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ResolvedInstallableUnit.java
@@ -16,7 +16,7 @@ import org.eclipse.equinox.p2.metadata.Version;
import java.util.*;
import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils;
import org.eclipse.equinox.p2.metadata.*;
-import org.eclipse.equinox.p2.query.IQuery;
+import org.osgi.framework.Filter;
public class ResolvedInstallableUnit implements IInstallableUnit {
private static IInstallableUnitFragment[] NO_IU = new IInstallableUnitFragment[0];
@@ -52,7 +52,7 @@ public class ResolvedInstallableUnit implements IInstallableUnit {
return original.getArtifacts();
}
- public IQuery<Boolean> getFilter() {
+ public Filter getFilter() {
return original.getFilter();
}
@@ -187,12 +187,7 @@ public class ResolvedInstallableUnit implements IInstallableUnit {
}
public boolean satisfies(IRequirement candidate) {
- Collection<IProvidedCapability> provides = getProvidedCapabilities();
- for (IProvidedCapability capability : provides) {
- if (capability.satisfies(candidate))
- return true;
- }
- return false;
+ return candidate.isMatch(this);
}
}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TranslationSupport.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TranslationSupport.java
index d3c3bb72b..e42289040 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TranslationSupport.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TranslationSupport.java
@@ -11,8 +11,6 @@
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
-import org.eclipse.equinox.p2.metadata.VersionRange;
-
import java.lang.ref.SoftReference;
import java.util.*;
import org.eclipse.core.runtime.IStatus;
@@ -20,6 +18,9 @@ import org.eclipse.core.runtime.Status;
import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
import org.eclipse.equinox.internal.provisional.p2.metadata.MetadataFactory;
import org.eclipse.equinox.p2.metadata.*;
+import org.eclipse.equinox.p2.metadata.expression.ExpressionUtil;
+import org.eclipse.equinox.p2.metadata.expression.IExpression;
+import org.eclipse.equinox.p2.metadata.query.ExpressionQuery;
import org.eclipse.equinox.p2.metadata.query.FragmentQuery;
import org.eclipse.equinox.p2.query.*;
import org.eclipse.osgi.service.localization.LocaleProvider;
@@ -40,6 +41,8 @@ public class TranslationSupport {
static final String NAMESPACE_IU_LOCALIZATION = "org.eclipse.equinox.p2.localization"; //$NON-NLS-1$
private IQueryable<IInstallableUnit> fragmentSource;
+ private static IExpression capabilityMatch = ExpressionUtil.parse("providedCapabilities.exists(x | x.name == $0 && x.namespace == $1)"); //$NON-NLS-1$
+
// Cache the IU fragments that provide localizations for a given locale.
// Map<String,SoftReference<IQueryResult>>: locale => soft reference to a queryResult
private Map<String, SoftReference<IQueryResult<IInstallableUnit>>> localeCollectorCache = new HashMap<String, SoftReference<IQueryResult<IInstallableUnit>>>(2);
@@ -200,7 +203,7 @@ public class TranslationSupport {
@SuppressWarnings("unchecked")
IQuery<IInstallableUnit>[] localeQuery = new IQuery[locales.size()];
for (int j = 0; j < locales.size(); j++) {
- localeQuery[j] = new RequiredCapability(NAMESPACE_IU_LOCALIZATION, locales.get(j), VersionRange.emptyRange, null, false, false);
+ localeQuery[j] = new ExpressionQuery<IInstallableUnit>(IInstallableUnit.class, capabilityMatch, locales.get(j), NAMESPACE_IU_LOCALIZATION);
}
IQuery<IInstallableUnit> iuQuery = new PipedQuery<IInstallableUnit>(new FragmentQuery(), CompoundQuery.createCompoundQuery(localeQuery, false));
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/All.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/All.java
new file mode 100644
index 000000000..4de975525
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/All.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.Iterator;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * A collection filter that yields true if the <code>filter</code> yields true for
+ * all of the elements of the <code>collection</code>
+ */
+final class All extends CollectionFilter {
+ All(Expression collection, LambdaExpression lambda) {
+ super(collection, lambda);
+ }
+
+ protected Object evaluate(IEvaluationContext context, Iterator<?> itor) {
+ Variable variable = lambda.getItemVariable();
+ while (itor.hasNext()) {
+ variable.setValue(context, itor.next());
+ if (lambda.evaluate(context) != Boolean.TRUE)
+ return Boolean.FALSE;
+ }
+ return Boolean.TRUE;
+ }
+
+ public int getExpressionType() {
+ return TYPE_ALL;
+ }
+
+ public String getOperator() {
+ return KEYWORD_ALL;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/And.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/And.java
new file mode 100644
index 000000000..7e8adf20e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/And.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * n-ary AND operator. The full evaluation is <code>true</code> if all its operands evaluate to
+ * <code>true</code>.
+ */
+final class And extends NAry {
+ And(Expression[] operands) {
+ super(assertLength(operands, 2, OPERATOR_AND));
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ for (int idx = 0; idx < operands.length; ++idx) {
+ if (operands[idx].evaluate(context) != Boolean.TRUE)
+ return Boolean.FALSE;
+ }
+ return Boolean.TRUE;
+ }
+
+ public int getExpressionType() {
+ return TYPE_AND;
+ }
+
+ public String getOperator() {
+ return OPERATOR_AND;
+ }
+
+ public int getPriority() {
+ return PRIORITY_AND;
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ buf.append("(&"); //$NON-NLS-1$
+ for (int idx = 0; idx < operands.length; ++idx)
+ operands[idx].toLDAPString(buf);
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/At.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/At.java
new file mode 100644
index 000000000..e089c1dbc
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/At.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.*;
+import org.eclipse.equinox.internal.p2.metadata.expression.Member.DynamicMember;
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * This class represents indexed or keyed access to an indexed collection
+ * or a map.
+ */
+public class At extends Binary {
+ protected At(Expression lhs, Expression rhs) {
+ super(lhs, rhs);
+ }
+
+ protected Object handleMember(IEvaluationContext context, Member member, Object instance, boolean[] handled) {
+ if (instance instanceof IInstallableUnit) {
+ if ("properties".equals(member.getName())) { //$NON-NLS-1$
+ // Avoid full copy of the properties map just to get one member
+ handled[0] = true;
+ return ((IInstallableUnit) instance).getProperty((String) rhs.evaluate(context));
+ }
+ }
+ return null;
+ }
+
+ public Object evaluate(org.eclipse.equinox.p2.metadata.expression.IEvaluationContext context) {
+ Object lval;
+ if (lhs instanceof DynamicMember) {
+ DynamicMember lm = (DynamicMember) lhs;
+ Object instance = lm.operand.evaluate(context);
+ boolean[] handled = new boolean[] {false};
+ Object result = handleMember(context, lm, instance, handled);
+ if (handled[0])
+ return result;
+ lval = lm.invoke(instance);
+ } else
+ lval = lhs.evaluate(context);
+
+ Object rval = rhs.evaluate(context);
+ if (lval == null)
+ throw new IllegalArgumentException("Unable to use [] on null"); //$NON-NLS-1$
+
+ if (lval instanceof Map<?, ?>)
+ return ((Map<?, ?>) lval).get(rval);
+
+ if (rval instanceof Number) {
+ if (lval instanceof List<?>)
+ return ((List<?>) lval).get(((Number) rval).intValue());
+ if (lval != null && lval.getClass().isArray())
+ return ((Object[]) lval)[((Number) rval).intValue()];
+ }
+
+ if (lval instanceof Dictionary<?, ?>)
+ return ((Dictionary<?, ?>) lval).get(rval);
+
+ throw new IllegalArgumentException("Unable to use [] on a " + lval.getClass().getName()); //$NON-NLS-1$
+ }
+
+ public Iterator<?> evaluateAsIterator(IEvaluationContext context) {
+ Object value = evaluate(context);
+ if (!(value instanceof Iterator<?>))
+ value = RepeatableIterator.create(value);
+ return (Iterator<?>) value;
+ }
+
+ public int getExpressionType() {
+ return TYPE_AT;
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ appendOperand(bld, rootVariable, lhs, getPriority());
+ bld.append('[');
+ appendOperand(bld, rootVariable, rhs, PRIORITY_MAX);
+ bld.append(']');
+ }
+
+ public String getOperator() {
+ return OPERATOR_AT;
+ }
+
+ public int getPriority() {
+ return PRIORITY_MEMBER;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Binary.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Binary.java
new file mode 100644
index 000000000..f4f7da504
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Binary.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.internal.p2.metadata.expression.Member.DynamicMember;
+import org.eclipse.equinox.p2.metadata.Version;
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * The abstract base class for all binary operations
+ */
+public abstract class Binary extends Expression {
+ public final Expression lhs;
+
+ public final Expression rhs;
+
+ protected Binary(Expression lhs, Expression rhs) {
+ this.lhs = lhs;
+ this.rhs = rhs;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return super.accept(visitor) && lhs.accept(visitor) && rhs.accept(visitor);
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0) {
+ Binary be = (Binary) e;
+ cmp = lhs.compareTo(be.lhs);
+ if (cmp == 0)
+ cmp = rhs.compareTo(be.rhs);
+ }
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ if (super.equals(o)) {
+ Binary bo = (Binary) o;
+ return lhs.equals(bo.lhs) && rhs.equals(bo.rhs);
+ }
+ return false;
+ }
+
+ public int getPriority() {
+ return PRIORITY_BINARY; // Default priority
+ }
+
+ public int hashCode() {
+ int result = 31 + lhs.hashCode();
+ return 31 * result + rhs.hashCode();
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ appendOperand(bld, rootVariable, lhs, getPriority());
+ bld.append(' ');
+ bld.append(getOperator());
+ bld.append(' ');
+ appendOperand(bld, rootVariable, rhs, getPriority());
+ }
+
+ /**
+ * Appends the LDAP filter attribute name from the lhs expression if
+ * possible.
+ * @throws UnsupportedOperationException when this expression does not conform to an
+ * LDAP filter binary expression
+ */
+ void appendLDAPAttribute(StringBuffer buf) {
+ if (lhs instanceof DynamicMember) {
+ DynamicMember attr = (DynamicMember) lhs;
+ if (attr.operand instanceof Variable) {
+ buf.append(attr.getName());
+ return;
+ }
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ private static char hexChar(int value) {
+ return (char) (value < 10 ? ('0' + value) : ('a' + (value - 10)));
+ }
+
+ static void appendLDAPEscaped(StringBuffer bld, String str) {
+ appendLDAPEscaped(bld, str, true);
+ }
+
+ static void appendLDAPEscaped(StringBuffer bld, String str, boolean escapeWild) {
+ int top = str.length();
+ for (int idx = 0; idx < top; ++idx) {
+ char c = str.charAt(idx);
+ if (!escapeWild) {
+ if (c == '*') {
+ bld.append(c);
+ continue;
+ } else if (c == '\\' && idx + 1 < top && str.charAt(idx + 1) == '*') {
+ bld.append("\\2a"); //$NON-NLS-1$
+ ++idx;
+ continue;
+ }
+ }
+ if (c == '(' || c == ')' || c == '*' || c == '\\' || c < ' ' || c > 127) {
+ short cs = (short) c;
+ bld.append('\\');
+ bld.append(hexChar((cs & 0x00f0) >> 4));
+ bld.append(hexChar(cs & 0x000f));
+ } else
+ bld.append(c);
+ }
+ }
+
+ /**
+ * Appends the LDAP filter value from the rhs expression if
+ * possible.
+ * @throws UnsupportedOperationException when this expression does not conform to an
+ * LDAP filter binary expression
+ */
+ void appendLDAPValue(StringBuffer buf) {
+ if (rhs instanceof Literal) {
+ Object value = rhs.evaluate(null);
+ if (value instanceof String || value instanceof Version) {
+ appendLDAPEscaped(buf, value.toString());
+ return;
+ }
+ }
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CoercingComparator.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CoercingComparator.java
new file mode 100644
index 000000000..fde3c4eee
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CoercingComparator.java
@@ -0,0 +1,392 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Comparator;
+import org.eclipse.equinox.internal.p2.metadata.MetadataActivator;
+import org.eclipse.equinox.p2.metadata.Version;
+
+/**
+ * A comparator that performs coercion if needed before comparison.
+ * @param <T> The type for the comparator.
+ */
+public abstract class CoercingComparator<T> {
+ static class BooleanCoercer extends CoercingComparator<Boolean> {
+ public int compare(Boolean o1, Boolean o2) {
+ return o1.compareTo(o2);
+ }
+
+ @Override
+ Boolean coerce(Object v) {
+ if (v instanceof Boolean)
+ return (Boolean) v;
+ if (v instanceof String) {
+ String sv = ((String) v).trim();
+ if (sv.equalsIgnoreCase("true")) //$NON-NLS-1$
+ return Boolean.TRUE;
+ if (sv.equalsIgnoreCase("false")) //$NON-NLS-1$
+ return Boolean.FALSE;
+ }
+ throw uncoercable(v);
+ }
+
+ @Override
+ Class<Boolean> getCoerceClass() {
+ return Boolean.class;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 7;
+ }
+ }
+
+ static class ClassCoercer extends CoercingComparator<Class<?>> {
+ public int compare(Class<?> o1, Class<?> o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+
+ @Override
+ Class<?> coerce(Object v) {
+ if (v instanceof Class<?>)
+ return (Class<?>) v;
+ if (v instanceof String) {
+ try {
+ return MetadataActivator.context.getBundle().loadClass(((String) v).trim());
+ } catch (Exception e) {
+ //
+ }
+ }
+ throw uncoercable(v);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ Class<Class<?>> getCoerceClass() {
+ Class<?> cls = Class.class;
+ return (Class<Class<?>>) cls;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 11;
+ }
+ }
+
+ static class FromStringCoercer<T extends Comparable<Object>> extends CoercingComparator<T> {
+ private final Class<T> coerceClass;
+ private final Constructor<T> constructor;
+
+ public FromStringCoercer(Class<T> coerceClass, Constructor<T> constructor) {
+ this.coerceClass = coerceClass;
+ this.constructor = constructor;
+ }
+
+ @Override
+ T coerce(Object v) {
+ if (v instanceof String) {
+ try {
+ return constructor.newInstance(new Object[] {((String) v).trim()});
+ } catch (Exception e) {
+ //
+ }
+ }
+ throw uncoercable(v);
+ }
+
+ @Override
+ int compare(T o1, T o2) {
+ return o1.compareTo(o2);
+ }
+
+ @Override
+ Class<T> getCoerceClass() {
+ return coerceClass;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 0;
+ }
+ }
+
+ static class IntegerCoercer extends CoercingComparator<Integer> {
+ public int compare(Integer o1, Integer o2) {
+ return o1.compareTo(o2);
+ }
+
+ @Override
+ Integer coerce(Object v) {
+ if (v instanceof Integer)
+ return (Integer) v;
+ if (v instanceof Number)
+ return new Integer(((Number) v).intValue());
+ if (v instanceof String) {
+ try {
+ return Integer.valueOf(((String) v).trim());
+ } catch (NumberFormatException e) {
+ //
+ }
+ }
+ throw uncoercable(v);
+ }
+
+ @Override
+ Class<Integer> getCoerceClass() {
+ return Integer.class;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 6;
+ }
+ }
+
+ static class LongCoercer extends CoercingComparator<Long> {
+ public int compare(Long o1, Long o2) {
+ return o1.compareTo(o2);
+ }
+
+ @Override
+ Long coerce(Object v) {
+ if (v instanceof Long)
+ return (Long) v;
+ if (v instanceof Number)
+ return new Long(((Number) v).longValue());
+ if (v instanceof String) {
+ try {
+ return Long.valueOf(((String) v).trim());
+ } catch (NumberFormatException e) {
+ //
+ }
+ }
+ throw uncoercable(v);
+ }
+
+ @Override
+ Class<Long> getCoerceClass() {
+ return Long.class;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 5;
+ }
+ }
+
+ static class StringCoercer extends CoercingComparator<String> {
+ public int compare(String o1, String o2) {
+ return o1.compareTo(o2);
+ }
+
+ @Override
+ String coerce(Object v) {
+ if (v instanceof Class<?>)
+ return ((Class<?>) v).getName();
+ return v.toString();
+ }
+
+ @Override
+ Class<String> getCoerceClass() {
+ return String.class;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 10;
+ }
+ }
+
+ static class VersionCoercer extends CoercingComparator<Version> {
+ public int compare(Version o1, Version o2) {
+ return o1.compareTo(o2);
+ }
+
+ boolean canCoerceTo(Class<?> cls) {
+ return Version.class.isAssignableFrom(cls);
+ }
+
+ @Override
+ Version coerce(Object v) {
+ if (v instanceof Version)
+ return (Version) v;
+ if (v instanceof String)
+ return Version.create((String) v);
+ if (v instanceof String) {
+ try {
+ return Version.create((String) v);
+ } catch (NumberFormatException e) {
+ //
+ }
+ }
+ throw uncoercable(v);
+ }
+
+ @Override
+ Class<Version> getCoerceClass() {
+ return Version.class;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 1;
+ }
+ }
+
+ private static class SetAccessibleAction implements PrivilegedAction<Object> {
+ private final AccessibleObject accessible;
+
+ SetAccessibleAction(AccessibleObject accessible) {
+ this.accessible = accessible;
+ }
+
+ public Object run() {
+ accessible.setAccessible(true);
+ return null;
+ }
+ }
+
+ private static CoercingComparator<?>[] coercers = {new ClassCoercer(), new BooleanCoercer(), new LongCoercer(), new IntegerCoercer(), new VersionCoercer(), new StringCoercer()};
+
+ private static final Class<?>[] constructorType = new Class<?>[] {String.class};
+
+ /**
+ * Finds the comparator for <code>a</code> and <code>b</code> and delegates the coercion/comparison to the comparator
+ * according to priority.
+ * @param o1 the first object to be compared.
+ * @param o2 the second object to be compared.
+ * @return The result of the comparison
+ * @throws IllegalArgumentException if no comparator was found or if coercion was impossible
+ * @see Comparator#compare(Object, Object)
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public static <TA extends Object, TB extends Object> int coerceAndCompare(TA o1, TB o2) throws IllegalArgumentException {
+ if (o1 == null || o2 == null)
+ throw new IllegalArgumentException("Cannot compare null to anything"); //$NON-NLS-1$
+
+ if (o1 instanceof Comparable && o1.getClass().isAssignableFrom(o2.getClass()))
+ return ((Comparable) o1).compareTo(o2);
+
+ if (o2 instanceof Comparable && o2.getClass().isAssignableFrom(o1.getClass()))
+ return -((Comparable) o2).compareTo(o1);
+
+ CoercingComparator<TA> ca = getComparator(o1, o2);
+ CoercingComparator<TB> cb = getComparator(o2, o1);
+ return ca.getCoercePrio() <= cb.getCoercePrio() ? ca.compare(o1, ca.coerce(o2)) : cb.compare(cb.coerce(o1), o2);
+ }
+
+ /**
+ * Finds the comparator for <code>a</code> and <code>b</code> and delegates the coercion/equal to the comparator
+ * according to priority.
+ * @param o1 the first object to be compared.
+ * @param o2 the second object to be compared.
+ * @return The result of the equality test
+ * @throws IllegalArgumentException if no comparator was found or if coercion was impossible
+ * @see Object#equals(Object)
+ */
+ public static <TA extends Object, TB extends Object> boolean coerceAndEquals(TA o1, TB o2) throws IllegalArgumentException {
+ if (o1 == o2)
+ return true;
+
+ if (o1 == null || o2 == null)
+ return false;
+
+ if (o1.getClass() != o2.getClass()) {
+ if (o1.getClass().isAssignableFrom(o2.getClass()))
+ return o1.equals(o2);
+ if (o2.getClass().isAssignableFrom(o1.getClass()))
+ return o2.equals(o1);
+ try {
+ CoercingComparator<TA> ca = getComparator(o1, o2);
+ CoercingComparator<TB> cb = getComparator(o2, o1);
+ return ca.getCoercePrio() <= cb.getCoercePrio() ? o1.equals(ca.coerce(o2)) : o2.equals(cb.coerce(o1));
+ } catch (IllegalArgumentException e) {
+ //
+ }
+ }
+ return o1.equals(o2);
+ }
+
+ /**
+ * Obtains the coercing comparator for the given <code>value</code>.
+ * @param value The value
+ * @return The coercing comparator
+ */
+ @SuppressWarnings("unchecked")
+ public static <V extends Object> CoercingComparator<V> getComparator(V value, Object v2) {
+ Class<V> vClass = (Class<V>) value.getClass();
+ CoercingComparator<?>[] carr = coercers;
+ int idx = carr.length;
+ while (--idx >= 0) {
+ CoercingComparator<?> c = carr[idx];
+ if (c.canCoerceTo(vClass)) {
+ CoercingComparator<V> cv = (CoercingComparator<V>) c;
+ return cv;
+ }
+ }
+
+ if (value instanceof Comparable<?> && v2 instanceof String) {
+ Class<Comparable<Object>> cClass = (Class<Comparable<Object>>) vClass;
+ Constructor<Comparable<Object>> constructor;
+ try {
+ constructor = cClass.getConstructor(constructorType);
+ if (!constructor.isAccessible())
+ AccessController.doPrivileged(new SetAccessibleAction(constructor));
+ synchronized (CoercingComparator.class) {
+ int top = coercers.length;
+ CoercingComparator<?>[] nc = new CoercingComparator<?>[top + 1];
+ System.arraycopy(coercers, 0, nc, 1, top);
+ CoercingComparator<V> cv = (CoercingComparator<V>) new FromStringCoercer<Comparable<Object>>(cClass, constructor);
+ nc[0] = cv;
+ coercers = nc;
+ return cv;
+ }
+ } catch (Exception e) {
+ //
+ }
+ }
+ throw new IllegalArgumentException("No comparator for " + vClass.getName()); //$NON-NLS-1$
+ }
+
+ protected IllegalArgumentException uncoercable(Object v) {
+ StringBuffer sb = new StringBuffer("Cannot coerce "); //$NON-NLS-1$
+ if (v instanceof String) {
+ sb.append('\'');
+ sb.append(v);
+ sb.append('\'');
+ } else if (v instanceof Number) {
+ sb.append("number "); //$NON-NLS-1$
+ sb.append(v);
+ } else {
+ sb.append("an object of instance "); //$NON-NLS-1$
+ sb.append(v.getClass().getName());
+ }
+ sb.append(" into a "); //$NON-NLS-1$
+ sb.append(getCoerceClass().getName());
+ return new IllegalArgumentException(sb.toString());
+ }
+
+ boolean canCoerceTo(Class<?> cls) {
+ return cls == getCoerceClass();
+ }
+
+ abstract T coerce(Object v);
+
+ abstract int compare(T o1, T o2);
+
+ abstract Class<T> getCoerceClass();
+
+ abstract int getCoercePrio();
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CollectionFilter.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CollectionFilter.java
new file mode 100644
index 000000000..bb845d373
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CollectionFilter.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.Iterator;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * Some kind of operation that is performed for each element of a collection. I.e.
+ * <code>x.&lt;operation&gt;(y | &lt;expression&rt;)</code>
+ */
+public abstract class CollectionFilter extends Unary {
+ public static void appendProlog(StringBuffer bld, Variable rootVariable, Expression lhs, String operator) {
+ if (lhs != rootVariable) {
+ appendOperand(bld, rootVariable, lhs, PRIORITY_COLLECTION);
+ bld.append('.');
+ }
+ bld.append(operator);
+ bld.append('(');
+ }
+
+ public final LambdaExpression lambda;
+
+ protected CollectionFilter(Expression collection, LambdaExpression lambda) {
+ super(collection);
+ this.lambda = lambda;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return super.accept(visitor) && lambda.accept(visitor);
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = lambda.compareTo(((CollectionFilter) e).lambda);
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && lambda.equals(((CollectionFilter) o).lambda);
+ }
+
+ public final Object evaluate(IEvaluationContext context) {
+ Iterator<?> lval = operand.evaluateAsIterator(context);
+ context = lambda.prolog(context);
+ return evaluate(context, lval);
+ }
+
+ public final Iterator<?> evaluateAsIterator(IEvaluationContext context) {
+ Iterator<?> lval = operand.evaluateAsIterator(context);
+ context = lambda.prolog(context);
+ return evaluateAsIterator(context, lval);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ appendProlog(bld, rootVariable, operand, getOperator());
+ appendOperand(bld, rootVariable, lambda, PRIORITY_LAMBDA);
+ bld.append(')');
+ }
+
+ public int hashCode() {
+ int result = 31 + operand.hashCode();
+ return 31 * result + lambda.hashCode();
+ }
+
+ public int getPriority() {
+ return PRIORITY_COLLECTION;
+ }
+
+ protected abstract Object evaluate(final IEvaluationContext context, Iterator<?> iterator);
+
+ protected Iterator<?> evaluateAsIterator(IEvaluationContext context, Iterator<?> iterator) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Compare.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Compare.java
new file mode 100644
index 000000000..7e958e41d
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Compare.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * Comparisons for magnitude.
+ */
+final class Compare extends Binary {
+ static IllegalArgumentException uncomparable(Object lval, Object rval) {
+ return new IllegalArgumentException("Cannot compare a " + lval.getClass().getName() + " to a " + rval.getClass().getName()); //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ final boolean compareLess;
+
+ final boolean equalOK;
+
+ Compare(Expression lhs, Expression rhs, boolean compareLess, boolean equalOK) {
+ super(lhs, rhs);
+ this.compareLess = compareLess;
+ this.equalOK = equalOK;
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ int cmpResult = CoercingComparator.coerceAndCompare(lhs.evaluate(context), rhs.evaluate(context));
+ return Boolean.valueOf(cmpResult == 0 ? equalOK : (cmpResult < 0 ? compareLess : !compareLess));
+ }
+
+ public int getExpressionType() {
+ return compareLess ? (equalOK ? TYPE_LESS_EQUAL : TYPE_LESS) : (equalOK ? TYPE_GREATER_EQUAL : TYPE_GREATER);
+ }
+
+ public String getOperator() {
+ return compareLess ? (equalOK ? OPERATOR_LT_EQUAL : OPERATOR_LT) : (equalOK ? OPERATOR_GT_EQUAL : OPERATOR_GT);
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ if (!equalOK)
+ buf.append("(!"); //$NON-NLS-1$
+ buf.append('(');
+ appendLDAPAttribute(buf);
+ if (equalOK)
+ buf.append(compareLess ? OPERATOR_LT_EQUAL : OPERATOR_GT_EQUAL);
+ else
+ buf.append(compareLess ? OPERATOR_GT_EQUAL : OPERATOR_LT_EQUAL);
+ appendLDAPValue(buf);
+ buf.append(')');
+ if (!equalOK)
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Equals.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Equals.java
new file mode 100644
index 000000000..7452c3165
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Equals.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * An expression that performs the == and != comparisons
+ */
+final class Equals extends Binary {
+ final boolean negate;
+
+ Equals(Expression lhs, Expression rhs, boolean negate) {
+ super(lhs, rhs);
+ this.negate = negate;
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ boolean result = CoercingComparator.coerceAndEquals(lhs.evaluate(context), rhs.evaluate(context));
+ if (negate)
+ result = !result;
+ return Boolean.valueOf(result);
+ }
+
+ public int getExpressionType() {
+ return negate ? TYPE_NOT_EQUALS : TYPE_EQUALS;
+ }
+
+ public String getOperator() {
+ return negate ? OPERATOR_NOT_EQUALS : OPERATOR_EQUALS;
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ if (negate)
+ buf.append("(!"); //$NON-NLS-1$
+ buf.append('(');
+ appendLDAPAttribute(buf);
+ buf.append('=');
+ appendLDAPValue(buf);
+ buf.append(')');
+ if (negate)
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/EvaluationContext.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/EvaluationContext.java
new file mode 100644
index 000000000..8fb3665bc
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/EvaluationContext.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.IExpression;
+
+/**
+ * Highly specialized evaluation contexts optimized for misc purposes
+ */
+public class EvaluationContext implements IEvaluationContext {
+ public static class Parameters extends EvaluationContext {
+ private static final Object[] noParameters = new Object[0];
+
+ private final Object[] parameters;
+
+ public Parameters(IEvaluationContext parentContext, Object[] parameters) {
+ super(parentContext);
+ this.parameters = parameters == null ? noParameters : parameters;
+ }
+
+ public Object getParameter(int position) {
+ return position >= 0 && position < parameters.length ? parameters[position] : super.getParameter(position);
+ }
+ }
+
+ public static class SingleVariableContext implements IEvaluationContext {
+ private final IEvaluationContext parentContext;
+
+ private Object value;
+
+ private final IExpression variable;
+
+ public SingleVariableContext(IEvaluationContext parentContext, IExpression variable) {
+ this.parentContext = parentContext;
+ this.variable = variable;
+ }
+
+ public Object getParameter(int position) {
+ return parentContext.getParameter(position);
+ }
+
+ public Object getValue(IExpression var) {
+ return variable == var ? value : parentContext.getValue(var);
+ }
+
+ public void setValue(IExpression var, Object val) {
+ if (variable == var)
+ value = val;
+ else
+ parentContext.setValue(var, val);
+ }
+ }
+
+ static class MultiVariableContext implements IEvaluationContext {
+ private final IEvaluationContext parentContext;
+
+ private final Object[] values;
+
+ public MultiVariableContext(IEvaluationContext parentContext, IExpression[] variables) {
+ this.parentContext = parentContext;
+ values = new Object[variables.length * 2];
+ for (int idx = 0, ndx = 0; ndx < variables.length; ++ndx, idx += 2)
+ values[idx] = variables[ndx];
+ }
+
+ public Object getParameter(int position) {
+ return parentContext.getParameter(position);
+ }
+
+ public Object getValue(IExpression variable) {
+ for (int idx = 0; idx < values.length; ++idx)
+ if (values[idx++] == variable)
+ return values[idx];
+ return parentContext.getValue(variable);
+ }
+
+ public void setValue(IExpression variable, Object value) {
+ for (int idx = 0; idx < values.length; ++idx)
+ if (values[idx++] == variable) {
+ values[idx] = value;
+ return;
+ }
+ parentContext.setValue(variable, value);
+ }
+ }
+
+ public static final EvaluationContext INSTANCE = new EvaluationContext(null);
+
+ public static IEvaluationContext create() {
+ return INSTANCE;
+ }
+
+ public static IEvaluationContext create(IEvaluationContext parent, IExpression variable) {
+ return new SingleVariableContext(parent, variable);
+ }
+
+ public static IEvaluationContext create(IEvaluationContext parent, IExpression[] variables) {
+ return variables.length == 1 ? new SingleVariableContext(parent, variables[0]) : new MultiVariableContext(parent, variables);
+ }
+
+ public static IEvaluationContext create(IEvaluationContext parent, Object[] parameters) {
+ return new Parameters(parent, parameters);
+ }
+
+ public static IEvaluationContext create(IExpression variable) {
+ return new SingleVariableContext(null, variable);
+ }
+
+ public static IEvaluationContext create(IExpression[] variables) {
+ if (variables == null || variables.length == 0)
+ return INSTANCE;
+ return variables.length == 1 ? create(variables[0]) : new MultiVariableContext(INSTANCE, variables);
+ }
+
+ public static IEvaluationContext create(Object[] parameters, IExpression variable) {
+ return parameters == null || parameters.length == 0 ? create(variable) : new SingleVariableContext(new Parameters(null, parameters), variable);
+ }
+
+ public static IEvaluationContext create(Object[] parameters, IExpression[] variables) {
+ if (parameters == null || parameters.length == 0)
+ return create(variables);
+
+ Parameters pctx = new Parameters(null, parameters);
+ if (variables == null || variables.length == 0)
+ return pctx;
+ return create(pctx, variables);
+ }
+
+ protected EvaluationContext(IEvaluationContext parentContext) {
+ this.parentContext = parentContext;
+ }
+
+ private final IEvaluationContext parentContext;
+
+ public Object getParameter(int position) {
+ if (parentContext == null)
+ throw new IllegalArgumentException("No such parameter: $" + position); //$NON-NLS-1$
+ return parentContext.getParameter(position);
+ }
+
+ public Object getValue(IExpression variable) {
+ if (parentContext == null)
+ throw new IllegalArgumentException("No such variable: " + variable); //$NON-NLS-1$
+ return parentContext.getValue(variable);
+ }
+
+ public void setValue(IExpression variable, Object value) {
+ if (parentContext == null)
+ throw new IllegalArgumentException("No such variable: " + variable); //$NON-NLS-1$
+ parentContext.setValue(variable, value);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Exists.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Exists.java
new file mode 100644
index 000000000..14fbc5df4
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Exists.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.Iterator;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * A collection filter that yields true if the <code>filter</code> yields true for
+ * any of the elements of the <code>collection</code>
+ */
+final class Exists extends CollectionFilter {
+ Exists(Expression collection, LambdaExpression lambda) {
+ super(collection, lambda);
+ }
+
+ protected Object evaluate(IEvaluationContext context, Iterator<?> itor) {
+ Variable variable = lambda.getItemVariable();
+ while (itor.hasNext()) {
+ variable.setValue(context, itor.next());
+ if (lambda.evaluate(context) == Boolean.TRUE)
+ return Boolean.TRUE;
+ }
+ return Boolean.FALSE;
+ }
+
+ public int getExpressionType() {
+ return TYPE_EXISTS;
+ }
+
+ public String getOperator() {
+ return KEYWORD_EXISTS;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Expression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Expression.java
new file mode 100644
index 000000000..75f47dd94
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Expression.java
@@ -0,0 +1,341 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.*;
+import org.eclipse.equinox.p2.metadata.expression.*;
+
+/**
+ * The base class of the expression tree.
+ */
+public abstract class Expression implements IExpression, Comparable<Expression>, IExpressionConstants {
+
+ static final Expression[] emptyArray = new Expression[0];
+
+ public static void appendOperand(StringBuffer bld, Variable rootVariable, Expression operand, int priority) {
+ if (priority < operand.getPriority()) {
+ bld.append('(');
+ operand.toString(bld, rootVariable);
+ bld.append(')');
+ } else
+ operand.toString(bld, rootVariable);
+ }
+
+ public static Expression[] assertLength(Expression[] operands, int minLength, int maxLength, String operand) {
+ if (operands == null)
+ operands = emptyArray;
+ if (operands.length < minLength)
+ throw new IllegalArgumentException("Not enough operands for " + operand); //$NON-NLS-1$
+ if (operands.length > maxLength)
+ throw new IllegalArgumentException("Too many operands for " + operand); //$NON-NLS-1$
+ return operands;
+ }
+
+ public static Expression[] assertLength(Expression[] operands, int length, String operand) {
+ if (operands == null)
+ operands = emptyArray;
+ if (operands.length < length)
+ throw new IllegalArgumentException("Not enough operands for " + operand); //$NON-NLS-1$
+ return operands;
+ }
+
+ public static int compare(Expression[] arr1, Expression[] arr2) {
+ int max = arr1.length;
+ if (max > arr2.length)
+ max = arr2.length;
+ for (int idx = 0; idx < max; ++idx) {
+ int cmp = arr1[idx].compareTo(arr2[idx]);
+ if (cmp != 0)
+ return cmp;
+ }
+ if (max == arr2.length) {
+ if (max < arr1.length)
+ return 1;
+ return 0;
+ }
+ return -1;
+ }
+
+ public static boolean equals(Expression[] arr1, Expression[] arr2) {
+ int idx = arr1.length;
+ if (idx != arr2.length)
+ return false;
+ while (--idx >= 0)
+ if (!arr1[idx].equals(arr2[idx]))
+ return false;
+ return true;
+ }
+
+ public static int hashCode(Expression[] arr) {
+ int idx = arr.length;
+ int result = 1;
+ while (--idx >= 0)
+ result = 31 * result + arr[idx].hashCode();
+ return result;
+ }
+
+ public static void elementsToString(StringBuffer bld, Variable rootVariable, Expression[] elements) {
+ int top = elements.length;
+ if (top > 0) {
+ elements[0].toString(bld, rootVariable);
+ for (int idx = 1; idx < top; ++idx) {
+ bld.append(", "); //$NON-NLS-1$
+ appendOperand(bld, rootVariable, elements[idx], PRIORITY_MAX);
+ }
+ }
+ }
+
+ /**
+ * Let the visitor visit this instance and all expressions that this
+ * instance contains.
+ * @param visitor The visiting visitor.
+ * @return <code>true</code> if the visitor should continue visiting, <code>false</code> otherwise.
+ */
+ public boolean accept(IExpressionVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = getPriority() - e.getPriority();
+ if (cmp == 0) {
+ int e1 = getExpressionType();
+ int e2 = e.getExpressionType();
+ cmp = e1 > e2 ? 1 : (e1 == e2 ? 0 : -1);
+ }
+ return cmp;
+ }
+
+ public boolean equals(Object e) {
+ if (e == this)
+ return true;
+ if (e == null || getClass() != e.getClass())
+ return false;
+ return getExpressionType() == ((Expression) e).getExpressionType();
+ }
+
+ /**
+ * Evaluate this expression with given context and variables.
+ * @param context The evaluation context
+ * @return The result of the evaluation.
+ */
+ public abstract Object evaluate(IEvaluationContext context);
+
+ public Iterator<?> evaluateAsIterator(IEvaluationContext context) {
+ Object value = evaluate(context);
+ if (!(value instanceof Iterator<?>))
+ value = RepeatableIterator.create(value);
+ return (Iterator<?>) value;
+ }
+
+ public abstract String getOperator();
+
+ public abstract int getPriority();
+
+ public boolean isRootVariable() {
+ return false;
+ }
+
+ public final String toLDAPString() {
+ StringBuffer bld = new StringBuffer();
+ toLDAPString(bld);
+ return bld.toString();
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ throw new UnsupportedOperationException();
+ }
+
+ public final String toString() {
+ StringBuffer bld = new StringBuffer();
+ toString(bld);
+ return bld.toString();
+ }
+
+ public void toString(StringBuffer bld) {
+ toString(bld, ExpressionFactory.THIS);
+ }
+
+ public abstract void toString(StringBuffer bld, Variable rootVariable);
+
+ private static class Compacter {
+ private Expression base;
+
+ private List<Expression> parts;
+
+ private int op;
+
+ Compacter(Expression base, int op) {
+ this.base = base;
+ this.op = op;
+ }
+
+ Expression getResultingFilter() {
+ if (parts == null)
+ return base;
+
+ int partsOp = op == TYPE_AND ? TYPE_OR : TYPE_AND;
+ return addFilter(base, normalize(parts, partsOp), op);
+ }
+
+ boolean merge(Expression b) {
+ Expression[] aArr;
+ Expression[] bArr;
+ if (base.getExpressionType() == op)
+ aArr = getFilterImpls(base);
+ else
+ aArr = new Expression[] {base};
+
+ if (b.getExpressionType() == op)
+ bArr = getFilterImpls(b);
+ else
+ bArr = new Expression[] {b};
+
+ List<Expression> common = null;
+ List<Expression> onlyA = null;
+
+ int atop = aArr.length;
+ int btop = bArr.length;
+ int aidx;
+ int bidx;
+ for (aidx = 0; aidx < atop; ++aidx) {
+ Expression af = aArr[aidx];
+ for (bidx = 0; bidx < btop; ++bidx) {
+ Expression bf = bArr[bidx];
+ if (af.equals(bf)) {
+ if (common == null)
+ common = new ArrayList<Expression>();
+ common.add(af);
+ break;
+ }
+ }
+ if (bidx == btop) {
+ if (onlyA == null)
+ onlyA = new ArrayList<Expression>();
+ onlyA.add(af);
+ }
+ }
+ if (common == null)
+ // Nothing in common
+ return false;
+
+ if (onlyA == null && parts == null)
+ return true;
+
+ List<Expression> onlyB = null;
+ for (bidx = 0; bidx < btop; ++bidx) {
+ Expression bf = bArr[bidx];
+ for (aidx = 0; aidx < atop; ++aidx)
+ if (bf.equals(aArr[aidx]))
+ break;
+ if (aidx == atop) {
+ if (onlyB == null)
+ onlyB = new ArrayList<Expression>();
+ onlyB.add(bf);
+ }
+ }
+
+ if (onlyB == null && parts == null) {
+ // All of B is already covered by base
+ base = b;
+ return true;
+ }
+
+ if (parts == null)
+ parts = new ArrayList<Expression>();
+
+ if (onlyA != null) {
+ base = normalize(common, op);
+ Expression af = normalize(onlyA, op);
+ if (!parts.contains(af))
+ parts.add(af);
+ }
+ Expression bf = normalize(onlyB, op);
+ if (!parts.contains(bf))
+ parts.add(bf);
+ return true;
+ }
+ }
+
+ static Expression addFilter(Expression base, Expression subFilter, int expressionType) {
+ if (base.equals(subFilter))
+ return base;
+
+ ArrayList<Expression> filters = new ArrayList<Expression>(2);
+ filters.add(base);
+ filters.add(subFilter);
+ return normalize(filters, expressionType);
+ }
+
+ static Expression normalize(List<Expression> operands, int op) {
+ int top = operands.size();
+ if (top == 1)
+ return operands.get(0);
+
+ // a | (b | c) becomes a | b | c
+ // a & (b & c) becomes a & b & c
+ //
+ for (int idx = 0; idx < top; ++idx) {
+ Expression f = operands.get(idx);
+ if (f.getExpressionType() != op)
+ continue;
+
+ Expression[] sfs = getFilterImpls(f);
+ operands.remove(idx);
+ --top;
+ for (int ndx = 0; ndx < sfs.length; ++ndx) {
+ Expression nf = sfs[ndx];
+ if (!operands.contains(nf))
+ operands.add(nf);
+ }
+ }
+ top = operands.size();
+ if (top == 1)
+ return operands.get(0);
+
+ Collections.sort(operands);
+ List<Compacter> splits = new ArrayList<Compacter>();
+ int reverseOp = op == TYPE_AND ? TYPE_OR : TYPE_AND;
+
+ for (int idx = 0; idx < top; ++idx)
+ merge(splits, operands.get(idx), reverseOp);
+
+ operands.clear();
+ top = splits.size();
+ for (int idx = 0; idx < top; ++idx) {
+ Expression filter = splits.get(idx).getResultingFilter();
+ if (!operands.contains(filter))
+ operands.add(filter);
+ }
+ top = operands.size();
+ if (top == 1)
+ return operands.get(0);
+
+ Collections.sort(operands);
+ Expression[] expArray = operands.toArray(new Expression[top]);
+ return op == TYPE_AND ? new And(expArray) : new Or(expArray);
+ }
+
+ static void merge(List<Compacter> splits, Expression base, int op) {
+ int top = splits.size();
+ for (int idx = 0; idx < top; ++idx) {
+ Compacter split = splits.get(idx);
+ if (split.merge(base))
+ return;
+ }
+ splits.add(new Compacter(base, op));
+ }
+
+ static Expression[] getFilterImpls(Expression expression) {
+ if (expression instanceof NAry)
+ return ((NAry) expression).operands;
+ throw new IllegalArgumentException();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/ExpressionFactory.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/ExpressionFactory.java
new file mode 100644
index 000000000..a9e70e311
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/ExpressionFactory.java
@@ -0,0 +1,125 @@
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.List;
+import org.eclipse.equinox.p2.metadata.expression.*;
+
+public class ExpressionFactory implements IExpressionFactory, IExpressionConstants {
+ public static final IExpressionFactory INSTANCE = new ExpressionFactory();
+ public static final Variable THIS = new Variable(VARIABLE_THIS);
+
+ protected static Expression[] convertArray(IExpression[] operands) {
+ Expression[] ops = new Expression[operands.length];
+ System.arraycopy(operands, 0, ops, 0, operands.length);
+ return ops;
+ }
+
+ protected ExpressionFactory() {
+ // Maintain singleton
+ }
+
+ public IExpression all(IExpression collection, IExpression lambda) {
+ return new All((Expression) collection, (LambdaExpression) lambda);
+ }
+
+ public IExpression and(IExpression... operands) {
+ return new And(convertArray(operands));
+ }
+
+ public IExpression at(IExpression target, IExpression key) {
+ return new At((Expression) target, (Expression) key);
+ }
+
+ @SuppressWarnings("unchecked")
+ public IExpression normalize(List<? extends IExpression> operands, int expressionType) {
+ return Expression.normalize((List<Expression>) operands, expressionType);
+ }
+
+ public IExpression constant(Object value) {
+ return Literal.create(value);
+ }
+
+ public IEvaluationContext createContext(Object... parameters) {
+ return EvaluationContext.create(parameters, (Variable[]) null);
+ }
+
+ public IEvaluationContext createContext(IExpression[] variables, Object... parameters) {
+ return EvaluationContext.create(parameters, variables);
+ }
+
+ public IFilterExpression filterExpression(IExpression expression) {
+ return new LDAPFilter((Expression) expression);
+ }
+
+ public IExpression equals(IExpression lhs, IExpression rhs) {
+ return new Equals((Expression) lhs, (Expression) rhs, false);
+ }
+
+ public IExpression exists(IExpression collection, IExpression lambda) {
+ return new Exists((Expression) collection, (LambdaExpression) lambda);
+ }
+
+ public IExpression greater(IExpression lhs, IExpression rhs) {
+ return new Compare((Expression) lhs, (Expression) rhs, false, false);
+ }
+
+ public IExpression greaterEqual(IExpression lhs, IExpression rhs) {
+ return new Compare((Expression) lhs, (Expression) rhs, false, true);
+ }
+
+ public IExpression indexedParameter(int index) {
+ return new Parameter(index);
+ }
+
+ public IExpression lambda(IExpression variable, IExpression body) {
+ return new LambdaExpression((Variable) variable, (Expression) body);
+ }
+
+ public IExpression less(IExpression lhs, IExpression rhs) {
+ return new Compare((Expression) lhs, (Expression) rhs, true, false);
+ }
+
+ public IExpression lessEqual(IExpression lhs, IExpression rhs) {
+ return new Compare((Expression) lhs, (Expression) rhs, true, true);
+ }
+
+ public IExpression matches(IExpression lhs, IExpression rhs) {
+ return new Matches((Expression) lhs, (Expression) rhs);
+ }
+
+ public <T> IMatchExpression<T> matchExpression(IExpression expression, Object... parameters) {
+ return new MatchExpression<T>((Expression) expression, parameters);
+ }
+
+ public IExpression member(IExpression target, String name) {
+ return new Member.DynamicMember((Expression) target, name);
+ }
+
+ public IExpression not(IExpression operand) {
+ if (operand instanceof Equals) {
+ Equals eq = (Equals) operand;
+ return new Equals(eq.lhs, eq.rhs, !eq.negate);
+ }
+ if (operand instanceof Compare) {
+ Compare cmp = (Compare) operand;
+ return new Compare(cmp.lhs, cmp.rhs, !cmp.compareLess, !cmp.equalOK);
+ }
+ if (operand instanceof Not)
+ return ((Not) operand).operand;
+
+ return new Not((Expression) operand);
+ }
+
+ public IExpression or(IExpression... operands) {
+ return new Or(convertArray(operands));
+ }
+
+ public IExpression thisVariable() {
+ return THIS;
+ }
+
+ public IExpression variable(String name) {
+ if (VARIABLE_THIS.equals(name))
+ return THIS;
+ return new Variable(name);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IExpressionConstants.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IExpressionConstants.java
new file mode 100644
index 000000000..3da1dbfac
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IExpressionConstants.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+public interface IExpressionConstants {
+ String KEYWORD_ALL = "all"; //$NON-NLS-1$
+
+ String KEYWORD_BOOLEAN = "boolean"; //$NON-NLS-1$
+ String KEYWORD_CLASS = "class"; //$NON-NLS-1$
+ String KEYWORD_EXISTS = "exists"; //$NON-NLS-1$
+ String KEYWORD_FALSE = "false"; //$NON-NLS-1$
+ String KEYWORD_FILTER = "filter"; //$NON-NLS-1$
+ String KEYWORD_NULL = "null"; //$NON-NLS-1$
+ String KEYWORD_RANGE = "range"; //$NON-NLS-1$
+ String KEYWORD_TRUE = "true"; //$NON-NLS-1$
+
+ String KEYWORD_VERSION = "version"; //$NON-NLS-1$
+ String OPERATOR_AND = "&&"; //$NON-NLS-1$
+ String OPERATOR_AT = "[]"; //$NON-NLS-1$
+ String OPERATOR_EQUALS = "=="; //$NON-NLS-1$
+ String OPERATOR_GT = ">"; //$NON-NLS-1$
+ String OPERATOR_GT_EQUAL = ">="; //$NON-NLS-1$
+ String OPERATOR_LT = "<"; //$NON-NLS-1$
+ String OPERATOR_LT_EQUAL = "<="; //$NON-NLS-1$
+ String OPERATOR_MATCHES = "~="; //$NON-NLS-1$
+ String OPERATOR_MEMBER = "."; //$NON-NLS-1$
+ String OPERATOR_NOT = "!"; //$NON-NLS-1$
+ String OPERATOR_NOT_EQUALS = "!="; //$NON-NLS-1$
+
+ String OPERATOR_OR = "||"; //$NON-NLS-1$
+ String OPERATOR_PARAMETER = "$"; //$NON-NLS-1$
+
+ int PRIORITY_LITERAL = 1;
+ int PRIORITY_VARIABLE = 1;
+ int PRIORITY_FUNCTION = 2; // for extend query expressions
+ int PRIORITY_MEMBER = 3;
+ int PRIORITY_COLLECTION = 4;
+ int PRIORITY_NOT = 5;
+ int PRIORITY_BINARY = 6;
+ int PRIORITY_AND = 7;
+ int PRIORITY_OR = 8;
+ int PRIORITY_CONDITION = 9;
+ int PRIORITY_ASSIGNMENT = 10;
+ int PRIORITY_LAMBDA = 11;
+ int PRIORITY_COMMA = 12;
+ int PRIORITY_MAX = 20;
+
+ String VARIABLE_EVERYTHING = "everything"; //$NON-NLS-1$
+ String VARIABLE_THIS = "this"; //$NON-NLS-1$
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IRepeatableIterator.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IRepeatableIterator.java
new file mode 100644
index 000000000..f20e266bf
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IRepeatableIterator.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.Iterator;
+
+public interface IRepeatableIterator<T> extends Iterator<T> {
+ /**
+ * Returns a copy that will iterate over the same elements
+ * as this iterator. The contents or position of this iterator
+ * is left unchanged.
+ * @return A re-initialized copy of this iterator.
+ */
+ IRepeatableIterator<T> getCopy();
+
+ Object getIteratorProvider();
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPApproximation.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPApproximation.java
new file mode 100644
index 000000000..291a6ef6c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPApproximation.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.io.Serializable;
+
+/**
+ * Map a string for an LDAP APPROX (~=) comparison.
+ * This implementation removes white spaces and transforms everything to lower case.
+ */
+public final class LDAPApproximation implements Serializable, Comparable<LDAPApproximation> {
+ private static final long serialVersionUID = 4782295637798543587L;
+ private final String pattern;
+ private transient String approxPattern;
+
+ public LDAPApproximation(String pattern) {
+ this.pattern = pattern;
+ }
+
+ public int compareTo(LDAPApproximation o) {
+ return pattern.compareTo(o.pattern);
+ }
+
+ public boolean equals(Object o) {
+ return o == this || (o instanceof LDAPApproximation && ((LDAPApproximation) o).pattern.equals(pattern));
+ }
+
+ public int hashCode() {
+ return 3 * pattern.hashCode();
+ }
+
+ /**
+ * Matches the <code>value</code> with the compiled expression. The value
+ * is considered matching if all characters are matched by the expression. A
+ * partial match is not enough.
+ * @param value The value to match
+ * @return <code>true</code> if the value was a match.
+ */
+ public boolean isMatch(CharSequence value) {
+ if (value == null)
+ return false;
+ if (approxPattern == null)
+ approxPattern = approxString(pattern);
+ return approxString(value).equals(approxPattern);
+ }
+
+ public String toString() {
+ return pattern;
+ }
+
+ private static String approxString(CharSequence input) {
+ boolean changed = false;
+ char[] output = new char[input.length()];
+ int cursor = 0;
+ for (int i = 0, length = output.length; i < length; i++) {
+ char c = input.charAt(i);
+ if (Character.isWhitespace(c)) {
+ changed = true;
+ continue;
+ }
+ if (Character.isUpperCase(c)) {
+ changed = true;
+ c = Character.toLowerCase(c);
+ }
+ output[cursor++] = c;
+ }
+ return changed ? new String(output, 0, cursor) : input.toString();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPFilter.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPFilter.java
new file mode 100644
index 000000000..607334bea
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPFilter.java
@@ -0,0 +1,69 @@
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.Dictionary;
+import java.util.Map;
+import org.eclipse.equinox.p2.metadata.expression.*;
+import org.osgi.framework.Filter;
+import org.osgi.framework.ServiceReference;
+
+public class LDAPFilter extends Unary implements IFilterExpression {
+
+ LDAPFilter(Expression expression) {
+ super(expression);
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return operand.accept(visitor);
+ }
+
+ public boolean equals(Object o) {
+ return (o instanceof Filter && !(o instanceof LDAPFilter)) ? equals(ExpressionUtil.parseLDAP(o.toString())) : super.equals(o);
+ }
+
+ @Override
+ public String getOperator() {
+ return operand.getOperator();
+ }
+
+ @Override
+ public int getPriority() {
+ return operand.getPriority();
+ }
+
+ public int getExpressionType() {
+ return 0;
+ }
+
+ public boolean match(Map<String, ? extends Object> map) {
+ return isMatch(MemberProvider.create(map, true));
+ }
+
+ @SuppressWarnings("rawtypes")
+ public boolean match(Dictionary dictionary) {
+ return isMatch(dictionary == null ? MemberProvider.emptyProvider() : MemberProvider.create(dictionary, true));
+ }
+
+ private boolean isMatch(Object candidate) {
+ Variable self = ExpressionFactory.THIS;
+ IEvaluationContext ctx = EvaluationContext.create(self);
+ self.setValue(ctx, candidate);
+ return Boolean.TRUE == operand.evaluate(ctx);
+ }
+
+ public boolean match(ServiceReference reference) {
+ return isMatch(reference == null ? MemberProvider.emptyProvider() : MemberProvider.create(reference, true));
+ }
+
+ public boolean matchCase(Map<String, ? extends Object> map) {
+ return isMatch(map == null ? MemberProvider.emptyProvider() : MemberProvider.create(map, false));
+ }
+
+ @SuppressWarnings("rawtypes")
+ public boolean matchCase(Dictionary dictionary) {
+ return isMatch(dictionary == null ? MemberProvider.emptyProvider() : MemberProvider.create(dictionary, false));
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ operand.toLDAPString(bld);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LambdaExpression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LambdaExpression.java
new file mode 100644
index 000000000..c5bacf0ab
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LambdaExpression.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * A function that executes some code
+ */
+public class LambdaExpression extends Unary {
+ protected final Variable each;
+
+ protected LambdaExpression(Variable each, Expression body) {
+ super(body);
+ this.each = each;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return super.accept(visitor) && each.accept(visitor);
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = each.compareTo(((LambdaExpression) e).each);
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && each.equals(((LambdaExpression) o).each);
+ }
+
+ public int hashCode() {
+ int result = 31 + operand.hashCode();
+ return 31 * result + each.hashCode();
+ }
+
+ public int getExpressionType() {
+ return TYPE_LAMBDA;
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ each.toString(bld, rootVariable);
+ bld.append(" | "); //$NON-NLS-1$
+ appendOperand(bld, rootVariable, operand, IExpressionConstants.PRIORITY_COMMA);
+ }
+
+ public Variable getItemVariable() {
+ return each;
+ }
+
+ public String getOperator() {
+ return "|"; //$NON-NLS-1$
+ }
+
+ public int getPriority() {
+ return IExpressionConstants.PRIORITY_LAMBDA;
+ }
+
+ public IEvaluationContext prolog(IEvaluationContext context) {
+ return EvaluationContext.create(context, each);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Literal.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Literal.java
new file mode 100644
index 000000000..2884fb757
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Literal.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.Version;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.SimplePattern;
+import org.osgi.framework.Filter;
+
+/**
+ * An expression that represents a constant value.
+ */
+public final class Literal extends Expression {
+ public static final Literal FALSE_CONSTANT = new Literal(Boolean.FALSE);
+
+ public static final Literal NULL_CONSTANT = new Literal(null);
+
+ public static final Literal TRUE_CONSTANT = new Literal(Boolean.TRUE);
+
+ static Literal create(Object value) {
+ if (value == null)
+ return NULL_CONSTANT;
+ if (value == Boolean.TRUE)
+ return TRUE_CONSTANT;
+ if (value == Boolean.FALSE)
+ return FALSE_CONSTANT;
+ return new Literal(value);
+ }
+
+ public final Object value;
+
+ private Literal(Object value) {
+ this.value = value;
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp != 0)
+ return cmp;
+
+ Object eValue = ((Literal) e).value;
+ if (value == null)
+ return eValue == null ? 0 : -1;
+
+ if (eValue == null)
+ return 1;
+
+ if (eValue.getClass() == value.getClass())
+ return ((Comparable) value).compareTo(eValue);
+
+ return eValue.getClass().getName().compareTo(value.getClass().getName());
+ }
+
+ public boolean equals(Object o) {
+ if (super.equals(o)) {
+ Literal bo = (Literal) o;
+ return value == null ? bo.value == null : value.equals(bo.value);
+ }
+ return false;
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return value;
+ }
+
+ public int getExpressionType() {
+ return TYPE_LITERAL;
+ }
+
+ public String getOperator() {
+ return "<literal>"; //$NON-NLS-1$
+ }
+
+ public int getPriority() {
+ return PRIORITY_LITERAL;
+ }
+
+ public int hashCode() {
+ return 31 + value.hashCode();
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ if (!(value instanceof Filter))
+ throw new UnsupportedOperationException();
+ buf.append(value.toString());
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ if (value == null)
+ bld.append("null"); //$NON-NLS-1$
+ else if (value instanceof String || value instanceof Version) {
+ String str = value.toString();
+ char sep = str.indexOf('\'') >= 0 ? '"' : '\'';
+ bld.append(sep);
+ bld.append(str);
+ bld.append(sep);
+ } else if (value instanceof SimplePattern) {
+ appendEscaped(bld, '/', value.toString());
+ } else
+ bld.append(value);
+ }
+
+ private void appendEscaped(StringBuffer bld, char delimiter, String str) {
+ bld.append(delimiter);
+ int top = str.length();
+ for (int idx = 0; idx < top; ++idx) {
+ char c = str.charAt(idx);
+ if (c == delimiter)
+ bld.append('\\');
+ bld.append(c);
+ }
+ bld.append(delimiter);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MatchExpression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MatchExpression.java
new file mode 100644
index 000000000..eb5cc7705
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MatchExpression.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.Arrays;
+import org.eclipse.equinox.p2.metadata.expression.*;
+
+/**
+ * The MatchExpression is a wrapper for an {@link IExpression} that is expected
+ * to return a boolean value. The wrapper provides the evaluation context needed
+ * to evaluate the expression.
+ */
+class MatchExpression<T> extends Unary implements IMatchExpression<T> {
+ private static final Object[] noParams = new Object[0];
+ private final Object[] parameters;
+
+ MatchExpression(Expression expression, Object[] parameters) {
+ super(expression);
+ this.parameters = parameters == null ? noParams : parameters;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return operand.accept(visitor);
+ }
+
+ public IEvaluationContext createContext() {
+ return EvaluationContext.create(parameters, ExpressionFactory.THIS);
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && Arrays.equals(parameters, ((MatchExpression<?>) o).parameters);
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return operand.evaluate(parameters.length == 0 ? context : EvaluationContext.create(context, parameters));
+ }
+
+ public int getExpressionType() {
+ return 0;
+ }
+
+ public String getOperator() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object[] getParameters() {
+ return parameters;
+ }
+
+ public int getPriority() {
+ return operand.getPriority();
+ }
+
+ public int hashCode() {
+ return operand.hashCode() * 31 + Arrays.hashCode(parameters);
+ }
+
+ public boolean isMatch(IEvaluationContext context, T value) {
+ ExpressionFactory.THIS.setValue(context, value);
+ return Boolean.TRUE == operand.evaluate(context);
+ }
+
+ public boolean isMatch(T value) {
+ return isMatch(createContext(), value);
+ }
+
+ public void toLDAPString(StringBuffer bld) {
+ operand.toLDAPString(bld);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ operand.toString(bld, rootVariable);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Matches.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Matches.java
new file mode 100644
index 000000000..da041b398
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Matches.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.Version;
+import org.eclipse.equinox.p2.metadata.VersionRange;
+import org.eclipse.equinox.p2.metadata.expression.*;
+
+/**
+ * <p>A class that performs &quot;matching&quot; The actual algorithm used for
+ * performing the match varies depending on the types of the items to match.</p>
+ * <p>The following things can be matched:</p>
+ * <table border="1" cellpadding="3">
+ * <tr><th>LHS</th><th>RHS</th><th>Implemented as</th></tr>
+ * <tr><td>{@link String}</td><td>{@link SimplePattern}</td><td>rhs.isMatch(lhs)</td></tr>
+ * <tr><td>{@link String}</td><td>{@link LDAPApproximation}</td><td>rhs.isMatch(lhs)</td></tr>
+ * <tr><td>&lt;any&gt;</td><td>{@link Class}</td><td>rhs.isInstance(lhs)</td></tr>
+ * <tr><td>{@link Class}</td><td>{@link Class}</td><td>rhs.isAssignableFrom(lhs)</td></tr>
+ * </table>
+ */
+public class Matches extends Binary {
+ protected Matches(Expression lhs, Expression rhs) {
+ super(lhs, rhs);
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return Boolean.valueOf(match(lhs.evaluate(context), rhs.evaluate(context)));
+ }
+
+ protected boolean match(Object lval, Object rval) {
+ if (rval instanceof VersionRange) {
+ VersionRange range = (VersionRange) rval;
+ if (lval instanceof Version)
+ return range.isIncluded((Version) lval);
+ if (lval instanceof String)
+ return range.isIncluded(Version.create((String) lval));
+ }
+ if (rval instanceof SimplePattern) {
+ if (lval instanceof CharSequence)
+ return ((SimplePattern) rval).isMatch((CharSequence) lval);
+ if (lval instanceof Character || lval instanceof Number || lval instanceof Boolean)
+ return ((SimplePattern) rval).isMatch(lval.toString());
+
+ } else if (rval instanceof LDAPApproximation) {
+ if (lval instanceof CharSequence)
+ return ((LDAPApproximation) rval).isMatch((CharSequence) lval);
+ if (lval instanceof Character || lval instanceof Number || lval instanceof Boolean)
+ return ((LDAPApproximation) rval).isMatch(lval.toString());
+
+ } else if (rval instanceof Class<?>) {
+ Class<?> rclass = (Class<?>) rval;
+ return lval instanceof Class<?> ? rclass.isAssignableFrom((Class<?>) lval) : rclass.isInstance(lval);
+ }
+
+ if (lval == null || rval == null)
+ return false;
+
+ throw new IllegalArgumentException("Cannot match a " + lval.getClass().getName() + " with a " + rval.getClass().getName()); //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ public int getExpressionType() {
+ return TYPE_MATCHES;
+ }
+
+ public String getOperator() {
+ return OPERATOR_MATCHES;
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ if (!(rhs instanceof Literal))
+ throw new UnsupportedOperationException();
+
+ boolean escapeWild = true;
+ Object val = rhs.evaluate(null);
+ buf.append('(');
+ appendLDAPAttribute(buf);
+ if (val instanceof LDAPApproximation) {
+ buf.append(getOperator());
+ } else if (val instanceof SimplePattern) {
+ buf.append('=');
+ escapeWild = false;
+ } else
+ throw new UnsupportedOperationException();
+ appendLDAPEscaped(buf, val.toString(), escapeWild);
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java
new file mode 100644
index 000000000..5d442f62c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.lang.reflect.*;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * <p>An expression that performs member calls to obtain some value
+ * from some object instance. It uses standard bean semantics so
+ * that an attempt to obtain &quot;value&quot; will cause an
+ * attempt to call <code>getValue()</code> and if no such method
+ * exists, <code>isValue()</code> and if that doesn't work either,
+ * <code>value()</code>.</p>
+ */
+public abstract class Member extends Unary {
+
+ public static final class DynamicMember extends Member {
+ private static final String GET_PREFIX = "get"; //$NON-NLS-1$
+ private static final String IS_PREFIX = "is"; //$NON-NLS-1$
+ private static final Class<?>[] NO_ARG_TYPES = new Class[0];
+
+ private Class<?> lastClass;
+
+ private Method method;
+ private String methodName;
+
+ DynamicMember(Expression operand, String name) {
+ super(operand, name, Expression.emptyArray);
+ if (!(name.startsWith(GET_PREFIX) || name.startsWith(IS_PREFIX)))
+ name = GET_PREFIX + Character.toUpperCase(name.charAt(0)) + name.substring(1);
+ this.methodName = name;
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return invoke(operand.evaluate(context));
+ }
+
+ public Object invoke(Object self) {
+ if (self == null)
+ throw new IllegalArgumentException("Cannot access member \'" + name + "\' in null"); //$NON-NLS-1$//$NON-NLS-2$
+
+ if (self instanceof MemberProvider)
+ return ((MemberProvider) self).getMember(name);
+
+ Class<?> c = self.getClass();
+ if (lastClass == null || !lastClass.isAssignableFrom(c)) {
+ Method m;
+ for (;;) {
+ try {
+ m = c.getMethod(methodName, NO_ARG_TYPES);
+ if (!Modifier.isPublic(m.getModifiers()))
+ throw new NoSuchMethodException();
+ break;
+ } catch (NoSuchMethodException e) {
+ if (methodName.startsWith(GET_PREFIX))
+ // Switch from using getXxx() to isXxx()
+ methodName = IS_PREFIX + Character.toUpperCase(name.charAt(0)) + name.substring(1);
+ else if (methodName.startsWith(IS_PREFIX))
+ // Switch from using isXxx() to xxx()
+ methodName = name;
+ else
+ throw new IllegalArgumentException("Cannot find a public member \'" + name + "\' in a " + self.getClass().getName()); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ }
+
+ // Since we already checked that it's public. This will speed
+ // up the calls a bit.
+ m.setAccessible(true);
+ lastClass = c;
+ method = m;
+ }
+
+ Exception checked;
+ try {
+ return method.invoke(self, NO_ARGS);
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (IllegalAccessException e) {
+ checked = e;
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getTargetException();
+ if (cause instanceof RuntimeException)
+ throw (RuntimeException) cause;
+ if (cause instanceof Error)
+ throw (Error) cause;
+ checked = (Exception) cause;
+ }
+ throw new RuntimeException("Problem invoking " + methodName + " on a " + self.getClass().getName(), checked); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ static final Object[] NO_ARGS = new Object[0];
+
+ static Member createDynamicMember(Expression operand, String name) {
+ return new DynamicMember(operand, name);
+ }
+
+ protected final Expression[] argExpressions;
+
+ final String name;
+
+ protected Member(Expression operand, String name, Expression[] args) {
+ super(operand);
+ this.name = name;
+ this.argExpressions = args;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ if (super.accept(visitor))
+ for (int idx = 0; idx < argExpressions.length; ++idx)
+ if (!argExpressions[idx].accept(visitor))
+ return false;
+ return true;
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0) {
+ cmp = name.compareTo(((Member) e).name);
+ if (cmp == 0)
+ cmp = compare(argExpressions, ((Member) e).argExpressions);
+ }
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ if (super.equals(o)) {
+ Member mo = (Member) o;
+ return name.equals(mo.name) && equals(argExpressions, mo.argExpressions);
+ }
+ return false;
+ }
+
+ public int getExpressionType() {
+ return TYPE_MEMBER;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getOperator() {
+ return OPERATOR_MEMBER;
+ }
+
+ public int getPriority() {
+ return PRIORITY_MEMBER;
+ }
+
+ public int hashCode() {
+ int result = 31 + name.hashCode();
+ result = 31 * result + operand.hashCode();
+ return 31 * result + hashCode(argExpressions);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ if (operand != rootVariable) {
+ appendOperand(bld, rootVariable, operand, getPriority());
+ bld.append('.');
+ }
+ bld.append(name);
+ if (argExpressions.length > 0) {
+ bld.append('(');
+ elementsToString(bld, rootVariable, argExpressions);
+ bld.append(')');
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MemberProvider.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MemberProvider.java
new file mode 100644
index 000000000..9eb460a8d
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MemberProvider.java
@@ -0,0 +1,139 @@
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.*;
+import java.util.Map.Entry;
+import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils;
+import org.osgi.framework.ServiceReference;
+
+public abstract class MemberProvider {
+
+ static class DictionaryMemberProvider extends MemberProvider {
+ private final Dictionary<String, ? extends Object> dictionary;
+
+ public DictionaryMemberProvider(Dictionary<String, ? extends Object> dictionary) {
+ this.dictionary = dictionary;
+ }
+
+ @Override
+ public Object getMember(String memberName) {
+ return dictionary.get(memberName);
+ }
+ }
+
+ static class CIDictionaryMemberProvider extends DictionaryMemberProvider {
+ public CIDictionaryMemberProvider(Dictionary<String, ? extends Object> dictionary) {
+ super(lowerCaseKeys(dictionary));
+ }
+
+ @Override
+ public Object getMember(String memberName) {
+ return super.getMember(memberName == null ? null : memberName.toLowerCase());
+ }
+
+ private static Dictionary<String, ? extends Object> lowerCaseKeys(Dictionary<String, ? extends Object> dictionary) {
+ boolean hasUpperCase = false;
+ for (Enumeration<String> keys = dictionary.keys(); keys.hasMoreElements();) {
+ String key = keys.nextElement();
+ if (key.toLowerCase() != key) {
+ hasUpperCase = true;
+ break;
+ }
+ }
+ if (!hasUpperCase)
+ return dictionary;
+
+ Dictionary<String, Object> lcMap = new Hashtable<String, Object>(dictionary.size());
+ for (Enumeration<String> keys = dictionary.keys(); keys.hasMoreElements();) {
+ String key = keys.nextElement();
+ if (lcMap.put(key.toLowerCase(), dictionary.get(key)) != null)
+ throw new IllegalArgumentException("case variants of the same key name: '" + key + '\''); //$NON-NLS-1$
+ }
+ return lcMap;
+ }
+ }
+
+ static class MapMemberProvider extends MemberProvider {
+ private final Map<String, ? extends Object> map;
+
+ public MapMemberProvider(Map<String, ? extends Object> map) {
+ this.map = map;
+ }
+
+ @Override
+ public Object getMember(String memberName) {
+ return map.get(memberName);
+ }
+ }
+
+ static class CIMapMemberProvider extends MapMemberProvider {
+ public CIMapMemberProvider(Map<String, ? extends Object> map) {
+ super(lowerCaseKeys(map));
+ }
+
+ @Override
+ public Object getMember(String memberName) {
+ return super.getMember(memberName == null ? null : memberName.toLowerCase());
+ }
+
+ private static Map<String, ? extends Object> lowerCaseKeys(Map<String, ? extends Object> map) {
+ boolean hasUpperCase = false;
+ Set<? extends Entry<String, ? extends Object>> entrySet = map.entrySet();
+ for (Entry<String, ?> entry : entrySet) {
+ String key = entry.getKey();
+ String lowKey = key.toLowerCase();
+ if (key != lowKey) {
+ hasUpperCase = true;
+ break;
+ }
+ }
+ if (!hasUpperCase)
+ return map;
+
+ Map<String, Object> lcMap = new HashMap<String, Object>(map.size());
+ for (Entry<String, ?> entry : entrySet) {
+ if (lcMap.put(entry.getKey().toLowerCase(), entry.getValue()) != null)
+ throw new IllegalArgumentException("case variants of the same key name: '" + entry.getKey() + '\''); //$NON-NLS-1$
+ }
+ return lcMap;
+ }
+ }
+
+ static class ServiceRefMemberProvider extends MemberProvider {
+ private final ServiceReference serviceRef;
+
+ public ServiceRefMemberProvider(ServiceReference serviceRef) {
+ this.serviceRef = serviceRef;
+ }
+
+ @Override
+ public Object getMember(String memberName) {
+ return serviceRef.getProperty(memberName);
+ }
+ }
+
+ private static final MemberProvider emptyProvider = create(CollectionUtils.emptyMap(), false);
+
+ /**
+ * Create a new member provider on the given value. The value can be an instance of a {@link Map}, {@link Dictionary},
+ * or {@link ServiceReference}.
+ * @param value The value that provides the members
+ * @param caseInsensitive <code>true</code> if the members should be retrievable in a case insensitive way.
+ * @return A member provided that is backed by <code>value</code>.
+ */
+ @SuppressWarnings("unchecked")
+ public static MemberProvider create(Object value, boolean caseInsensitive) {
+ if (value instanceof Map<?, ?>)
+ return caseInsensitive ? new CIMapMemberProvider((Map<String, ?>) value) : new MapMemberProvider((Map<String, ?>) value);
+ if (value instanceof Dictionary<?, ?>)
+ return caseInsensitive ? new CIDictionaryMemberProvider((Dictionary<String, ?>) value) : new DictionaryMemberProvider((Dictionary<String, ?>) value);
+ if (value instanceof ServiceReference)
+ return new ServiceRefMemberProvider((ServiceReference) value);
+ throw new IllegalArgumentException();
+ }
+
+ public abstract Object getMember(String memberName);
+
+ public static MemberProvider emptyProvider() {
+ return emptyProvider;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/NAry.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/NAry.java
new file mode 100644
index 000000000..805cff2ab
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/NAry.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * The abstract baseclass for all N-ary expressions
+ */
+public abstract class NAry extends Expression {
+ public final Expression[] operands;
+
+ protected NAry(Expression[] operands) {
+ this.operands = operands;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ if (super.accept(visitor))
+ for (int idx = 0; idx < operands.length; ++idx)
+ if (!operands[idx].accept(visitor))
+ return false;
+ return true;
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = compare(operands, ((NAry) e).operands);
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && equals(operands, ((NAry) o).operands);
+ }
+
+ public abstract String getOperator();
+
+ public int hashCode() {
+ return hashCode(operands);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ appendOperand(bld, rootVariable, operands[0], getPriority());
+ for (int idx = 1; idx < operands.length; ++idx) {
+ bld.append(' ');
+ bld.append(getOperator());
+ bld.append(' ');
+ appendOperand(bld, rootVariable, operands[idx], getPriority());
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Not.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Not.java
new file mode 100644
index 000000000..5246859a3
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Not.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * An expression that yields <code>true</code> when its operand does not.
+ */
+final class Not extends Unary {
+ Not(Expression operand) {
+ super(operand);
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return Boolean.valueOf(operand.evaluate(context) != Boolean.TRUE);
+ }
+
+ public int getExpressionType() {
+ return TYPE_NOT;
+ }
+
+ public String getOperator() {
+ return OPERATOR_NOT;
+ }
+
+ public int getPriority() {
+ return PRIORITY_NOT;
+ }
+
+ public int hashCode() {
+ return 3 * operand.hashCode();
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ buf.append("(!"); //$NON-NLS-1$
+ operand.toLDAPString(buf);
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Or.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Or.java
new file mode 100644
index 000000000..32805042e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Or.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * n-ary OR operator. The full evaluation is <code>false</code> if none of its operands
+ * evaluate to <code>true</code>.
+ */
+final class Or extends NAry {
+ public Or(Expression[] operands) {
+ super(assertLength(operands, 2, OPERATOR_OR));
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ for (int idx = 0; idx < operands.length; ++idx) {
+ if (operands[idx].evaluate(context) == Boolean.TRUE)
+ return Boolean.TRUE;
+ }
+ return Boolean.FALSE;
+ }
+
+ public int getExpressionType() {
+ return TYPE_OR;
+ }
+
+ public String getOperator() {
+ return OPERATOR_OR;
+ }
+
+ public int getPriority() {
+ return PRIORITY_OR;
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ buf.append("(|"); //$NON-NLS-1$
+ for (int idx = 0; idx < operands.length; ++idx)
+ operands[idx].toLDAPString(buf);
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Parameter.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Parameter.java
new file mode 100644
index 000000000..b4c56346e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Parameter.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * The abstract base class for the indexed and keyed parameters
+ */
+public class Parameter extends Expression {
+ final int position;
+
+ Parameter(int position) {
+ this.position = position;
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = position - ((Parameter) e).position;
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+ if (o == null)
+ return false;
+ return getClass() == o.getClass() && position == ((Parameter) o).position;
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return context.getParameter(position);
+ }
+
+ public int getExpressionType() {
+ return TYPE_PARAMETER;
+ }
+
+ public String getOperator() {
+ return OPERATOR_PARAMETER;
+ }
+
+ public int getPriority() {
+ return PRIORITY_VARIABLE;
+ }
+
+ public int hashCode() {
+ return 31 * (1 + position);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ bld.append('$');
+ bld.append(position);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/RepeatableIterator.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/RepeatableIterator.java
new file mode 100644
index 000000000..e44a4e507
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/RepeatableIterator.java
@@ -0,0 +1,237 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.*;
+import org.eclipse.equinox.p2.query.IQueryResult;
+
+public class RepeatableIterator<T> implements IRepeatableIterator<T> {
+ private final List<T> values;
+ private int position = -1;
+
+ @SuppressWarnings("unchecked")
+ public static <T> IRepeatableIterator<T> create(Object unknown) {
+ if (unknown.getClass().isArray())
+ return create((T[]) unknown);
+ if (unknown instanceof Iterator<?>)
+ return create((Iterator<T>) unknown);
+ if (unknown instanceof List<?>)
+ return create((List<T>) unknown);
+ if (unknown instanceof Collection<?>)
+ return create((Collection<T>) unknown);
+ if (unknown instanceof Map<?, ?>)
+ return create((Set<T>) ((Map<?, ?>) unknown).entrySet());
+ if (unknown instanceof IQueryResult<?>)
+ return create((IQueryResult<T>) unknown);
+ throw new IllegalArgumentException("Cannot convert a " + unknown.getClass().getName() + " into an iterator"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ public static <T> IRepeatableIterator<T> create(Iterator<T> iterator) {
+ return iterator instanceof IRepeatableIterator<?> ? ((IRepeatableIterator<T>) iterator).getCopy() : new ElementRetainingIterator<T>(iterator);
+ }
+
+ public static <T> IRepeatableIterator<T> create(List<T> values) {
+ return new RepeatableIterator<T>(values);
+ }
+
+ public static <T> IRepeatableIterator<T> create(Collection<T> values) {
+ return new CollectionIterator<T>(values);
+ }
+
+ public static <T> IRepeatableIterator<T> create(IQueryResult<T> values) {
+ return new QueryResultIterator<T>(values);
+ }
+
+ public static <T> IRepeatableIterator<T> create(T[] values) {
+ return new ArrayIterator<T>(values);
+ }
+
+ RepeatableIterator(List<T> values) {
+ this.values = values;
+ }
+
+ public IRepeatableIterator<T> getCopy() {
+ return new RepeatableIterator<T>(values);
+ }
+
+ public boolean hasNext() {
+ return position + 1 < values.size();
+ }
+
+ public T next() {
+ if (++position == values.size()) {
+ --position;
+ throw new NoSuchElementException();
+ }
+ return values.get(position);
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object getIteratorProvider() {
+ return values;
+ }
+
+ void setPosition(int position) {
+ this.position = position;
+ }
+
+ List<T> getValues() {
+ return values;
+ }
+
+ static class ArrayIterator<T> implements IRepeatableIterator<T> {
+ private final T[] array;
+ private int position = -1;
+
+ public ArrayIterator(T[] array) {
+ this.array = array;
+ }
+
+ public Object getIteratorProvider() {
+ return array;
+ }
+
+ public boolean hasNext() {
+ return position + 1 < array.length;
+ }
+
+ public T next() {
+ if (++position >= array.length)
+ throw new NoSuchElementException();
+ return array[position];
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public IRepeatableIterator<T> getCopy() {
+ return new ArrayIterator<T>(array);
+ }
+ }
+
+ static class CollectionIterator<T> implements IRepeatableIterator<T> {
+ private final Collection<T> collection;
+
+ private final Iterator<T> iterator;
+
+ CollectionIterator(Collection<T> collection) {
+ this.collection = collection;
+ this.iterator = collection.iterator();
+ }
+
+ public IRepeatableIterator<T> getCopy() {
+ return new CollectionIterator<T>(collection);
+ }
+
+ public Object getIteratorProvider() {
+ return collection;
+ }
+
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ public T next() {
+ return iterator.next();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ static class QueryResultIterator<T> implements IRepeatableIterator<T> {
+ private final IQueryResult<T> queryResult;
+
+ private final Iterator<T> iterator;
+
+ QueryResultIterator(IQueryResult<T> queryResult) {
+ this.queryResult = queryResult;
+ this.iterator = queryResult.iterator();
+ }
+
+ public IRepeatableIterator<T> getCopy() {
+ return new QueryResultIterator<T>(queryResult);
+ }
+
+ public Object getIteratorProvider() {
+ return queryResult;
+ }
+
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ public T next() {
+ return iterator.next();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ static class ElementRetainingIterator<T> extends RepeatableIterator<T> {
+
+ private Iterator<T> innerIterator;
+
+ ElementRetainingIterator(Iterator<T> iterator) {
+ super(new ArrayList<T>());
+ innerIterator = iterator;
+ }
+
+ public synchronized boolean hasNext() {
+ if (innerIterator != null) {
+ if (innerIterator.hasNext())
+ return true;
+ innerIterator = null;
+ setPosition(getValues().size());
+ }
+ return super.hasNext();
+ }
+
+ public synchronized T next() {
+ if (innerIterator != null) {
+ T val = innerIterator.next();
+ getValues().add(val);
+ return val;
+ }
+ return super.next();
+ }
+
+ public synchronized IRepeatableIterator<T> getCopy() {
+ // If the current iterator still exists, we must exhaust it first
+ //
+ exhaustInnerIterator();
+ return super.getCopy();
+ }
+
+ public synchronized Object getIteratorProvider() {
+ exhaustInnerIterator();
+ return super.getIteratorProvider();
+ }
+
+ private void exhaustInnerIterator() {
+ if (innerIterator != null) {
+ List<T> values = getValues();
+ int savePos = values.size() - 1;
+ while (innerIterator.hasNext())
+ values.add(innerIterator.next());
+ innerIterator = null;
+ setPosition(savePos);
+ }
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Unary.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Unary.java
new file mode 100644
index 000000000..9c6b7d8a5
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Unary.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * The abstract base class for all unary expressions
+ */
+public abstract class Unary extends Expression {
+ public final Expression operand;
+
+ protected Unary(Expression operand) {
+ this.operand = operand;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return super.accept(visitor) && operand.accept(visitor);
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = operand.compareTo(((Unary) e).operand);
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && operand.equals(((Unary) o).operand);
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return operand.evaluate(context);
+ }
+
+ public int hashCode() {
+ return operand.hashCode() * 3 + operand.getExpressionType();
+ }
+
+ public Expression getOperand() {
+ return operand;
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ bld.append(getOperator());
+ appendOperand(bld, rootVariable, operand, getPriority());
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Variable.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Variable.java
new file mode 100644
index 000000000..a7adfb738
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Variable.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.Iterator;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * An expression representing a variable stack in the current thread.
+ */
+public class Variable extends Expression {
+
+ private final String name;
+
+ public Variable(String name) {
+ this.name = name;
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = name.compareTo(((Variable) e).name);
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && name.equals(((Variable) o).name);
+ }
+
+ public final Object evaluate(IEvaluationContext context) {
+ return context.getValue(this);
+ }
+
+ public Iterator<?> evaluateAsIterator(IEvaluationContext context) {
+ Object value = context.getValue(this);
+ if (value instanceof IRepeatableIterator<?>)
+ return ((IRepeatableIterator<?>) value).getCopy();
+
+ Iterator<?> itor = RepeatableIterator.create(value);
+ setValue(context, itor);
+ return itor;
+ }
+
+ public int getExpressionType() {
+ return TYPE_VARIABLE;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getOperator() {
+ return "<variable>"; //$NON-NLS-1$
+ }
+
+ public int getPriority() {
+ return PRIORITY_VARIABLE;
+ }
+
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ public final void setValue(IEvaluationContext context, Object value) {
+ context.setValue(this, value);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ bld.append(name);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/ExpressionParser.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/ExpressionParser.java
new file mode 100644
index 000000000..905d0927c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/ExpressionParser.java
@@ -0,0 +1,617 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression.parser;
+
+import org.eclipse.equinox.internal.p2.metadata.expression.LDAPApproximation;
+
+import java.util.*;
+import org.eclipse.equinox.internal.p2.metadata.expression.IExpressionConstants;
+import org.eclipse.equinox.p2.metadata.expression.*;
+
+public class ExpressionParser extends Stack<IExpression> implements IExpressionConstants, IExpressionParser {
+ private static final long serialVersionUID = 5481439062356612378L;
+
+ protected static final int TOKEN_OR = 1;
+ protected static final int TOKEN_AND = 2;
+
+ protected static final int TOKEN_EQUAL = 10;
+ protected static final int TOKEN_NOT_EQUAL = 11;
+ protected static final int TOKEN_LESS = 12;
+ protected static final int TOKEN_LESS_EQUAL = 13;
+ protected static final int TOKEN_GREATER = 14;
+ protected static final int TOKEN_GREATER_EQUAL = 15;
+ protected static final int TOKEN_MATCHES = 16;
+
+ protected static final int TOKEN_NOT = 20;
+ protected static final int TOKEN_DOT = 21;
+ protected static final int TOKEN_COMMA = 22;
+ protected static final int TOKEN_PIPE = 23;
+ protected static final int TOKEN_DOLLAR = 24;
+ protected static final int TOKEN_IF = 25;
+ protected static final int TOKEN_ELSE = 26;
+
+ protected static final int TOKEN_LP = 30;
+ protected static final int TOKEN_RP = 31;
+ protected static final int TOKEN_LB = 32;
+ protected static final int TOKEN_RB = 33;
+ protected static final int TOKEN_LC = 34;
+ protected static final int TOKEN_RC = 35;
+
+ protected static final int TOKEN_IDENTIFIER = 40;
+ protected static final int TOKEN_LITERAL = 41;
+
+ protected static final int TOKEN_NULL = 50;
+ protected static final int TOKEN_TRUE = 51;
+ protected static final int TOKEN_FALSE = 52;
+
+ private static final int TOKEN_ALL = 60;
+ private static final int TOKEN_EXISTS = 61;
+
+ protected static final int TOKEN_END = 0;
+ protected static final int TOKEN_ERROR = -1;
+
+ protected static final Map<String, Integer> keywords;
+ static {
+ keywords = new HashMap<String, Integer>();
+ keywords.put(KEYWORD_FALSE, new Integer(TOKEN_FALSE));
+ keywords.put(KEYWORD_NULL, new Integer(TOKEN_NULL));
+ keywords.put(KEYWORD_TRUE, new Integer(TOKEN_TRUE));
+ keywords.put(KEYWORD_ALL, new Integer(TOKEN_ALL));
+ keywords.put(KEYWORD_EXISTS, new Integer(TOKEN_EXISTS));
+ }
+
+ protected final IExpressionFactory factory;
+
+ protected String expression;
+ protected int tokenPos;
+ protected int currentToken;
+ protected int lastTokenPos;
+ protected Object tokenValue;
+ protected String rootVariable;
+
+ public ExpressionParser(IExpressionFactory factory) {
+ this.factory = factory;
+ }
+
+ public synchronized IExpression parse(String exprString) {
+ expression = exprString;
+ tokenPos = 0;
+ currentToken = 0;
+ tokenValue = null;
+ IExpression thisVariable = factory.thisVariable();
+ rootVariable = ExpressionUtil.getName(thisVariable);
+ push(thisVariable);
+ try {
+ nextToken();
+ IExpression expr = currentToken == TOKEN_END ? factory.constant(Boolean.TRUE) : parseCondition();
+ assertToken(TOKEN_END);
+ return expr;
+ } finally {
+ if (thisVariable != null)
+ popVariable(); // pop item
+ }
+ }
+
+ protected Map<String, Integer> keywordToTokenMap() {
+ return keywords;
+ }
+
+ protected IExpression parseCondition() {
+ // Just a hook in this parser. Conditions are not supported
+ return parseOr();
+ }
+
+ protected IExpression parseOr() {
+ IExpression expr = parseAnd();
+ if (currentToken != TOKEN_OR)
+ return expr;
+
+ ArrayList<IExpression> exprs = new ArrayList<IExpression>();
+ exprs.add(expr);
+ do {
+ nextToken();
+ exprs.add(parseAnd());
+ } while (currentToken == TOKEN_OR);
+ return factory.or(exprs.toArray(new IExpression[exprs.size()]));
+ }
+
+ protected IExpression parseAnd() {
+ IExpression expr = parseBinary();
+ if (currentToken != TOKEN_AND)
+ return expr;
+
+ ArrayList<IExpression> exprs = new ArrayList<IExpression>();
+ exprs.add(expr);
+ do {
+ nextToken();
+ exprs.add(parseBinary());
+ } while (currentToken == TOKEN_AND);
+ return factory.and(exprs.toArray(new IExpression[exprs.size()]));
+ }
+
+ protected IExpression parseBinary() {
+ IExpression expr = parseNot();
+ for (;;) {
+ switch (currentToken) {
+ case TOKEN_OR :
+ case TOKEN_AND :
+ case TOKEN_RP :
+ case TOKEN_RB :
+ case TOKEN_RC :
+ case TOKEN_COMMA :
+ case TOKEN_IF :
+ case TOKEN_ELSE :
+ case TOKEN_END :
+ break;
+ case TOKEN_EQUAL :
+ case TOKEN_NOT_EQUAL :
+ case TOKEN_GREATER :
+ case TOKEN_GREATER_EQUAL :
+ case TOKEN_LESS :
+ case TOKEN_LESS_EQUAL :
+ case TOKEN_MATCHES :
+ int realToken = currentToken;
+ nextToken();
+ IExpression rhs;
+ if (realToken == TOKEN_MATCHES && currentToken == TOKEN_LITERAL && tokenValue instanceof String)
+ rhs = factory.constant(new LDAPApproximation((String) tokenValue));
+ else
+ rhs = parseNot();
+ switch (realToken) {
+ case TOKEN_EQUAL :
+ expr = factory.equals(expr, rhs);
+ break;
+ case TOKEN_NOT_EQUAL :
+ expr = factory.not(factory.equals(expr, rhs));
+ break;
+ case TOKEN_GREATER :
+ expr = factory.greater(expr, rhs);
+ break;
+ case TOKEN_GREATER_EQUAL :
+ expr = factory.greaterEqual(expr, rhs);
+ break;
+ case TOKEN_LESS :
+ expr = factory.less(expr, rhs);
+ break;
+ case TOKEN_LESS_EQUAL :
+ expr = factory.lessEqual(expr, rhs);
+ break;
+ default :
+ expr = factory.matches(expr, rhs);
+ }
+ continue;
+ default :
+ throw syntaxError();
+ }
+ break;
+ }
+ return expr;
+ }
+
+ protected IExpression parseNot() {
+ if (currentToken == TOKEN_NOT) {
+ nextToken();
+ IExpression expr = parseNot();
+ return factory.not(expr);
+ }
+ return parseCollectionExpression();
+ }
+
+ protected IExpression parseCollectionExpression() {
+ IExpression expr = parseCollectionLHS();
+ if (expr == null) {
+ expr = parseMember();
+ if (currentToken != TOKEN_DOT)
+ return expr;
+ nextToken();
+ }
+ for (;;) {
+ int funcToken = currentToken;
+ nextToken();
+ assertToken(TOKEN_LP);
+ nextToken();
+ expr = parseCollectionRHS(expr, funcToken);
+ if (currentToken != TOKEN_DOT)
+ break;
+ nextToken();
+ }
+ return expr;
+ }
+
+ protected IExpression parseCollectionLHS() {
+ IExpression expr = null;
+ switch (currentToken) {
+ case TOKEN_EXISTS :
+ case TOKEN_ALL :
+ expr = getVariableOrRootMember(rootVariable);
+ break;
+ }
+ return expr;
+ }
+
+ protected IExpression parseCollectionRHS(IExpression expr, int funcToken) {
+ switch (funcToken) {
+ case TOKEN_EXISTS :
+ expr = factory.exists(expr, parseLambdaDefinition());
+ break;
+ case TOKEN_ALL :
+ expr = factory.all(expr, parseLambdaDefinition());
+ break;
+ default :
+ throw syntaxError();
+ }
+ return expr;
+ }
+
+ protected IExpression parseLambdaDefinition() {
+ assertToken(TOKEN_IDENTIFIER);
+ IExpression each = factory.variable((String) tokenValue);
+ push(each);
+ try {
+ nextToken();
+ assertToken(TOKEN_PIPE);
+ nextToken();
+ IExpression body = parseCondition();
+ assertToken(TOKEN_RP);
+ nextToken();
+ return factory.lambda(each, body);
+ } finally {
+ pop();
+ }
+ }
+
+ protected IExpression parseMember() {
+ IExpression expr = parseUnary();
+ String name;
+ while (currentToken == TOKEN_DOT || currentToken == TOKEN_LB) {
+ int savePos = tokenPos;
+ int saveToken = currentToken;
+ Object saveTokenValue = tokenValue;
+ nextToken();
+ if (saveToken == TOKEN_DOT) {
+ switch (currentToken) {
+ case TOKEN_IDENTIFIER :
+ name = (String) tokenValue;
+ nextToken();
+ expr = factory.member(expr, name);
+ break;
+
+ default :
+ tokenPos = savePos;
+ currentToken = saveToken;
+ tokenValue = saveTokenValue;
+ return expr;
+ }
+ } else {
+ IExpression atExpr = parseMember();
+ assertToken(TOKEN_RB);
+ nextToken();
+ expr = factory.at(expr, atExpr);
+ }
+ }
+ return expr;
+ }
+
+ protected IExpression parseUnary() {
+ IExpression expr;
+ switch (currentToken) {
+ case TOKEN_LP :
+ nextToken();
+ expr = parseCondition();
+ assertToken(TOKEN_RP);
+ nextToken();
+ break;
+ case TOKEN_LITERAL :
+ expr = factory.constant(tokenValue);
+ nextToken();
+ break;
+ case TOKEN_IDENTIFIER :
+ expr = getVariableOrRootMember((String) tokenValue);
+ nextToken();
+ break;
+ case TOKEN_NULL :
+ expr = factory.constant(null);
+ nextToken();
+ break;
+ case TOKEN_TRUE :
+ expr = factory.constant(Boolean.TRUE);
+ nextToken();
+ break;
+ case TOKEN_FALSE :
+ expr = factory.constant(Boolean.FALSE);
+ nextToken();
+ break;
+ case TOKEN_DOLLAR :
+ expr = parseParameter();
+ break;
+ default :
+ throw syntaxError();
+ }
+ return expr;
+ }
+
+ private IExpression parseParameter() {
+ if (currentToken == TOKEN_DOLLAR) {
+ nextToken();
+ if (currentToken == TOKEN_LITERAL && tokenValue instanceof Integer) {
+ IExpression param = factory.indexedParameter(((Integer) tokenValue).intValue());
+ nextToken();
+ return param;
+ }
+ }
+ throw syntaxError();
+ }
+
+ protected IExpression[] parseArray() {
+ IExpression expr = parseCondition();
+ if (currentToken != TOKEN_COMMA)
+ return new IExpression[] {expr};
+
+ ArrayList<IExpression> operands = new ArrayList<IExpression>();
+ operands.add(expr);
+ do {
+ nextToken();
+ if (currentToken == TOKEN_LC)
+ // We don't allow lambdas in the array
+ break;
+ operands.add(parseCondition());
+ } while (currentToken == TOKEN_COMMA);
+ return operands.toArray(new IExpression[operands.size()]);
+ }
+
+ protected void assertToken(int token) {
+ if (currentToken != token)
+ throw syntaxError();
+ }
+
+ protected IExpression getVariableOrRootMember(String id) {
+ int idx = size();
+ while (--idx >= 0) {
+ IExpression v = get(idx);
+ if (id.equals(v.toString()))
+ return v;
+ }
+
+ if (rootVariable == null || rootVariable.equals(id))
+ throw syntaxError("No such variable: " + id); //$NON-NLS-1$
+
+ return factory.member(getVariableOrRootMember(rootVariable), id);
+ }
+
+ protected void nextToken() {
+ tokenValue = null;
+ int top = expression.length();
+ char c = 0;
+ while (tokenPos < top) {
+ c = expression.charAt(tokenPos);
+ if (!Character.isWhitespace(c))
+ break;
+ ++tokenPos;
+ }
+ if (tokenPos >= top) {
+ lastTokenPos = top;
+ currentToken = TOKEN_END;
+ return;
+ }
+
+ lastTokenPos = tokenPos;
+ switch (c) {
+ case '|' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '|') {
+ tokenValue = OPERATOR_OR;
+ currentToken = TOKEN_OR;
+ tokenPos += 2;
+ } else {
+ currentToken = TOKEN_PIPE;
+ ++tokenPos;
+ }
+ break;
+
+ case '&' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '&') {
+ tokenValue = OPERATOR_AND;
+ currentToken = TOKEN_AND;
+ tokenPos += 2;
+ } else
+ currentToken = TOKEN_ERROR;
+ break;
+
+ case '=' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
+ tokenValue = OPERATOR_EQUALS;
+ currentToken = TOKEN_EQUAL;
+ tokenPos += 2;
+ } else
+ currentToken = TOKEN_ERROR;
+ break;
+
+ case '!' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
+ tokenValue = OPERATOR_NOT_EQUALS;
+ currentToken = TOKEN_NOT_EQUAL;
+ tokenPos += 2;
+ } else {
+ currentToken = TOKEN_NOT;
+ ++tokenPos;
+ }
+ break;
+
+ case '~' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
+ tokenValue = OPERATOR_MATCHES;
+ currentToken = TOKEN_MATCHES;
+ tokenPos += 2;
+ } else
+ currentToken = TOKEN_ERROR;
+ break;
+
+ case '>' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
+ tokenValue = OPERATOR_GT_EQUAL;
+ currentToken = TOKEN_GREATER_EQUAL;
+ tokenPos += 2;
+ } else {
+ currentToken = TOKEN_GREATER;
+ ++tokenPos;
+ }
+ break;
+
+ case '<' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
+ tokenValue = OPERATOR_LT_EQUAL;
+ currentToken = TOKEN_LESS_EQUAL;
+ tokenPos += 2;
+ } else {
+ currentToken = TOKEN_LESS;
+ ++tokenPos;
+ }
+ break;
+
+ case '?' :
+ currentToken = TOKEN_IF;
+ ++tokenPos;
+ break;
+
+ case ':' :
+ currentToken = TOKEN_ELSE;
+ ++tokenPos;
+ break;
+
+ case '.' :
+ currentToken = TOKEN_DOT;
+ ++tokenPos;
+ break;
+
+ case '$' :
+ currentToken = TOKEN_DOLLAR;
+ ++tokenPos;
+ break;
+
+ case '{' :
+ currentToken = TOKEN_LC;
+ ++tokenPos;
+ break;
+
+ case '}' :
+ currentToken = TOKEN_RC;
+ ++tokenPos;
+ break;
+
+ case '(' :
+ currentToken = TOKEN_LP;
+ ++tokenPos;
+ break;
+
+ case ')' :
+ currentToken = TOKEN_RP;
+ ++tokenPos;
+ break;
+
+ case '[' :
+ currentToken = TOKEN_LB;
+ ++tokenPos;
+ break;
+
+ case ']' :
+ currentToken = TOKEN_RB;
+ ++tokenPos;
+ break;
+
+ case ',' :
+ currentToken = TOKEN_COMMA;
+ ++tokenPos;
+ break;
+
+ case '"' :
+ case '\'' : {
+ int start = ++tokenPos;
+ while (tokenPos < top && expression.charAt(tokenPos) != c)
+ ++tokenPos;
+ if (tokenPos == top) {
+ tokenPos = start - 1;
+ currentToken = TOKEN_ERROR;
+ } else {
+ tokenValue = expression.substring(start, tokenPos++);
+ currentToken = TOKEN_LITERAL;
+ }
+ break;
+ }
+
+ case '/' : {
+ int start = ++tokenPos;
+ StringBuffer buf = new StringBuffer();
+ while (tokenPos < top) {
+ c = expression.charAt(tokenPos);
+ if (c == '\\' && tokenPos + 1 < top) {
+ c = expression.charAt(++tokenPos);
+ if (c != '/')
+ buf.append('\\');
+ } else if (c == '/')
+ break;
+ buf.append(c);
+ ++tokenPos;
+ }
+ if (tokenPos == top) {
+ tokenPos = start - 1;
+ currentToken = TOKEN_ERROR;
+ } else {
+ tokenValue = SimplePattern.compile(expression.substring(start, tokenPos++));
+ currentToken = TOKEN_LITERAL;
+ }
+ break;
+ }
+
+ default :
+ if (Character.isDigit(c)) {
+ int start = tokenPos++;
+ while (tokenPos < top && Character.isDigit(expression.charAt(tokenPos)))
+ ++tokenPos;
+ tokenValue = Integer.valueOf(expression.substring(start, tokenPos));
+ currentToken = TOKEN_LITERAL;
+ break;
+ }
+ if (Character.isJavaIdentifierStart(c)) {
+ int start = tokenPos++;
+ while (tokenPos < top && Character.isJavaIdentifierPart(expression.charAt(tokenPos)))
+ ++tokenPos;
+ String word = expression.substring(start, tokenPos);
+ Integer token = keywordToTokenMap().get(word);
+ if (token == null)
+ currentToken = TOKEN_IDENTIFIER;
+ else
+ currentToken = token.intValue();
+ tokenValue = word;
+ break;
+ }
+ throw syntaxError();
+ }
+ }
+
+ protected void popVariable() {
+ if (isEmpty())
+ throw syntaxError();
+ pop();
+ }
+
+ protected ExpressionParseException syntaxError() {
+ Object tv = tokenValue;
+ if (tv == null) {
+ if (lastTokenPos >= expression.length())
+ return syntaxError("Unexpeced end of expression"); //$NON-NLS-1$
+ tv = expression.substring(lastTokenPos, lastTokenPos + 1);
+ }
+ return syntaxError("Unexpected token \"" + tv + '"'); //$NON-NLS-1$
+ }
+
+ protected ExpressionParseException syntaxError(String message) {
+ return new ExpressionParseException(expression, message, tokenPos);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/LDAPFilterParser.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/LDAPFilterParser.java
new file mode 100644
index 000000000..b5fd6714c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/LDAPFilterParser.java
@@ -0,0 +1,268 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression.parser;
+
+import java.util.ArrayList;
+import org.eclipse.equinox.internal.p2.metadata.Messages;
+import org.eclipse.equinox.internal.p2.metadata.expression.IExpressionConstants;
+import org.eclipse.equinox.internal.p2.metadata.expression.LDAPApproximation;
+import org.eclipse.equinox.p2.metadata.expression.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Parser class for OSGi filter strings. This class parses the complete filter string and builds a tree of Filter
+ * objects rooted at the parent.
+ */
+public class LDAPFilterParser {
+ private final IExpressionFactory factory;
+
+ private final IExpression self;
+
+ private final StringBuffer sb = new StringBuffer();
+
+ private String filterString;
+
+ private int position;
+
+ public LDAPFilterParser(IExpressionFactory factory) {
+ this.factory = factory;
+ self = factory.variable(IExpressionConstants.VARIABLE_THIS);
+ position = 0;
+ }
+
+ public synchronized IFilterExpression parse(String filter) {
+ filterString = filter;
+ position = 0;
+ try {
+ IExpression expr = parseFilter();
+ if (position != filterString.length())
+ throw syntaxException(Messages.filter_trailing_characters);
+ return factory.filterExpression(expr);
+ } catch (StringIndexOutOfBoundsException e) {
+ throw syntaxException(Messages.filter_premature_end);
+ }
+ }
+
+ private IExpression parseAnd() {
+ skipWhiteSpace();
+ char c = filterString.charAt(position);
+ if (c != '(')
+ throw syntaxException(Messages.filter_missing_leftparen);
+
+ ArrayList<IExpression> operands = new ArrayList<IExpression>();
+ while (c == '(') {
+ IExpression child = parseFilter();
+ if (!operands.contains(child))
+ operands.add(child);
+ c = filterString.charAt(position);
+ }
+ // int sz = operands.size();
+ // return sz == 1 ? operands.get(0) : factory.and(operands.toArray(new IExpression[sz]));
+ return factory.normalize(operands, IExpression.TYPE_AND);
+ }
+
+ private IExpression parseAttr() {
+ skipWhiteSpace();
+
+ int begin = position;
+ int end = position;
+
+ char c = filterString.charAt(begin);
+ while (!(c == '~' || c == '<' || c == '>' || c == '=' || c == '(' || c == ')')) {
+ position++;
+ if (!Character.isWhitespace(c))
+ end = position;
+ c = filterString.charAt(position);
+ }
+ if (end == begin)
+ throw syntaxException(Messages.filter_missing_attr);
+ return factory.member(self, filterString.substring(begin, end));
+ }
+
+ private IExpression parseFilter() {
+ IExpression filter;
+ skipWhiteSpace();
+
+ if (filterString.charAt(position) != '(')
+ throw syntaxException(Messages.filter_missing_leftparen);
+
+ position++;
+ filter = parseFiltercomp();
+
+ skipWhiteSpace();
+
+ if (filterString.charAt(position) != ')')
+ throw syntaxException(Messages.filter_missing_rightparen);
+
+ position++;
+ skipWhiteSpace();
+
+ return filter;
+ }
+
+ private IExpression parseFiltercomp() {
+ skipWhiteSpace();
+
+ char c = filterString.charAt(position);
+
+ switch (c) {
+ case '&' : {
+ position++;
+ return parseAnd();
+ }
+ case '|' : {
+ position++;
+ return parseOr();
+ }
+ case '!' : {
+ position++;
+ return parseNot();
+ }
+ }
+ return parseItem();
+ }
+
+ private IExpression parseItem() {
+ IExpression attr = parseAttr();
+
+ skipWhiteSpace();
+ String value;
+
+ boolean[] hasWild = {false};
+ char c = filterString.charAt(position);
+ switch (c) {
+ case '~' :
+ case '>' :
+ case '<' :
+ if (filterString.charAt(position + 1) != '=')
+ throw syntaxException(Messages.filter_invalid_operator);
+ position += 2;
+ int savePos = position;
+ value = parseValue(hasWild);
+ if (hasWild[0]) {
+ // Unescaped wildcard found. This is not legal for the given operator
+ position = savePos;
+ throw syntaxException(Messages.filter_invalid_value);
+ }
+ switch (c) {
+ case '>' :
+ return factory.greaterEqual(attr, factory.constant(value));
+ case '<' :
+ return factory.lessEqual(attr, factory.constant(value));
+ }
+ return factory.matches(attr, factory.constant(new LDAPApproximation(value)));
+ case '=' :
+ position++;
+ value = parseValue(hasWild);
+ return hasWild[0] ? factory.matches(attr, factory.constant(SimplePattern.compile(value))) : factory.equals(attr, factory.constant(value));
+ }
+ throw syntaxException(Messages.filter_invalid_operator);
+ }
+
+ private IExpression parseNot() {
+ skipWhiteSpace();
+
+ if (filterString.charAt(position) != '(')
+ throw syntaxException(Messages.filter_missing_leftparen);
+ return factory.not(parseFilter());
+ }
+
+ private IExpression parseOr() {
+ skipWhiteSpace();
+ char c = filterString.charAt(position);
+ if (c != '(')
+ throw syntaxException(Messages.filter_missing_leftparen);
+
+ ArrayList<IExpression> operands = new ArrayList<IExpression>();
+ while (c == '(') {
+ IExpression child = parseFilter();
+ operands.add(child);
+ c = filterString.charAt(position);
+ }
+ // int sz = operands.size();
+ // return sz == 1 ? operands.get(0) : factory.or(operands.toArray(new IExpression[sz]));
+ return factory.normalize(operands, IExpression.TYPE_OR);
+ }
+
+ private static int hexValue(char c) {
+ int v;
+ if (c <= '9')
+ v = c - '0';
+ else if (c <= 'F')
+ v = (c - 'A') + 10;
+ else
+ v = (c - 'a') + 10;
+ return v;
+ }
+
+ private String parseValue(boolean[] hasWildBin) {
+ sb.setLength(0);
+ int savePos = position;
+ boolean hasEscapedWild = false;
+ parseloop: while (true) {
+ char c = filterString.charAt(position);
+ switch (c) {
+ case '*' :
+ if (hasEscapedWild && !hasWildBin[0]) {
+ // We must redo the parse.
+ position = savePos;
+ hasWildBin[0] = true;
+ return parseValue(hasWildBin);
+ }
+ hasWildBin[0] = true;
+ sb.append(c);
+ position++;
+ break;
+
+ case ')' :
+ break parseloop;
+
+ case '(' :
+ throw syntaxException(Messages.filter_invalid_value);
+
+ case '\\' :
+ c = filterString.charAt(++position);
+ if (c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f' && position + 1 < filterString.length()) {
+ char nc = filterString.charAt(position + 1);
+ if (nc >= '0' && nc <= '9' || nc >= 'A' && nc <= 'F' || nc >= 'a' && nc <= 'f') {
+ // Assume proper \xx escape where xx are hex digits
+ ++position;
+ c = (char) (((hexValue(c) << 4) & 0xf0) | (hexValue(nc) & 0x0f));
+ if (c == '*' && hasWildBin != null) {
+ hasEscapedWild = true;
+ if (hasWildBin[0])
+ sb.append('\\');
+ }
+ }
+ }
+ /* fall through into default */
+
+ default :
+ sb.append(c);
+ position++;
+ break;
+ }
+ }
+ if (sb.length() == 0)
+ throw syntaxException(Messages.filter_missing_value);
+ return sb.toString();
+ }
+
+ private void skipWhiteSpace() {
+ for (int top = filterString.length(); position < top; ++position)
+ if (!Character.isWhitespace(filterString.charAt(position)))
+ break;
+ }
+
+ protected ExpressionParseException syntaxException(String message) {
+ return new ExpressionParseException(NLS.bind(message, filterString, Integer.toString(position)));
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/messages.properties b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/messages.properties
index 4ddb7ab02..a70676f45 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/messages.properties
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/messages.properties
@@ -25,6 +25,14 @@ EOS_after_escape=End of string was encountere after the escape character
expected_orignal_after_colon_0=An original version was expected after colon: {0}
expected_orignal_after_slash_0=A format or colon was expected after slash: {0}
expected_slash_after_raw_vector_0=A slash was expected after a raw version: {0}
+filter_missing_leftparen=Filter "{0}" Missing ''('' at position {1}
+filter_missing_rightparen=Filter "{0}" Missing '')'' at position {1}
+filter_trailing_characters=Filter "{0}" Extraneous trailing characters at position {1}
+filter_invalid_operator=Filter "{0}" Invalid operator at position {1}
+filter_missing_attr=Filter "{0}" Missing attr at position {1}
+filter_missing_value=Filter "{0}" is missing value at position {1}
+filter_invalid_value=Filter "{0}" has invalid value at position {1}
+filter_premature_end=Filter "{0}" ends before it is complete
format_0_unable_to_parse_1=Format "{0}" was unable to parse {1}
format_0_unable_to_parse_empty_version=Format "{0}" was unable to parse an empty version
format_is_empty=Format is empty
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/MetadataFactory.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/MetadataFactory.java
index e7d1afe67..4e023f487 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/MetadataFactory.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/MetadataFactory.java
@@ -12,9 +12,6 @@
*******************************************************************************/
package org.eclipse.equinox.internal.provisional.p2.metadata;
-import org.eclipse.equinox.p2.metadata.Version;
-import org.eclipse.equinox.p2.metadata.VersionRange;
-
import java.net.URI;
import java.util.*;
import java.util.Map.Entry;
@@ -22,7 +19,7 @@ import org.eclipse.core.runtime.Assert;
import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils;
import org.eclipse.equinox.internal.p2.metadata.*;
import org.eclipse.equinox.p2.metadata.*;
-import org.eclipse.equinox.p2.query.IQuery;
+import org.osgi.framework.Filter;
/**
* A factory class for instantiating various p2 metadata objects.
@@ -132,6 +129,10 @@ public class MetadataFactory {
unit().setCopyright(copyright);
}
+ public void setFilter(Filter filter) {
+ unit().setFilter(filter);
+ }
+
public void setFilter(String filter) {
unit().setFilter(filter);
}
@@ -306,11 +307,11 @@ public class MetadataFactory {
* and <code>false</code> otherwise.
* @param multiple <code>true</code> if this capability can be satisfied by multiple provided capabilities, or it requires exactly one match
*/
- public static IRequiredCapability createRequiredCapability(String namespace, String name, VersionRange range, String filter, boolean optional, boolean multiple) {
- return new RequiredCapability(namespace, name, range, filter, optional, multiple);
+ public static IRequiredCapability createRequiredCapability(String namespace, String name, VersionRange range, Filter filter, boolean optional, boolean multiple) {
+ return new RequiredCapability(namespace, name, range, filter, optional ? 0 : 1, multiple ? Integer.MAX_VALUE : 1, true);
}
- public static IRequirement createRequiredCapability(String namespace, String name, VersionRange range, IQuery<Boolean> filter, int minCard, int maxCard, boolean greedy) {
+ public static IRequirement createRequiredCapability(String namespace, String name, VersionRange range, Filter filter, int minCard, int maxCard, boolean greedy) {
return new RequiredCapability(namespace, name, range, filter, minCard, maxCard, greedy);
}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IInstallableUnit.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IInstallableUnit.java
index d97d3a49a..fbc39cbb4 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IInstallableUnit.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IInstallableUnit.java
@@ -12,7 +12,7 @@
package org.eclipse.equinox.p2.metadata;
import java.util.*;
-import org.eclipse.equinox.p2.query.IQuery;
+import org.osgi.framework.Filter;
/**
* An installable unit represents an atomic, indivisible unit of installable functionality
@@ -128,7 +128,7 @@ public interface IInstallableUnit extends IVersionedId, Comparable<IInstallableU
*
* @return The installation filter for this unit, or <code>null</code>
*/
- public IQuery<Boolean> getFilter();
+ public Filter getFilter();
/**
* Returns the fragments that have been bound to this installable unit, or
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IProvidedCapability.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IProvidedCapability.java
index ac2255a6a..0496e8925 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IProvidedCapability.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IProvidedCapability.java
@@ -10,7 +10,6 @@
******************************************************************************/
package org.eclipse.equinox.p2.metadata;
-
/**
* Describes a capability as exposed or required by an installable unit
*
@@ -27,25 +26,6 @@ public interface IProvidedCapability {
public Version getVersion();
/**
- * Returns whether this provided capability satisfies the given required capability.
- * @return <code>true</code> if this capability satisfies the given required
- * capability, and <code>false</code> otherwise.
- *
- * This method must maintain the following semantics:
- * <ul>
- * <li> If the provided capability and the candidate have different names,
- * return false
- * <li> If the provided capability and the candidate have different namespaces.
- * return false
- * <li> If the candidate's version range includes the provided capability's
- * version, return true
- * <li> otherwise, return false
- * </ul>
- *
- */
- public boolean satisfies(IRequirement candidate);
-
- /**
* Returns whether this provided capability is equal to the given object.
*
* This method returns <i>true</i> if:
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IRequirement.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IRequirement.java
index 39619541b..cc866e2fa 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IRequirement.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IRequirement.java
@@ -9,7 +9,8 @@
******************************************************************************/
package org.eclipse.equinox.p2.metadata;
-import org.eclipse.equinox.p2.query.IQuery;
+import org.eclipse.equinox.p2.metadata.expression.IMatchExpression;
+import org.osgi.framework.Filter;
/**
* @noimplement This interface is not intended to be implemented by clients.
@@ -18,13 +19,20 @@ import org.eclipse.equinox.p2.query.IQuery;
*/
public interface IRequirement {
- public int getMin();
+ int getMin();
- public int getMax();
+ int getMax();
- public IQuery<IInstallableUnit> getMatches();
+ Filter getFilter();
- public IQuery<Boolean> getFilter();
+ /**
+ * Returns a boolean match expression that will return true for any
+ * {@link IInstallableUnit} that matches the requirement.
+ * @return A boolean match expression for installable unit matching.
+ */
+ IMatchExpression<IInstallableUnit> getMatches();
- public boolean isGreedy();
+ boolean isMatch(IInstallableUnit iu);
+
+ boolean isGreedy();
} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/ExpressionParseException.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/ExpressionParseException.java
new file mode 100644
index 000000000..4f1eb1e3f
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/ExpressionParseException.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.p2.metadata.expression;
+
+/**
+ * An exception used by an expression parser that indicates that something went wrong when
+ * parsing.
+ */
+public class ExpressionParseException extends RuntimeException {
+ private static final long serialVersionUID = 8432875384760577764L;
+
+ public ExpressionParseException(String message) {
+ super(message);
+ }
+
+ public ExpressionParseException(String expression, String message, int position) {
+ super("Parse error in string \"" + expression + "\": " + message + " at position " + position); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/ExpressionUtil.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/ExpressionUtil.java
new file mode 100644
index 000000000..02861aeed
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/ExpressionUtil.java
@@ -0,0 +1,179 @@
+/*******************************************************************************
+ * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.p2.metadata.expression;
+
+import org.eclipse.equinox.internal.p2.metadata.expression.*;
+import org.eclipse.equinox.internal.p2.metadata.expression.parser.ExpressionParser;
+import org.eclipse.equinox.internal.p2.metadata.expression.parser.LDAPFilterParser;
+
+/**
+ * Global access to factory, parser, and methods for introspection
+ */
+public abstract class ExpressionUtil {
+ private static final LDAPFilterParser ldapFilterParser = new LDAPFilterParser(ExpressionFactory.INSTANCE);
+ private static final ExpressionParser expressionParser = new ExpressionParser(ExpressionFactory.INSTANCE);
+
+ /**
+ * Returns the global expression factory
+ * @return The global expression factory.
+ */
+ public static IExpressionFactory getFactory() {
+ return ExpressionFactory.INSTANCE;
+ }
+
+ /**
+ * Creates and returns a new expression parser
+ * @return The new parser
+ */
+ public static IExpressionParser newParser() {
+ return new ExpressionParser(getFactory());
+ }
+
+ /**
+ * Parse an LDAP filter from the <code>filter</code> string. If <code>filter</code> is <code>null</code>
+ * or a string that is empty or only consists of whitespace, then this method returns <code>null</code>.
+ * @param filter The filter to parse. Can be <code>null</code> or empty.
+ * @return An expression that corresponds to the LDAP filter or <code>null</code>.
+ * @throws ExpressionParseException If the syntax was invalid
+ */
+ public static IFilterExpression parseLDAP(String filter) throws IllegalArgumentException {
+ filter = trimmedOrNull(filter);
+ return filter == null ? null : ldapFilterParser.parse(filter);
+ }
+
+ /**
+ * Parse a boolean Expression from the <code>expression</code> string. If <code>expression</code> is <code>null</code>
+ * or a string that is empty or only consists of whitespace, then this method returns <code>null</code>.
+ * @param expression The expression to parse. Can be <code>null</code> or empty.
+ * @return An expression that corresponds to the LDAP filter or <code>null</code>.
+ * @throws ExpressionParseException If the syntax was invalid
+ */
+ public static IExpression parse(String expression) {
+ expression = trimmedOrNull(expression);
+ return expression == null ? null : expressionParser.parse(expression);
+ }
+
+ /**
+ * If <code>str</code> is <code>null</code>, then this method returns <code>null</code>.
+ * Otherwise <code>str</code> is trimmed from whitespace at both ends. If the result
+ * of the trim is an empty string, then <code>null</code> is returned, otherwise the
+ * result of the trim is returned.
+ * @param str The string to trim. Can be <code>null</code>.
+ * @return The trimmed string or <code>null</code>.
+ */
+ public static String trimmedOrNull(String str) {
+ if (str != null) {
+ str = str.trim();
+ if (str.length() == 0)
+ str = null;
+ }
+ return str;
+ }
+
+ /**
+ * Obtains the Left Hand Side (LHS) of a binary expression.
+ * @param expression The expression to introspect
+ * @return The left hand side operator
+ * @throws IllegalArgumentException if the expression is not a binary expression
+ * @see IExpression#TYPE_AT
+ * @see IExpression#TYPE_EQUALS
+ * @see IExpression#TYPE_GREATER
+ * @see IExpression#TYPE_GREATER_EQUAL
+ * @see IExpression#TYPE_LESS
+ * @see IExpression#TYPE_LESS_EQUAL
+ * @see IExpression#TYPE_MATCHES
+ * @see IExpression#TYPE_NOT_EQUALS
+ */
+ public static IExpression getLHS(IExpression expression) {
+ if (expression instanceof Binary)
+ return ((Binary) expression).lhs;
+ throw new IllegalArgumentException();
+ }
+
+ /**
+ * Obtains the name of a variable or member expression.
+ * @param expression The expression to introspect
+ * @return The name of the expression
+ * @throws IllegalArgumentException if the expression is not a variable or a member
+ * @see IExpression#TYPE_MEMBER
+ * @see IExpression#TYPE_VARIABLE
+ */
+ public static String getName(IExpression expression) {
+ if (expression instanceof Member)
+ return ((Member) expression).getName();
+ if (expression instanceof Variable)
+ return ((Variable) expression).getName();
+ throw new IllegalArgumentException();
+ }
+
+ /**
+ * Obtains the operand of an unary expression
+ * @param expression The expression to introspect
+ * @return The expression operand
+ * @throws IllegalArgumentException if the expression is not an unary expression
+ * @see IExpression#TYPE_ALL
+ * @see IExpression#TYPE_EXISTS
+ * @see IExpression#TYPE_LAMBDA
+ * @see IExpression#TYPE_NOT
+ */
+ public static IExpression getOperand(IExpression expression) {
+ if (expression instanceof Unary)
+ return ((Unary) expression).operand;
+ throw new IllegalArgumentException();
+ }
+
+ /**
+ * Obtains the operands of an n-ary expression
+ * @param expression The expression to introspect
+ * @return The expression operand
+ * @throws IllegalArgumentException if the expression is not a n-ary expression
+ * @see IExpression#TYPE_AND
+ * @see IExpression#TYPE_OR
+ */
+ public static IExpression[] getOperands(IExpression expression) {
+ if (expression instanceof NAry)
+ return ((NAry) expression).operands;
+ throw new IllegalArgumentException();
+ }
+
+ /**
+ * Obtains the Right Hand Side (RHS) of a binary expression.
+ * @param expression The expression to introspect
+ * @return The right hand side operator
+ * @throws IllegalArgumentException if the expression is not a binary expression
+ * @see IExpression#TYPE_AT
+ * @see IExpression#TYPE_EQUALS
+ * @see IExpression#TYPE_GREATER
+ * @see IExpression#TYPE_GREATER_EQUAL
+ * @see IExpression#TYPE_LESS
+ * @see IExpression#TYPE_LESS_EQUAL
+ * @see IExpression#TYPE_MATCHES
+ * @see IExpression#TYPE_NOT_EQUALS
+ */
+ public static IExpression getRHS(IExpression expression) {
+ if (expression instanceof Binary)
+ return ((Binary) expression).rhs;
+ throw new IllegalArgumentException();
+ }
+
+ /**
+ * Obtains the value of a literal expression
+ * @param expression The expression to introspect
+ * @return The literal value
+ * @throws IllegalArgumentException if the expression is not a literal
+ * @see IExpression#TYPE_LITERAL
+ */
+ public static Object getValue(IExpression expression) {
+ if (expression instanceof Literal)
+ return ((Literal) expression).value;
+ throw new IllegalArgumentException();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IEvaluationContext.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IEvaluationContext.java
new file mode 100644
index 000000000..1a3427cca
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IEvaluationContext.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.p2.metadata.expression;
+
+/**
+ * The evaluation context. Contexts can be nested and new contexts are pushed for each closure
+ * during an evaluation of an expression.
+ */
+public interface IEvaluationContext {
+ /**
+ * Retrieve the value of the given <code>variable</code> from this context
+ * @param variable The variable who's value should be retrieved
+ * @return The current value for the variable
+ */
+ Object getValue(IExpression variable);
+
+ /**
+ * Set the current value for the given <code>variable</code> to <code>value</code>
+ * @param variable The variable who's value should be set
+ * @param value The new value for the variable.
+ */
+ void setValue(IExpression variable, Object value);
+
+ /**
+ * Returns the value of the parameter at the given <code>position</code>
+ * @param position The zero based position for the parameter
+ * @return The parameter value
+ */
+ Object getParameter(int position);
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpression.java
new file mode 100644
index 000000000..889675381
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpression.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.p2.metadata.expression;
+
+/**
+ * A node in the expression tree
+ */
+public interface IExpression {
+ int TYPE_ALL = 1;
+ int TYPE_AND = 2;
+ int TYPE_AT = 3;
+ int TYPE_EQUALS = 4;
+ int TYPE_EXISTS = 5;
+ int TYPE_GREATER = 6;
+ int TYPE_GREATER_EQUAL = 7;
+ int TYPE_LAMBDA = 8;
+ int TYPE_LESS = 9;
+ int TYPE_LESS_EQUAL = 10;
+ int TYPE_LITERAL = 11;
+ int TYPE_MATCHES = 12;
+ int TYPE_MEMBER = 13;
+ int TYPE_NOT = 14;
+ int TYPE_NOT_EQUALS = 15;
+ int TYPE_OR = 16;
+ int TYPE_PARAMETER = 17;
+ int TYPE_VARIABLE = 18;
+
+ /**
+ * 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.
+ */
+ boolean accept(IExpressionVisitor visitor);
+
+ /**
+ * Evaluate this expression with given context and variables.
+ * @param context The evaluation context
+ * @return The result of the evaluation.
+ */
+ Object evaluate(IEvaluationContext context);
+
+ /**
+ * Returns the expression type (see TYPE_xxx constants).
+ */
+ int getExpressionType();
+
+ /**
+ * Appends the string representation of this expression to the collector <code>collector</code>.
+ */
+ void toString(StringBuffer collector);
+
+ /**
+ * Appends the an LDAP filter representation of this expression to the <code>collector</code>.
+ * @throws UnsupportedOperationException if the expression contains nodes
+ * that cannot be represented in an LDAP filter
+ */
+ void toLDAPString(StringBuffer collector);
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionFactory.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionFactory.java
new file mode 100644
index 000000000..f76079a5d
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionFactory.java
@@ -0,0 +1,206 @@
+/*******************************************************************************
+ * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.p2.metadata.expression;
+
+import java.util.List;
+
+/**
+ * This inteface provides all the factory methods needed to create the
+ * nodes of the expression tree.
+ */
+public interface IExpressionFactory {
+ String FUNC_BOOLEAN = "boolean"; //$NON-NLS-1$
+ String FUNC_VERSION = "version"; //$NON-NLS-1$
+ String FUNC_CLASS = "class"; //$NON-NLS-1$
+ String FUNC_RANGE = "range"; //$NON-NLS-1$
+ String FUNC_FILTER = "filter"; //$NON-NLS-1$
+
+ IExpression[] NO_ARGS = new IExpression[0];
+
+ /**
+ * Create a collection filter that yields true if the <code>lambda</code> yields true for
+ * all of the elements of the <code>collection</code>
+ * @param collection The collection providing the elements to test
+ * @param lambda The lambda that performs the test
+ * @return A boolean expression
+ */
+ IExpression all(IExpression collection, IExpression lambda);
+
+ /**
+ * Create a logical <i>and</i> of its <code>operands</code>.
+ * @param operands The boolean operands
+ * @return A boolean expression
+ */
+ IExpression and(IExpression... operands);
+
+ /**
+ * Create an lookup of <code>key</code> in the <code>target</code>.
+ * The key expression should evaluate to a string or an integer.
+ * @param target The target for the lookup
+ * @param key The key to use for the lookup
+ * @return A lookup expression
+ */
+ IExpression at(IExpression target, IExpression key);
+
+ /**
+ * Create an evaluation context with one single variable
+ * @param params Indexed parameters to use in the expression
+ * @return the context
+ */
+ IEvaluationContext createContext(Object... params);
+
+ /**
+ * Create an evaluation context with one single variable
+ * @param params Indexed parameters to use in the expression
+ * @param variables The variables that will be maintained by the context
+ * @return the context
+ */
+ IEvaluationContext createContext(IExpression[] variables, Object... params);
+
+ /**
+ * Creates an expression that evaluates to the constant <code>value</code>.
+ * @param value The constant
+ * @return A constant expression
+ */
+ IExpression constant(Object value);
+
+ /**
+ * Create an expression that tests if <code>lhs</code> is equal to <code>rhs</code>.
+ * @param lhs The left hand side value.
+ * @param rhs The right hand side value.
+ * @return A boolean expression
+ */
+ IExpression equals(IExpression lhs, IExpression rhs);
+
+ /**
+ * Create a collection filter that yields true if the <code>lambda</code> yields true for
+ * at least one of the elements of the <code>collection</code>
+ * @param collection The collection providing the elements to test
+ * @param lambda The lambda that performs the test
+ * @return A boolean expression
+ */
+ IExpression exists(IExpression collection, IExpression lambda);
+
+ /**
+ * Creates a top level expression suitable for predicate matching
+ * @param expression The boolean expression
+ * @return A top level predicate expression
+ */
+ IFilterExpression filterExpression(IExpression expression);
+
+ /**
+ * Create an expression that tests if <code>lhs</code> is greater than <code>rhs</code>.
+ * @param lhs The left hand side value.
+ * @param rhs The right hand side value.
+ * @return A boolean expression
+ */
+ IExpression greater(IExpression lhs, IExpression rhs);
+
+ /**
+ * Create an expression that tests if <code>lhs</code> is greater than or equal to <code>rhs</code>.
+ * @param lhs The left hand side value.
+ * @param rhs The right hand side value.
+ * @return A boolean expression
+ */
+ IExpression greaterEqual(IExpression lhs, IExpression rhs);
+
+ /**
+ * Creates an indexed parameter expression
+ * @param index The index to use
+ * @return a parameter expression
+ */
+ IExpression indexedParameter(int index);
+
+ /**
+ * Creates a lambda expression that takes exactly one variable. Suitable for use
+ * in most collection expressions.
+ * @param variable The element variable that the lambda uses
+ * @param body The body of the lambda
+ * @return A lambda expression
+ */
+ IExpression lambda(IExpression variable, IExpression body);
+
+ /**
+ * Create an expression that tests if <code>lhs</code> is less than <code>rhs</code>.
+ * @param lhs The left hand side value.
+ * @param rhs The right hand side value.
+ * @return A boolean expression
+ */
+ IExpression less(IExpression lhs, IExpression rhs);
+
+ /**
+ * Create an expression that tests if <code>lhs</code> is less than or equal to <code>rhs</code>.
+ * @param lhs The left hand side value.
+ * @param rhs The right hand side value.
+ * @return A boolean expression
+ */
+ IExpression lessEqual(IExpression lhs, IExpression rhs);
+
+ /**
+ * Performs boolean normalizations on the expression to create a canonical form.
+ * @param operands The operands to normalize
+ * @param expressionType The type (must be either {@link IExpression#TYPE_AND}
+ * or {@link IExpression#TYPE_OR}.
+ * @return The normalized expression
+ */
+ IExpression normalize(List<? extends IExpression> operands, int expressionType);
+
+ /**
+ * Create an expression that tests if <code>lhs</code> matches <code>rhs</code>.
+ * @param lhs The left hand side value.
+ * @param rhs The right hand side value.
+ * @return A boolean expression
+ */
+ IExpression matches(IExpression lhs, IExpression rhs);
+
+ /**
+ * Creates a parameterized top level expression suitable for predicate matching
+ * @param expression The boolean expression
+ * @param parameters The parameters to use in the call
+ * @return A top level predicate expression
+ */
+ <T> IMatchExpression<T> matchExpression(IExpression expression, Object... parameters);
+
+ /**
+ * Creates a member accessor expression.
+ * @param target The target for the member access
+ * @param name The name of the member
+ * @return A member expression
+ */
+ IExpression member(IExpression target, String name);
+
+ /**
+ * Creates an expression that negates the result of evaluating its <code>operand</code>.
+ * @param operand The boolean expression to negate
+ * @return A boolean expression
+ */
+ IExpression not(IExpression operand);
+
+ /**
+ * Create a logical <i>or</i> of its <code>operands</code>.
+ * @param operands The boolean operands
+ * @return A boolean expression
+ */
+ IExpression or(IExpression... operands);
+
+ /**
+ * Returns the variable that represents <code>this</this> in an expression
+ * @return The <code>this</this> variable.
+ */
+ IExpression thisVariable();
+
+ /**
+ * Creates an expression that represents a variable
+ * @param name The name of the variable
+ * @return A variable expression
+ */
+ IExpression variable(String name);
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionParser.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionParser.java
new file mode 100644
index 000000000..f933a87bf
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionParser.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.p2.metadata.expression;
+
+/**
+ * A parser that produces an expression tree based on a string representation. An
+ * implementation will use the {@link IExpressionFactory} to create the actual expressions
+ */
+public interface IExpressionParser {
+ /**
+ * Create a new expression. The expression will have access to the global
+ * variable and to the context parameters.
+ * @param exprString The string representing the boolean expression.
+ * @return The resulting expression tree.
+ */
+ IExpression parse(String exprString);
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionVisitor.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionVisitor.java
new file mode 100644
index 000000000..376275d88
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionVisitor.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.p2.metadata.expression;
+
+/**
+ * A general purpose visitor that will visit each node in an expression tree.
+ */
+public interface IExpressionVisitor {
+ /**
+ * The method that will be called for each expression that is
+ * visited.
+ * @param expression The expression that the visitor visits.
+ * @return <code>true</code> to continue visiting other expressions or
+ * <code>false</code> to break out.
+ */
+ boolean visit(IExpression expression);
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IFilterExpression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IFilterExpression.java
new file mode 100644
index 000000000..34253f794
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IFilterExpression.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.p2.metadata.expression;
+
+import java.util.Map;
+import org.osgi.framework.Filter;
+
+/**
+ * An interface that combines the IExpression with the LDAP filter. The
+ * string representation is the LDAP filter syntax.
+ */
+public interface IFilterExpression extends IExpression, Filter {
+ /**
+ * Filter using a <code>Map</code>. This <code>Filter</code> is
+ * executed using the specified <code>Map</code>'s keys and values.
+ * The keys are case insensitively matched with this <code>Filter</code>.
+ *
+ * @param map The <code>Map</code> whose keys are used in the
+ * match.
+ * @return <code>true</code> if the <code>map</code>'s keys and
+ * values match this filter; <code>false</code> otherwise.
+ * @throws IllegalArgumentException If <code>map</code> contains case
+ * variants of the same key name.
+ */
+ boolean match(Map<String, ? extends Object> map);
+
+ /**
+ * Filter with case sensitivity using a <code>Map</code>. This
+ * <code>Filter</code> is executed using the specified
+ * <code>Map</code>'s keys and values. The keys are case sensitively
+ * matched with this <code>Filter</code>.
+ *
+ * @param map The <code>Map</code> whose keys are used in the
+ * match.
+ * @return <code>true</code> if the <code>map</code>'s keys and
+ * values match this filter; <code>false</code> otherwise.
+ */
+ boolean matchCase(Map<String, ? extends Object> map);
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IMatchExpression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IMatchExpression.java
new file mode 100644
index 000000000..419e3f4ba
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IMatchExpression.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.p2.metadata.expression;
+
+/**
+ * A match expression is a boolean expression matching a candidate of a
+ * specific type. An {@link IEvaluationContext} is needed in order to evaluate
+ * a match and this class provides two ways of doing that. Either a context
+ * is created first and then reused in several subsequent calls to
+ * {@link #isMatch(IEvaluationContext, Object)} or, if no repeated calls are
+ * expected, the {@link #isMatch(Object)} method can be used. It will then
+ * create a context on each call.
+ */
+public interface IMatchExpression<T> extends IExpression {
+ /**
+ * <p>Creates a new context to be passed to repeated subsequent evaluations. The context
+ * will introduce 'this' as an uninitialized variable and make the parameters available.
+ * @return A new evaluation context.
+ */
+ IEvaluationContext createContext();
+
+ /**
+ * Returns the parameters that this match expression was created with.
+ * @return An array of parameters, possibly empty but never <code>null</code>.
+ */
+ Object[] getParameters();
+
+ /**
+ * This method creates a new evaluation context and assigns the <code>candidate</code>
+ * to the 'this' variable of the <code>context</code> and then evaluates the expression.
+ * This is essentially a short form for <pre>isMatch(createContext(), candidate)</pre>.
+ * @param candidate The object to test.
+ * @return the result of the evaluation.
+ */
+ boolean isMatch(T candidate);
+
+ /**
+ * This method assigns <code>candidate</code> to the 'this' variable of the
+ * <code>context</code> and then evaluates the expression.
+ * @param context A context
+ * @param candidate The object to test.
+ * @return the result of the evaluation.
+ */
+ boolean isMatch(IEvaluationContext context, T candidate);
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/SimplePattern.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/SimplePattern.java
new file mode 100644
index 000000000..2b709ccb6
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/SimplePattern.java
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.p2.metadata.expression;
+
+import java.io.Serializable;
+
+/**
+ * A simple compiled pattern. It supports two kinds of wildcards. The '*' (any character zero to many times)
+ * and the '?' (any character exactly one time).
+ */
+public class SimplePattern implements Serializable, Comparable<SimplePattern> {
+ private static final long serialVersionUID = -2477990705739062410L;
+
+ /**
+ * Matches the <code>value</code> with the compiled expression. The value
+ * is considered matching if all characters are matched by the expression. A
+ * partial match is not enough.
+ * @param value The value to match
+ * @return <code>true</code> if the value was a match.
+ */
+ public synchronized boolean isMatch(CharSequence value) {
+ if (node == null)
+ node = parse(pattern, 0);
+ return node.match(value, 0);
+ }
+
+ public String toString() {
+ return pattern;
+ }
+
+ public int compareTo(SimplePattern o) {
+ return pattern.compareTo(o.pattern);
+ }
+
+ public boolean equals(Object o) {
+ return o == this || (o instanceof SimplePattern && ((SimplePattern) o).pattern.equals(pattern));
+ }
+
+ public int hashCode() {
+ return 3 * pattern.hashCode();
+ }
+
+ private final String pattern;
+ private transient Node node;
+
+ private SimplePattern(String pattern, Node node) {
+ this.pattern = pattern;
+ this.node = node;
+ }
+
+ private static class RubberBandNode extends Node {
+ RubberBandNode(Node next) {
+ super(next);
+ }
+
+ boolean match(CharSequence value, int pos) {
+ if (next == null)
+ return true;
+
+ int top = value.length();
+ while (pos < top) {
+ if (next.match(value, pos++))
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private static class AnyCharacterNode extends Node {
+ AnyCharacterNode(Node next) {
+ super(next);
+ }
+
+ boolean match(CharSequence value, int pos) {
+ int top = value.length();
+ return next == null ? pos + 1 == top : next.match(value, pos + 1);
+ }
+ }
+
+ private static class ConstantNode extends Node {
+ final String constant;
+
+ ConstantNode(Node next, String constant) {
+ super(next);
+ this.constant = constant;
+ }
+
+ boolean match(CharSequence value, int pos) {
+ int vtop = value.length();
+ int ctop = constant.length();
+ if (ctop + pos > vtop)
+ return false;
+
+ for (int idx = 0; idx < ctop; ++idx, ++pos)
+ if (constant.charAt(idx) != value.charAt(pos))
+ return false;
+
+ return next == null ? true : next.match(value, pos);
+ }
+ }
+
+ private static abstract class Node {
+ final Node next;
+
+ Node(Node next) {
+ this.next = next;
+ }
+
+ abstract boolean match(CharSequence value, int pos);
+ }
+
+ public static SimplePattern compile(String pattern) {
+ if (pattern == null)
+ throw new IllegalArgumentException("Pattern can not be null"); //$NON-NLS-1$
+ return new SimplePattern(pattern, null);
+ }
+
+ private static Node parse(String pattern, int pos) {
+ int top = pattern.length();
+ StringBuffer bld = null;
+ Node parsedNode = null;
+ while (pos < top) {
+ char c = pattern.charAt(pos);
+ switch (c) {
+ case '*' :
+ parsedNode = new RubberBandNode(parse(pattern, pos + 1));
+ break;
+ case '?' :
+ parsedNode = new AnyCharacterNode(parse(pattern, pos + 1));
+ break;
+ case '\\' :
+ if (++pos == top)
+ throw new IllegalArgumentException("Pattern ends with escape"); //$NON-NLS-1$
+ c = pattern.charAt(pos);
+ // fall through
+ default :
+ if (bld == null)
+ bld = new StringBuffer();
+ bld.append(c);
+ ++pos;
+ continue;
+ }
+ break;
+ }
+
+ if (bld != null)
+ parsedNode = new ConstantNode(parsedNode, bld.toString());
+ return parsedNode;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/ExpressionQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/ExpressionQuery.java
new file mode 100644
index 000000000..ea0cb676b
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/ExpressionQuery.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2009 IBM Corporation 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:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.p2.metadata.query;
+
+import org.eclipse.equinox.internal.p2.metadata.expression.ExpressionFactory;
+import org.eclipse.equinox.p2.metadata.expression.*;
+import org.eclipse.equinox.p2.query.MatchQuery;
+
+/**
+ * A query that matches candidates against an expression.
+ */
+public class ExpressionQuery<T> extends MatchQuery<T> {
+ private final IMatchExpression<T> expression;
+ private final IEvaluationContext context;
+ private final Class<T> matchingClass;
+
+ public ExpressionQuery(Class<T> matchingClass, IExpression expression, Object... parameters) {
+ this(matchingClass, ExpressionUtil.getFactory().<T> matchExpression(expression, parameters));
+ }
+
+ public ExpressionQuery(Class<T> matchingClass, IMatchExpression<T> expression) {
+ this.matchingClass = matchingClass;
+ this.expression = expression;
+ this.context = expression.createContext();
+ }
+
+ @Override
+ public boolean isMatch(T candidate) {
+ if (!matchingClass.isInstance(candidate))
+ return false;
+ ExpressionFactory.THIS.setValue(context, candidate);
+ return Boolean.TRUE == expression.evaluate(context);
+ }
+
+ public IMatchExpression<T> getExpression() {
+ return expression;
+ }
+}

Back to the top