diff options
author | Thomas Hallgren | 2010-01-13 13:03:53 +0000 |
---|---|---|
committer | Thomas Hallgren | 2010-01-13 13:03:53 +0000 |
commit | 521e788834ac2eccb042012c6080349f4c0bb6e3 (patch) | |
tree | 0582904865268f0a43e6cd9ab230b754d6ddf4bb | |
parent | d743b627ab504ab6787a7cb560abdbe5d029e08e (diff) | |
download | rt.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
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.<operation>(y | <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 "matching" The actual algorithm used for + * performing the match varies depending on the types of the items to match.</p> + * <p>The following things can be matched:</p> + * <table border="1" cellpadding="3"> + * <tr><th>LHS</th><th>RHS</th><th>Implemented as</th></tr> + * <tr><td>{@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><any></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 "value" will cause an + * attempt to call <code>getValue()</code> and if no such method + * exists, <code>isValue()</code> and if that doesn't work either, + * <code>value()</code>.</p> + */ +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; + } +} |