Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata')
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ArtifactKey.java15
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/BasicVersion.java83
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/Copyright.java2
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/IRequiredCapability.java45
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnit.java132
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnitFragment.java27
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnitPatch.java36
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/License.java16
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/Messages.java25
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/NotRequirement.java69
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ORRequirement.java85
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/OSGiVersion.java213
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/OmniVersion.java247
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ProvidedCapability.java13
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequiredCapability.java247
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequirementChange.java43
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ResolvedInstallableUnit.java149
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TouchpointData.java18
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TouchpointInstruction.java18
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TouchpointType.java5
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TranslationSupport.java299
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/UpdateDescriptor.java5
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionFormat.java345
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionFormatParser.java1548
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionParser.java388
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionVector.java325
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionedId.java104
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/All.java42
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/And.java50
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/At.java96
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Binary.java135
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CoercingComparator.java392
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CollectionFilter.java85
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Compare.java60
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Equals.java52
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/EvaluationContext.java161
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Exists.java42
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Expression.java341
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/ExpressionFactory.java125
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IExpressionConstants.java58
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IRepeatableIterator.java25
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPApproximation.java77
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPFilter.java69
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LambdaExpression.java72
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Literal.java123
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MatchExpression.java83
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Matches.java95
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java178
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MemberProvider.java139
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/NAry.java59
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Not.java48
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Or.java50
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Parameter.java64
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/RepeatableIterator.java237
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Unary.java57
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Variable.java79
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/ExpressionParser.java617
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/LDAPFilterParser.java268
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/messages.properties13
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/IUPropertyQuery.java48
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/LatestIUVersionQuery.java48
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/ObjectMatchQuery.java22
-rw-r--r--bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/UpdateQuery.java48
63 files changed, 8159 insertions, 501 deletions
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ArtifactKey.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ArtifactKey.java
index f495ee2a7..709fa2b1c 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ArtifactKey.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ArtifactKey.java
@@ -10,11 +10,12 @@
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
+import org.eclipse.equinox.p2.metadata.Version;
+
import java.util.ArrayList;
import java.util.StringTokenizer;
import org.eclipse.core.runtime.Assert;
-import org.eclipse.equinox.internal.provisional.p2.metadata.IArtifactKey;
-import org.eclipse.equinox.internal.provisional.p2.metadata.Version;
+import org.eclipse.equinox.p2.metadata.IArtifactKey;
/**
* The concrete type for representing IArtifactKey's.
@@ -31,7 +32,7 @@ public class ArtifactKey implements IArtifactKey {
private static String[] getArrayFromList(String stringList, String separator) {
if (stringList == null || stringList.trim().length() == 0)
return new String[0];
- ArrayList list = new ArrayList();
+ ArrayList<String> list = new ArrayList<String>();
boolean separatorSeen = true;
StringTokenizer tokens = new StringTokenizer(stringList, separator, true);
while (tokens.hasMoreTokens()) {
@@ -48,7 +49,7 @@ public class ArtifactKey implements IArtifactKey {
}
if (separatorSeen)
list.add(""); //$NON-NLS-1$
- return (String[]) list.toArray(new String[list.size()]);
+ return list.toArray(new String[list.size()]);
}
public static IArtifactKey parse(String specification) {
@@ -79,6 +80,12 @@ public class ArtifactKey implements IArtifactKey {
this.version = version;
}
+ public ArtifactKey(IArtifactKey artifactKey) {
+ this.classifier = artifactKey.getClassifier();
+ this.id = artifactKey.getId();
+ this.version = artifactKey.getVersion();
+ }
+
public String getClassifier() {
return classifier;
}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/BasicVersion.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/BasicVersion.java
new file mode 100644
index 000000000..0514284f0
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/BasicVersion.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata;
+
+import org.eclipse.equinox.p2.metadata.Version;
+
+/**
+ * The abstract BasicVersion class adds the methods necessary to to compare and serialize
+ * versions in version ranges. The class is not intended as public API.
+ */
+public abstract class BasicVersion extends Version {
+ private static final long serialVersionUID = -2983093417537485027L;
+
+ /**
+ * Returns the OSGi major component of this version identifier.
+ *
+ * @return The major component.
+ * @throws UnsupportedOperationException if the first element in the
+ * vector is not a number.
+ * @see #isOSGiCompatible()
+ */
+ public abstract int getMajor();
+
+ /**
+ * Returns the OSGi micro component of this version identifier.
+ *
+ * @return The micro component.
+ * @throws UnsupportedOperationException if the third element in the
+ * vector is not a number.
+ * @see #isOSGiCompatible()
+ */
+ public abstract int getMicro();
+
+ /**
+ * Returns the OSGi minor component of this version identifier.
+ *
+ * @return The minor component.
+ * @throws UnsupportedOperationException if the second element in the
+ * vector is not a number.
+ * @see #isOSGiCompatible()
+ */
+ public abstract int getMinor();
+
+ /**
+ * Returns the OSGi qualifier component of this version identifier.
+ *
+ * @return The qualifier component or <code>null</code> if not set.
+ * @throws UnsupportedOperationException if the fourth element in the
+ * vector is set to something other then a string.
+ * @see #isOSGiCompatible()
+ */
+ public abstract String getQualifier();
+
+ /**
+ * Appends the original for this version onto the <code>sb</code> StringBuffer
+ * if present.
+ * @param sb The buffer that will receive the raw string format
+ * @param rangeSafe Set to <code>true</code> if range delimiters should be escaped
+ */
+ public abstract void originalToString(StringBuffer sb, boolean rangeSafe);
+
+ /**
+ * Appends the raw format for this version onto the <code>sb</code> StringBuffer.
+ * @param sb The buffer that will receive the raw string format
+ * @param rangeSafe Set to <code>true</code> if range delimiters should be escaped
+ */
+ public abstract void rawToString(StringBuffer sb, boolean rangeSafe);
+
+ /**
+ * This method is package protected since it violates the immutable
+ * contract.
+ * @return The raw vector. Must be treated as read-only
+ */
+ abstract Comparable<?>[] getVector();
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/Copyright.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/Copyright.java
index 9098f377c..b56578830 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/Copyright.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/Copyright.java
@@ -11,7 +11,7 @@
package org.eclipse.equinox.internal.p2.metadata;
import java.net.URI;
-import org.eclipse.equinox.internal.provisional.p2.metadata.ICopyright;
+import org.eclipse.equinox.p2.metadata.ICopyright;
/**
* The <code>Copyright</code> class represents a software copyright. A copyright has
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
new file mode 100644
index 000000000..ef92ee045
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/IRequiredCapability.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+* Copyright (c) 2008, 2009 EclipseSource 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:
+* EclipseSource - initial API and implementation
+******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata;
+
+import org.eclipse.equinox.p2.metadata.*;
+
+/**
+ * A required capability represents some external constraint on an {@link IInstallableUnit}.
+ * Each capability represents something an {@link IInstallableUnit} needs that
+ * it expects to be provided by another {@link IInstallableUnit}. Capabilities are
+ * entirely generic, and are intended to be capable of representing anything that
+ * an {@link IInstallableUnit} may need either at install time, or at runtime.
+ * <p>
+ * Capabilities are segmented into namespaces. Anyone can introduce new
+ * capability namespaces. Some well-known namespaces are introduced directly
+ * by the provisioning framework.
+ *
+ * @see IInstallableUnit#NAMESPACE_IU_ID
+ *
+ * @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 IRequirement {
+
+ // public String getFilter();
+
+ public String getName();
+
+ public String getNamespace();
+
+ /**
+ * Returns the range of versions that satisfy this required capability. Returns
+ * an empty version range ({@link VersionRange#emptyRange} if any version
+ * will satisfy the capability.
+ * @return the range of versions that satisfy this required capability.
+ */
+ public VersionRange getRange();
+} \ 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 b5dd1ac29..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
@@ -11,40 +11,45 @@
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
-import java.util.ArrayList;
-import java.util.Map;
+import org.eclipse.equinox.p2.metadata.Version;
+
+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.internal.provisional.p2.metadata.*;
+import org.eclipse.equinox.p2.metadata.*;
+import org.eclipse.equinox.p2.metadata.expression.ExpressionUtil;
+import org.osgi.framework.Filter;
public class InstallableUnit implements IInstallableUnit {
private static final OrderedProperties NO_PROPERTIES = new OrderedProperties();
private static final IProvidedCapability[] NO_PROVIDES = new IProvidedCapability[0];
- private static final IRequiredCapability[] NO_REQUIRES = new IRequiredCapability[0];
+ private static final IRequirement[] NO_REQUIRES = new IRequirement[0];
private static final IArtifactKey[] NO_ARTIFACTS = new IArtifactKey[0];
private static final ITouchpointData[] NO_TOUCHPOINT_DATA = new ITouchpointData[0];
+ private static final ILicense[] NO_LICENSE = new ILicense[0];
private IArtifactKey[] artifacts = NO_ARTIFACTS;
- private String filter;
+ private Filter filter;
private String id;
private OrderedProperties properties;
private OrderedProperties localizedProperties;
IProvidedCapability[] providedCapabilities = NO_PROVIDES;
- private IRequiredCapability[] requires = NO_REQUIRES;
- private IRequiredCapability[] metaRequires = NO_REQUIRES;
+ private IRequirement[] requires = NO_REQUIRES;
+ private IRequirement[] metaRequires = NO_REQUIRES;
private boolean singleton;
- private ArrayList touchpointData = null;
+ private ITouchpointData[] touchpointData = NO_TOUCHPOINT_DATA;
private ITouchpointType touchpointType;
- private Version version;
+ private Version version = Version.emptyVersion;
private IUpdateDescriptor updateInfo;
- private ILicense license;
+ private ILicense[] licenses = NO_LICENSE;
private ICopyright copyright;
public InstallableUnit() {
@@ -52,26 +57,22 @@ public class InstallableUnit implements IInstallableUnit {
}
public void addTouchpointData(ITouchpointData newData) {
- ensureTouchpointDataCapacity(1);
- touchpointData.add(newData);
- }
-
- public int compareTo(Object toCompareTo) {
- if (!(toCompareTo instanceof IInstallableUnit)) {
- return -1;
+ int tl = touchpointData.length;
+ if (tl == 0)
+ touchpointData = new ITouchpointData[] {newData};
+ else {
+ ITouchpointData[] newDatas = new ITouchpointData[tl + 1];
+ System.arraycopy(touchpointData, 0, newDatas, 0, tl);
+ newDatas[tl] = newData;
+ touchpointData = newDatas;
}
- IInstallableUnit other = (IInstallableUnit) toCompareTo;
- if (getId().compareTo(other.getId()) == 0)
- return (getVersion().compareTo(other.getVersion()));
- return getId().compareTo(other.getId());
}
- private void ensureTouchpointDataCapacity(int size) {
- if (touchpointData != null) {
- touchpointData.ensureCapacity(size);
- } else {
- touchpointData = new ArrayList(size);
- }
+ public int compareTo(IInstallableUnit other) {
+ int cmp = getId().compareTo(other.getId());
+ if (cmp == 0)
+ cmp = getVersion().compareTo(other.getVersion());
+ return cmp;
}
public boolean equals(Object obj) {
@@ -95,16 +96,16 @@ public class InstallableUnit implements IInstallableUnit {
return true;
}
- public IArtifactKey[] getArtifacts() {
- return artifacts;
+ public Collection<IArtifactKey> getArtifacts() {
+ return CollectionUtils.unmodifiableList(artifacts);
}
- public String getFilter() {
+ public Filter getFilter() {
return filter;
}
- public IInstallableUnitFragment[] getFragments() {
- return null;
+ public List<IInstallableUnitFragment> getFragments() {
+ return CollectionUtils.emptyList();
}
public String getId() {
@@ -117,7 +118,7 @@ public class InstallableUnit implements IInstallableUnit {
*
* @return an <i>unmodifiable copy</i> of the IU properties.
*/
- public Map getProperties() {
+ public Map<String, String> getProperties() {
return OrderedProperties.unmodifiableProperties(properties());
}
@@ -135,18 +136,21 @@ public class InstallableUnit implements IInstallableUnit {
return properties().getProperty(key);
}
- public IProvidedCapability[] getProvidedCapabilities() {
- return providedCapabilities;
+ public Collection<IProvidedCapability> getProvidedCapabilities() {
+ return CollectionUtils.unmodifiableList(providedCapabilities);
}
- public IRequiredCapability[] getRequiredCapabilities() {
- return requires;
+ public String getProperty(String key, String locale) {
+ return TranslationSupport.getInstance().getIUProperty(this, key, locale);
+ }
+
+ public List<IRequirement> getRequiredCapabilities() {
+ return CollectionUtils.unmodifiableList(requires);
}
- public ITouchpointData[] getTouchpointData() {
- return (touchpointData == null ? NO_TOUCHPOINT_DATA //
- : (ITouchpointData[]) touchpointData.toArray(new ITouchpointData[touchpointData.size()]));
+ public List<ITouchpointData> getTouchpointData() {
+ return CollectionUtils.unmodifiableList(touchpointData);
}
public ITouchpointType getTouchpointType() {
@@ -165,10 +169,6 @@ public class InstallableUnit implements IInstallableUnit {
return result;
}
- public boolean isFragment() {
- return false;
- }
-
public boolean isResolved() {
return false;
}
@@ -195,10 +195,14 @@ public class InstallableUnit implements IInstallableUnit {
providedCapabilities = newCapabilities;
}
- public void setFilter(String filter) {
+ public void setFilter(Filter filter) {
this.filter = filter;
}
+ public void setFilter(String filter) {
+ setFilter(filter == null ? null : ExpressionUtil.parseLDAP(filter));
+ }
+
public void setId(String id) {
this.id = id;
}
@@ -209,7 +213,7 @@ public class InstallableUnit implements IInstallableUnit {
public String setLocalizedProperty(String key, String value) {
if (localizedProperties == null)
localizedProperties = new OrderedProperties();
- return (String) localizedProperties.put(key, value);
+ return localizedProperties.put(key, value);
}
public String setProperty(String key, String value) {
@@ -220,12 +224,12 @@ public class InstallableUnit implements IInstallableUnit {
return (String) properties.setProperty(key, value);
}
- public void setRequiredCapabilities(IRequiredCapability[] capabilities) {
+ public void setRequiredCapabilities(IRequirement[] capabilities) {
if (capabilities.length == 0) {
this.requires = NO_REQUIRES;
} else {
//copy array for safety
- this.requires = (IRequiredCapability[]) capabilities.clone();
+ this.requires = capabilities.clone();
}
}
@@ -257,12 +261,16 @@ public class InstallableUnit implements IInstallableUnit {
this.updateInfo = updateInfo;
}
- public void setLicense(ILicense license) {
- this.license = license;
+ public void setLicenses(ILicense[] license) {
+ this.licenses = license == null ? NO_LICENSE : license;
}
- public ILicense getLicense() {
- return license;
+ public Collection<ILicense> getLicenses() {
+ return CollectionUtils.unmodifiableList(licenses);
+ }
+
+ public ILicense[] getLicenses(String locale) {
+ return TranslationSupport.getInstance().getLicenses(this, locale);
}
public void setCopyright(ICopyright copyright) {
@@ -273,24 +281,24 @@ public class InstallableUnit implements IInstallableUnit {
return copyright;
}
- public boolean satisfies(IRequiredCapability candidate) {
- IProvidedCapability[] provides = getProvidedCapabilities();
- for (int i = 0; i < provides.length; i++)
- if (provides[i].satisfies(candidate))
- return true;
- return false;
+ public ICopyright getCopyright(String locale) {
+ return TranslationSupport.getInstance().getCopyright(this, locale);
+ }
+
+ public boolean satisfies(IRequirement candidate) {
+ return candidate.isMatch(this);
}
- public IRequiredCapability[] getMetaRequiredCapabilities() {
- return metaRequires;
+ public Collection<IRequirement> getMetaRequiredCapabilities() {
+ return CollectionUtils.unmodifiableList(metaRequires);
}
- public void setMetaRequiredCapabilities(IRequiredCapability[] metaReqs) {
+ public void setMetaRequiredCapabilities(IRequirement[] metaReqs) {
if (metaReqs.length == 0) {
this.metaRequires = NO_REQUIRES;
} else {
//copy array for safety
- this.metaRequires = (IRequiredCapability[]) metaReqs.clone();
+ this.metaRequires = metaReqs.clone();
}
}
}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnitFragment.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnitFragment.java
index 7709c9dbc..86ea33ad2 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnitFragment.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnitFragment.java
@@ -10,37 +10,36 @@
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
-import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnitFragment;
-import org.eclipse.equinox.internal.provisional.p2.metadata.IRequiredCapability;
+import java.util.List;
+import org.eclipse.equinox.p2.metadata.IInstallableUnitFragment;
+import org.eclipse.equinox.p2.metadata.IRequirement;
public class InstallableUnitFragment extends InstallableUnit implements IInstallableUnitFragment {
- private IRequiredCapability[] hostRequirements;
+ private IRequirement[] hostRequirements;
public InstallableUnitFragment() {
super();
}
- public void setHost(IRequiredCapability[] hostRequirements) {
+ public void setHost(IRequirement[] hostRequirements) {
if (hostRequirements == null)
return;
this.hostRequirements = hostRequirements;
addRequiredCapability(hostRequirements);
}
- private void addRequiredCapability(IRequiredCapability[] toAdd) {
- IRequiredCapability[] current = super.getRequiredCapabilities();
- IRequiredCapability[] result = new IRequiredCapability[current.length + toAdd.length];
- System.arraycopy(current, 0, result, 0, current.length);
- System.arraycopy(toAdd, 0, result, current.length, toAdd.length);
+ private void addRequiredCapability(IRequirement[] toAdd) {
+ List<IRequirement> current = super.getRequiredCapabilities();
+ int currSize = current.size();
+ IRequirement[] result = new IRequirement[currSize + toAdd.length];
+ for (int i = 0; i < currSize; ++i)
+ result[i] = current.get(i);
+ System.arraycopy(toAdd, 0, result, current.size(), toAdd.length);
setRequiredCapabilities(result);
}
- public boolean isFragment() {
- return true;
- }
-
- public IRequiredCapability[] getHost() {
+ public IRequirement[] getHost() {
return hostRequirements;
}
}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnitPatch.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnitPatch.java
index 31013548b..41ce8f56a 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnitPatch.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/InstallableUnitPatch.java
@@ -8,42 +8,46 @@
******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
-import org.eclipse.equinox.internal.provisional.p2.metadata.*;
+import java.util.List;
+import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils;
+import org.eclipse.equinox.p2.metadata.*;
public class InstallableUnitPatch extends InstallableUnit implements IInstallableUnitPatch {
private IRequirementChange[] changes;
- private IRequiredCapability lifeCycle;
- private IRequiredCapability[][] scope;
-
- private void addRequiredCapability(IRequiredCapability[] toAdd) {
- IRequiredCapability[] current = super.getRequiredCapabilities();
- IRequiredCapability[] result = new IRequiredCapability[current.length + toAdd.length];
- System.arraycopy(current, 0, result, 0, current.length);
- System.arraycopy(toAdd, 0, result, current.length, toAdd.length);
+ private IRequirement lifeCycle;
+ private IRequirement[][] scope;
+
+ private void addRequiredCapability(IRequirement[] toAdd) {
+ List<IRequirement> current = super.getRequiredCapabilities();
+ int currSize = current.size();
+ IRequirement[] result = new IRequirement[currSize + toAdd.length];
+ for (int i = 0; i < currSize; ++i)
+ result[i] = current.get(i);
+ System.arraycopy(toAdd, 0, result, current.size(), toAdd.length);
setRequiredCapabilities(result);
}
- public IRequiredCapability[][] getApplicabilityScope() {
+ public IRequirement[][] getApplicabilityScope() {
return scope;
}
- public IRequiredCapability getLifeCycle() {
+ public IRequirement getLifeCycle() {
return lifeCycle;
}
- public IRequirementChange[] getRequirementsChange() {
- return changes;
+ public List<IRequirementChange> getRequirementsChange() {
+ return CollectionUtils.unmodifiableList(changes);
}
- public void setApplicabilityScope(IRequiredCapability[][] applyTo) {
+ public void setApplicabilityScope(IRequirement[][] applyTo) {
scope = applyTo;
}
- public void setLifeCycle(IRequiredCapability lifeCycle) {
+ public void setLifeCycle(IRequirement lifeCycle) {
if (lifeCycle == null)
return;
this.lifeCycle = lifeCycle;
- addRequiredCapability(new IRequiredCapability[] {lifeCycle});
+ addRequiredCapability(new IRequirement[] {lifeCycle});
}
public void setRequirementsChange(IRequirementChange[] changes) {
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/License.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/License.java
index 112956f26..38c62c1b1 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/License.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/License.java
@@ -16,7 +16,7 @@ import java.math.BigInteger;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import org.eclipse.equinox.internal.provisional.p2.metadata.ILicense;
+import org.eclipse.equinox.p2.metadata.ILicense;
/**
* The <code>License</code> class represents a software license. A license has required body text
@@ -38,7 +38,7 @@ public class License implements ILicense {
/**
* The <code>digest</code> is the cached message digest of the normalized body
*/
- private BigInteger digest;
+ private String digest;
/**
* Creates a new license object which is identified by users using the <code>body</code> field.
@@ -49,11 +49,12 @@ public class License implements ILicense {
* @param body the license body, cannot be <code>null</code>
* @throws IllegalArgumentException when the <code>body</code> is <code>null</code>
*/
- public License(URI location, String body) {
+ public License(URI location, String body, String uuid) {
if (body == null)
throw new IllegalArgumentException("body cannot be null"); //$NON-NLS-1$
this.body = body;
this.location = location;
+ this.digest = uuid;
}
/**
@@ -78,9 +79,10 @@ public class License implements ILicense {
* version of the license where all whitespace has been reduced to one space.
* @return the message digest as a <code>BigInteger</code>, never <code>null</code>
*/
- public synchronized BigInteger getDigest() {
+ public synchronized String getUUID() {
if (digest == null)
- digest = calculateLicenseDigest();
+ digest = calculateLicenseDigest().toString(16);
+
return digest;
}
@@ -94,7 +96,7 @@ public class License implements ILicense {
return false;
if (obj instanceof ILicense) {
ILicense other = (ILicense) obj;
- if (other.getDigest().equals(getDigest()))
+ if (other.getUUID().equals(getUUID()))
return true;
}
return false;
@@ -104,7 +106,7 @@ public class License implements ILicense {
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
- return getDigest().hashCode();
+ return getUUID().hashCode();
}
private BigInteger calculateLicenseDigest() {
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 10a596c32..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
@@ -16,9 +16,6 @@ import org.eclipse.osgi.util.NLS;
* TODO Shouldn't be a public class in an API package.
*/
public class Messages extends NLS {
- public static String _0_is_not_a_positive_integer_in_osgi_1;
-
- public static String _0_is_not_a_string_in_osgi_1;
public static String _0_is_not_a_valid_qualifier_in_osgi_1;
@@ -54,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;
@@ -68,8 +81,6 @@ public class Messages extends NLS {
public static String illegal_character_encountered_ascii_0;
- public static String illegal_number_of_entries_0_in_osgi_1;
-
public static String missing_comma_in_range_0;
public static String negative_character_range;
@@ -80,14 +91,14 @@ public class Messages extends NLS {
public static String only_format_specified_0;
+ public static String only_max_and_empty_string_defaults_can_have_translations;
+
public static String original_must_start_with_colon_0;
public static String original_stated_but_missing_0;
public static String pad_defined_more_then_once;
- public static String pad_not_allowed_in_osgi_0;
-
public static String performing_subquery;
public static String premature_end_of_format;
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/NotRequirement.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/NotRequirement.java
deleted file mode 100644
index 752d5444e..000000000
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/NotRequirement.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package org.eclipse.equinox.internal.p2.metadata;
-
-import org.eclipse.equinox.internal.provisional.p2.metadata.*;
-
-public class NotRequirement implements IRequiredCapability {
- private IRequiredCapability negatedRequirement;
-
- public NotRequirement(IRequiredCapability iRequiredCapabilities) {
- negatedRequirement = iRequiredCapabilities;
- }
-
- public IRequiredCapability getRequirement() {
- return negatedRequirement;
- }
-
- public String getFilter() {
- return negatedRequirement.getFilter();
- }
-
- public String getName() {
- return negatedRequirement.getName();
- }
-
- public String getNamespace() {
- return negatedRequirement.getNamespace();
- }
-
- public VersionRange getRange() {
- return negatedRequirement.getRange();
- }
-
- public String[] getSelectors() {
- return negatedRequirement.getSelectors();
- }
-
- public boolean isGreedy() {
- return negatedRequirement.isGreedy();
- }
-
- public boolean isMultiple() {
- return negatedRequirement.isMultiple();
- }
-
- public boolean isOptional() {
- return negatedRequirement.isOptional();
- }
-
- public void setFilter(String filter) {
- // TODO Auto-generated method stub
-
- }
-
- public void setSelectors(String[] selectors) {
- // TODO Auto-generated method stub
-
- }
-
- public boolean isNegation() {
- return true;
- }
-
- public String toString() {
- return "NOT(" + negatedRequirement.toString() + ')'; //$NON-NLS-1$
- }
-
- public boolean satisfiedBy(IProvidedCapability cap) {
- return !negatedRequirement.satisfiedBy(cap);
- }
-}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ORRequirement.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ORRequirement.java
deleted file mode 100644
index 3aab2c9ff..000000000
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/ORRequirement.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package org.eclipse.equinox.internal.p2.metadata;
-
-import org.eclipse.equinox.internal.provisional.p2.metadata.*;
-
-public class ORRequirement implements IRequiredCapability {
- private IRequiredCapability[] oredRequirements;
-
- public ORRequirement(IRequiredCapability[] reqs) {
- oredRequirements = reqs;
- }
-
- public IRequiredCapability[] getRequirements() {
- return oredRequirements;
- }
-
- public String getFilter() {
- // TODO Auto-generated method stub
- return null;
- }
-
- public String getName() {
- // TODO Auto-generated method stub
- return null;
- }
-
- public String getNamespace() {
- // TODO Auto-generated method stub
- return null;
- }
-
- public VersionRange getRange() {
- // TODO Auto-generated method stub
- return null;
- }
-
- public String[] getSelectors() {
- // TODO Auto-generated method stub
- return null;
- }
-
- public boolean isGreedy() {
- // TODO Auto-generated method stub
- return true;
- }
-
- public boolean isMultiple() {
- // TODO Auto-generated method stub
- return false;
- }
-
- public boolean isOptional() {
- return false;
- }
-
- public void setFilter(String filter) {
- // TODO Auto-generated method stub
-
- }
-
- public void setSelectors(String[] selectors) {
- // TODO Auto-generated method stub
-
- }
-
- public boolean isNegation() {
- return false;
- }
-
- public String toString() {
- String result = "OR(";
- for (int i = 0; i < oredRequirements.length; i++) {
- result += oredRequirements[i].toString();
- }
- return result + ")";
- }
-
- public boolean satisfiedBy(IProvidedCapability cap) {
- for (int i = 0; i < oredRequirements.length; i++) {
- boolean result = oredRequirements[i].satisfiedBy(cap);
- if (result)
- return true;
- }
- return false;
- }
-}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/OSGiVersion.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/OSGiVersion.java
new file mode 100644
index 000000000..979494d49
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/OSGiVersion.java
@@ -0,0 +1,213 @@
+/*******************************************************************************
+ * 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;
+
+import org.eclipse.equinox.p2.metadata.IVersionFormat;
+import org.eclipse.equinox.p2.metadata.Version;
+
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * @Immutable
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class OSGiVersion extends BasicVersion {
+
+ private static final long serialVersionUID = -4530178927569560877L;
+
+ private static final boolean[] allowedOSGiChars;
+
+ private final int major;
+
+ private final int minor;
+
+ private final int micro;
+
+ private final Comparable<?> qualifier;
+
+ static {
+ allowedOSGiChars = new boolean[128];
+ for (int c = '0'; c <= '9'; ++c)
+ allowedOSGiChars[c] = true;
+ for (int c = 'A'; c <= 'Z'; ++c)
+ allowedOSGiChars[c] = true;
+ for (int c = 'a'; c <= 'z'; ++c)
+ allowedOSGiChars[c] = true;
+ allowedOSGiChars['_'] = true;
+ allowedOSGiChars['-'] = true;
+ }
+
+ public static boolean isValidOSGiQualifier(Comparable<?> e) {
+ if (e == VersionVector.MAXS_VALUE)
+ return true;
+
+ if (!(e instanceof String))
+ return false;
+
+ String s = (String) e;
+ int idx = s.length();
+ boolean[] allowed = allowedOSGiChars;
+ while (--idx >= 0) {
+ int c = s.charAt(idx);
+ if (c < '-' || c > 'z' || !allowed[c])
+ return false;
+ }
+ return true;
+ }
+
+ static BasicVersion fromVector(Comparable<?>[] vector, Comparable<? extends Object> pad) {
+ if (vector.length != 4) {
+ if (vector.length == 0) {
+ if (pad == null)
+ return (BasicVersion) emptyVersion;
+ if (pad == VersionVector.MAX_VALUE)
+ return (BasicVersion) MAX_VERSION;
+ }
+ throw new IllegalArgumentException();
+ }
+ int major = ((Integer) vector[0]).intValue();
+ int minor = ((Integer) vector[1]).intValue();
+ int micro = ((Integer) vector[2]).intValue();
+ Comparable<?> qualifier = vector[3];
+ return (major == 0 && minor == 0 && micro == 0 && qualifier == VersionVector.MINS_VALUE) ? (BasicVersion) emptyVersion : new OSGiVersion(major, minor, micro, qualifier);
+ }
+
+ public OSGiVersion(int major, int minor, int micro, Comparable<? extends Object> qualifier) {
+ this.major = major;
+ this.minor = minor;
+ this.micro = micro;
+ if (!isValidOSGiQualifier(qualifier))
+ throw new IllegalArgumentException(NLS.bind(Messages._0_is_not_a_valid_qualifier_in_osgi_1, "qualifier", this)); //$NON-NLS-1$
+ this.qualifier = qualifier;
+ }
+
+ public int compareTo(Version v) {
+ int result;
+ if (!(v instanceof OSGiVersion)) {
+ BasicVersion ov = (BasicVersion) v;
+ result = VersionVector.compare(getVector(), null, ov.getVector(), ov.getPad());
+ } else {
+ OSGiVersion ov = (OSGiVersion) v;
+ result = major - ov.major;
+ if (result == 0) {
+ result = minor - ov.minor;
+ if (result == 0) {
+ result = micro - ov.micro;
+ if (result == 0)
+ result = VersionVector.compareSegments(qualifier, ov.qualifier);
+ }
+ }
+ }
+ return result;
+ }
+
+ public boolean equals(Object object) {
+ if (object == this)
+ return true;
+
+ if (!(object instanceof OSGiVersion)) {
+ if (object instanceof BasicVersion) {
+ BasicVersion ov = (BasicVersion) object;
+ return VersionVector.equals(getVector(), null, ov.getVector(), ov.getPad());
+ }
+ return false;
+ }
+
+ OSGiVersion other = (OSGiVersion) object;
+ return micro == other.micro && minor == other.minor && major == other.major && qualifier.equals(other.qualifier);
+ }
+
+ public IVersionFormat getFormat() {
+ return VersionFormat.OSGI_FORMAT;
+ }
+
+ public int getMajor() {
+ return major;
+ }
+
+ public int getMicro() {
+ return micro;
+ }
+
+ public int getMinor() {
+ return minor;
+ }
+
+ public String getOriginal() {
+ return toString();
+ }
+
+ public String getQualifier() {
+ return qualifier == VersionVector.MAXS_VALUE ? IVersionFormat.DEFAULT_MAX_STRING_TRANSLATION : (String) qualifier;
+ }
+
+ public int hashCode() {
+ return (major << 24) + (minor << 16) + (micro << 8) + qualifier.hashCode();
+ }
+
+ public boolean isOSGiCompatible() {
+ return true;
+ }
+
+ public void originalToString(StringBuffer sb, boolean rangeSafe) {
+ toString(sb);
+ }
+
+ public void rawToString(StringBuffer sb, boolean rangeSafe) {
+ sb.append(major);
+ sb.append('.');
+ sb.append(minor);
+ sb.append('.');
+ sb.append(micro);
+ sb.append('.');
+ sb.append('\'');
+ sb.append(qualifier);
+ sb.append('\'');
+ }
+
+ public void toString(StringBuffer sb) {
+ sb.append(major);
+ sb.append('.');
+ sb.append(minor);
+ sb.append('.');
+ sb.append(micro);
+ if (qualifier != VersionVector.MINS_VALUE) {
+ sb.append('.');
+ sb.append(getQualifier());
+ }
+ }
+
+ public Comparable<?>[] getVector() {
+ return new Comparable[] {VersionParser.valueOf(major), VersionParser.valueOf(minor), VersionParser.valueOf(micro), qualifier};
+ }
+
+ public Comparable<?> getPad() {
+ return null;
+ }
+
+ public Comparable<?> getSegment(int index) {
+ switch (index) {
+ case 0 :
+ return VersionParser.valueOf(major);
+ case 1 :
+ return VersionParser.valueOf(minor);
+ case 2 :
+ return VersionParser.valueOf(micro);
+ case 3 :
+ return qualifier;
+ }
+ throw new ArrayIndexOutOfBoundsException(index); // Not in the imaginary vector array
+ }
+
+ public int getSegmentCount() {
+ return 4;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/OmniVersion.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/OmniVersion.java
new file mode 100644
index 000000000..c45171038
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/OmniVersion.java
@@ -0,0 +1,247 @@
+/*******************************************************************************
+ * 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;
+
+import org.eclipse.equinox.p2.metadata.IVersionFormat;
+import org.eclipse.equinox.p2.metadata.Version;
+
+/**
+ * <p>The Generic Omni Version is composed of a vector of Comparable objects and a pad value. The pad
+ * might be <code>null</code>. The vector can contain integers, strings, {@link VersionVector}
+ * instances, or one of the special objects {@link VersionVector#MAX_VALUE MAX_VALUE},
+ * {@link VersionVector#MAXS_VALUE MAXS_VALUE}, or {@link VersionVector#MIN_VALUE MIN_VALUE}.</p>
+ *
+ * <p>When two versions are compared, they are always considered padded to infinity by their
+ * pad value or by {@link VersionVector#MIN_VALUE MIN_VALUE} in case the pad value is
+ * <code>null</code>. The comparison is type sensitive so that:</p><pre>
+ * MAX_VALUE &gt; Integer &gt; VersionVector &gt; MAXS_VALUE &gt; String &gt; MIN_VALUE<br/>
+ * </pre>
+ *
+ * @Immutable
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class OmniVersion extends BasicVersion {
+ private static final long serialVersionUID = 1996212688810048879L;
+
+ private static OmniVersion minimumVersion;
+
+ private static OmniVersion maximumVersion;
+
+ private final Comparable<?>[] vector;
+
+ private final Comparable<?> padValue;
+
+ /**
+ * The optional format
+ */
+ private final IVersionFormat format;
+
+ /**
+ * The optional original string
+ */
+ private final String original;
+
+ static BasicVersion fromVector(Comparable<?>[] vector, Comparable<?> padValue, IVersionFormat format, String original) {
+ if (vector.length == 0) {
+ if (padValue == null)
+ return (BasicVersion) emptyVersion;
+ if (padValue == VersionVector.MAX_VALUE)
+ return (BasicVersion) MAX_VERSION;
+ }
+ if (vector.length == 3 && padValue == null && vector[0] == VersionParser.ZERO_INT && vector[1] == VersionParser.ZERO_INT && vector[2] == VersionParser.ZERO_INT)
+ return (BasicVersion) emptyVersion;
+
+ return new OmniVersion(vector, padValue, format, original);
+ }
+
+ public static Version createMinVersion() {
+ if (minimumVersion == null)
+ minimumVersion = new OmniVersion(new Comparable[0], null, null, null);
+ return minimumVersion;
+ }
+
+ public static Version createMaxVersion() {
+ if (maximumVersion == null)
+ maximumVersion = new OmniVersion(new Comparable[0], VersionVector.MAX_VALUE, null, null);
+ return maximumVersion;
+ }
+
+ private OmniVersion(Comparable<?>[] array, Comparable<?> padValue, IVersionFormat format, String original) {
+ this.vector = array;
+ this.padValue = padValue;
+ this.format = format;
+ this.original = original;
+ }
+
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+
+ if (!(o instanceof BasicVersion))
+ return false;
+
+ BasicVersion ov = (BasicVersion) o;
+ return VersionVector.equals(vector, padValue, ov.getVector(), ov.getPad());
+ }
+
+ public IVersionFormat getFormat() {
+ return format;
+ }
+
+ public int getMajor() {
+ return getIntElement(0);
+ }
+
+ public int getMicro() {
+ return getIntElement(2);
+ }
+
+ public int getMinor() {
+ return getIntElement(1);
+ }
+
+ public String getOriginal() {
+ return original;
+ }
+
+ public String getQualifier() {
+ if (vector.length == 3)
+ return VersionVector.MINS_VALUE;
+
+ if (vector.length != 4)
+ throw new UnsupportedOperationException();
+
+ Comparable<?> qualifier = vector[3];
+ if (qualifier == VersionVector.MAXS_VALUE)
+ return IVersionFormat.DEFAULT_MAX_STRING_TRANSLATION;
+ if (!(qualifier instanceof String))
+ throw new UnsupportedOperationException();
+ return (String) qualifier;
+ }
+
+ public int hashCode() {
+ return VersionVector.hashCode(vector, padValue);
+ }
+
+ /**
+ * Checks if this version is in compliance with the OSGi version spec.
+ * @return A flag indicating whether the version is OSGi compatible or not.
+ */
+ public boolean isOSGiCompatible() {
+ if (vector.length < 3 || vector.length > 4)
+ return (this == emptyVersion || this == MAX_VERSION);
+
+ if (getPad() != null)
+ return false;
+
+ for (int i = 0; i < 3; ++i) {
+ Object e = vector[i];
+ if (!(e instanceof Integer && ((Integer) e).intValue() >= 0))
+ return false;
+ }
+
+ if (vector.length == 3)
+ return true; // No qualifier. Still compatible
+ return OSGiVersion.isValidOSGiQualifier(vector[3]);
+ }
+
+ /**
+ * Appends the original for this version onto the <code>sb</code> StringBuffer
+ * if present.
+ * @param sb The buffer that will receive the raw string format
+ * @param rangeSafe Set to <code>true</code> if range delimiters should be escaped
+ */
+ public void originalToString(StringBuffer sb, boolean rangeSafe) {
+ if (original != null) {
+ if (rangeSafe) {
+ // Escape all range delimiters while appending
+ String s = original;
+ int end = s.length();
+ for (int idx = 0; idx < end; ++idx) {
+ char c = s.charAt(idx);
+ if (c == '\\' || c == '[' || c == '(' || c == ']' || c == ')' || c == ',' || c <= ' ')
+ sb.append('\\');
+ sb.append(c);
+ }
+ } else
+ sb.append(original);
+ }
+ }
+
+ /**
+ * Appends the raw format for this version onto the <code>sb</code> StringBuffer.
+ * @param sb The buffer that will receive the raw string format
+ * @param rangeSafe Set to <code>true</code> if range delimiters should be escaped
+ */
+ public void rawToString(StringBuffer sb, boolean rangeSafe) {
+ VersionVector.toString(sb, vector, padValue, rangeSafe);
+ }
+
+ /**
+ * Appends the string representation of this version onto the
+ * <code>sb</code> StringBuffer.
+ * @param sb The buffer that will receive the version string
+ */
+ public void toString(StringBuffer sb) {
+ if (this == emptyVersion)
+ sb.append("0.0.0"); //$NON-NLS-1$
+ else {
+ sb.append(RAW_PREFIX);
+ VersionVector.toString(sb, vector, padValue, false);
+ if (format != null || original != null) {
+ sb.append('/');
+ if (format != null)
+ format.toString(sb);
+ if (original != null) {
+ sb.append(':');
+ originalToString(sb, false);
+ }
+ }
+ }
+ }
+
+ private int getIntElement(int i) {
+ if (!(vector.length > i && vector[i] instanceof Integer))
+ throw new UnsupportedOperationException();
+ return ((Integer) vector[i]).intValue();
+ }
+
+ // Preserve singletons during deserialization
+ private Object readResolve() {
+ Version v = this;
+ if (equals(MAX_VERSION))
+ v = MAX_VERSION;
+ else if (equals(emptyVersion))
+ v = emptyVersion;
+ return v;
+ }
+
+ public Comparable<?> getPad() {
+ return padValue;
+ }
+
+ public Comparable<?> getSegment(int index) {
+ return vector[index];
+ }
+
+ public int getSegmentCount() {
+ return vector.length;
+ }
+
+ Comparable<?>[] getVector() {
+ return vector;
+ }
+
+ public int compareTo(Version v) {
+ BasicVersion ov = (BasicVersion) v;
+ return VersionVector.compare(vector, padValue, ov.getVector(), ov.getPad());
+ }
+}
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 1d8ed6dfa..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
@@ -12,7 +12,8 @@
package org.eclipse.equinox.internal.p2.metadata;
import org.eclipse.core.runtime.Assert;
-import org.eclipse.equinox.internal.provisional.p2.metadata.*;
+import org.eclipse.equinox.p2.metadata.IProvidedCapability;
+import org.eclipse.equinox.p2.metadata.Version;
/**
* Describes a capability as exposed or required by an installable unit
@@ -59,17 +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;
}
-
}
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 690c9e1ba..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
@@ -12,7 +12,9 @@
package org.eclipse.equinox.internal.p2.metadata;
import org.eclipse.core.runtime.Assert;
-import org.eclipse.equinox.internal.provisional.p2.metadata.*;
+import org.eclipse.equinox.p2.metadata.*;
+import org.eclipse.equinox.p2.metadata.expression.*;
+import org.osgi.framework.Filter;
/**
* A required capability represents some external constraint on an {@link IInstallableUnit}.
@@ -28,72 +30,124 @@ import org.eclipse.equinox.internal.provisional.p2.metadata.*;
* @see IInstallableUnit#NAMESPACE_IU_ID
*/
public class RequiredCapability implements IRequiredCapability {
- private static final String[] NO_SELECTORS = new String[0];
+ private final Filter filter;
+ private final boolean greedy;
+ private final IMatchExpression<IInstallableUnit> matchExpression;
+ private final int min;
+ private final int max;
- private String filter;
- private final boolean multiple;
- private final String name;//never null
- private final String namespace;//never null
- private boolean optional;
- private boolean greedy = true;
- private final VersionRange range;//never null
- private String[] selectors = NO_SELECTORS;//never null
+ 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;
- this.optional = optional;
- this.filter = filter;
- this.multiple = multiple;
+ 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, optional, multiple);
+ 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, Filter filter, int min, int max, boolean greedy) {
+ Assert.isNotNull(namespace);
+ Assert.isNotNull(name);
+ 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 {
+ 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());
+ }
+ }
+ }
+ 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 (multiple != other.isMultiple())
- return false;
- if (!name.equals(other.getName()))
- return false;
- if (!namespace.equals(other.getNamespace()))
- return false;
- if (optional != other.isOptional())
- return false;
- if (!range.equals(other.getRange()))
- return false;
- return true;
- }
-
- public String getFilter() {
- return filter;
+ 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];
}
/**
@@ -103,55 +157,17 @@ public class RequiredCapability implements IRequiredCapability {
* @return the range of versions that satisfy this required capability.
*/
public VersionRange getRange() {
- return range;
- }
-
- /**
- * Returns the properties to use for evaluating required capability filters
- * downstream from this capability. For example, if the selector "doc"
- * is provided, then a downstream InstallableUnit with a required capability
- * filtered with "doc=true" will be included.
- */
- public String[] getSelectors() {
- return selectors;
+ return extractRange(matchExpression);
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((filter == null) ? 0 : filter.hashCode());
- result = prime * result + (multiple ? 1231 : 1237);
- result = prime * result + name.hashCode();
- result = prime * result + namespace.hashCode();
- result = prime * result + (optional ? 1231 : 1237);
- result = prime * result + range.hashCode();
+ result = prime * result + matchExpression.hashCode();
return result;
}
- public boolean isMultiple() {
- return multiple;
- }
-
- public boolean isOptional() {
- return optional;
- }
-
- /**
- * 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) {
- this.filter = filter;
- }
-
- /**
- * TODO This object shouldn't be mutable since it makes equality unstable, and
- * introduces lifecycle issues (how are the changes persisted, etc)
- */
- public void setSelectors(String[] selectors) {
- this.selectors = selectors;
- }
-
public boolean isGreedy() {
return greedy;
}
@@ -173,6 +189,7 @@ public class RequiredCapability implements IRequiredCapability {
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(']');
@@ -181,15 +198,59 @@ public class RequiredCapability implements IRequiredCapability {
return result.toString();
}
- public boolean isNegation() {
- return false;
+ public int getMin() {
+ return min;
+ }
+
+ public int getMax() {
+ return max;
+ }
+
+ public IMatchExpression<IInstallableUnit> getMatches() {
+ return matchExpression;
+ }
+
+ public Filter getFilter() {
+ return filter;
+ }
+
+ public boolean isMatch(IInstallableUnit candidate) {
+ 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);
}
- 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());
+ 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 49742867a..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,7 +10,7 @@
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
-import org.eclipse.equinox.internal.provisional.p2.metadata.*;
+import org.eclipse.equinox.p2.metadata.IRequirementChange;
public class RequirementChange implements IRequirementChange {
private IRequiredCapability applyOn;
@@ -39,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 0d4459aa7..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
@@ -11,41 +11,48 @@
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
+import org.eclipse.equinox.p2.metadata.Version;
+
import java.util.*;
-import org.eclipse.equinox.internal.provisional.p2.metadata.*;
+import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils;
+import org.eclipse.equinox.p2.metadata.*;
+import org.osgi.framework.Filter;
public class ResolvedInstallableUnit implements IInstallableUnit {
- private static IInstallableUnit[] NO_IU = new IInstallableUnit[0];
+ private static IInstallableUnitFragment[] NO_IU = new IInstallableUnitFragment[0];
- private IInstallableUnit[] fragments = NO_IU;
- protected IInstallableUnit original;
+ private final IInstallableUnitFragment[] fragments;
+ protected final IInstallableUnit original;
public ResolvedInstallableUnit(IInstallableUnit resolved) {
- this.original = resolved;
+ this(resolved, null);
}
public ResolvedInstallableUnit(IInstallableUnit resolved, IInstallableUnitFragment[] fragments) {
this.original = resolved;
- this.fragments = fragments;
+ this.fragments = fragments == null ? NO_IU : fragments;
}
- public IInstallableUnitFragment[] getFragments() {
- ArrayList result = new ArrayList();
- if (fragments != null)
- result.addAll(Arrays.asList(fragments));
- for (int i = 0; i < result.size(); i++) {
- IInstallableUnit fragment = (IInstallableUnit) result.get(i);
+ public List<IInstallableUnitFragment> getFragments() {
+ int fcount = fragments.length;
+ if (fcount == 0)
+ return CollectionUtils.emptyList();
+
+ ArrayList<IInstallableUnitFragment> result = new ArrayList<IInstallableUnitFragment>(fcount);
+ result.addAll(Arrays.asList(fragments));
+ for (int i = 0; i < fcount; i++) {
+ IInstallableUnit fragment = fragments[i];
if (fragment.isResolved())
- result.addAll(Arrays.asList(fragment.getFragments()));
+ result.addAll(fragment.getFragments());
}
- return (IInstallableUnitFragment[]) result.toArray(new IInstallableUnitFragment[result.size()]);
+ return result;
}
- public IArtifactKey[] getArtifacts() {
+ public Collection<IArtifactKey> getArtifacts() {
return original.getArtifacts();
}
- public String getFilter() {
+ public Filter getFilter() {
return original.getFilter();
}
@@ -57,47 +64,56 @@ public class ResolvedInstallableUnit implements IInstallableUnit {
return original.getProperty(key);
}
- public Map getProperties() {
+ public Map<String, String> getProperties() {
return original.getProperties();
}
- public IProvidedCapability[] getProvidedCapabilities() {
- ArrayList result = new ArrayList();
- result.addAll(Arrays.asList(original.getProvidedCapabilities()));
- for (int i = 0; i < fragments.length; i++) {
- result.addAll(Arrays.asList(fragments[i].getProvidedCapabilities()));
- }
- return (IProvidedCapability[]) result.toArray(new IProvidedCapability[result.size()]);
+ public String getProperty(String key, String locale) {
+ return original.getProperty(key, locale);
}
- public IRequiredCapability[] getRequiredCapabilities() {
- ArrayList result = new ArrayList();
- result.addAll(Arrays.asList(original.getRequiredCapabilities()));
- for (int i = 0; i < fragments.length; i++) {
- result.addAll(Arrays.asList(fragments[i].getRequiredCapabilities()));
- }
- return (IRequiredCapability[]) result.toArray(new IRequiredCapability[result.size()]);
+ public Collection<IProvidedCapability> getProvidedCapabilities() {
+ Collection<IProvidedCapability> originalCapabilities = original.getProvidedCapabilities();
+ if (fragments.length == 0)
+ return originalCapabilities;
+
+ ArrayList<IProvidedCapability> result = new ArrayList<IProvidedCapability>(originalCapabilities);
+ for (int i = 0; i < fragments.length; i++)
+ result.addAll(fragments[i].getProvidedCapabilities());
+ return result;
}
- public IRequiredCapability[] getMetaRequiredCapabilities() {
- ArrayList result = new ArrayList();
- result.addAll(Arrays.asList(original.getMetaRequiredCapabilities()));
- for (int i = 0; i < fragments.length; i++) {
- result.addAll(Arrays.asList(fragments[i].getMetaRequiredCapabilities()));
- }
- return (IRequiredCapability[]) result.toArray(new IRequiredCapability[result.size()]);
+ public Collection<IRequirement> getRequiredCapabilities() {
+ Collection<IRequirement> originalCapabilities = original.getRequiredCapabilities();
+ if (fragments.length == 0)
+ return originalCapabilities;
+
+ ArrayList<IRequirement> result = new ArrayList<IRequirement>(originalCapabilities);
+ for (int i = 0; i < fragments.length; i++)
+ result.addAll(fragments[i].getRequiredCapabilities());
+ return result;
}
- public ITouchpointData[] getTouchpointData() {
- ArrayList result = new ArrayList();
- result.addAll(Arrays.asList(original.getTouchpointData()));
- for (int i = 0; i < fragments.length; i++) {
- ITouchpointData[] data = fragments[i].getTouchpointData();
- for (int j = 0; j < data.length; j++) {
- result.add(data[j]);
- }
- }
- return (ITouchpointData[]) result.toArray(new ITouchpointData[result.size()]);
+ public Collection<IRequirement> getMetaRequiredCapabilities() {
+ Collection<IRequirement> originalCapabilities = original.getMetaRequiredCapabilities();
+ if (fragments.length == 0)
+ return originalCapabilities;
+
+ ArrayList<IRequirement> result = new ArrayList<IRequirement>(originalCapabilities);
+ for (int i = 0; i < fragments.length; i++)
+ result.addAll(fragments[i].getMetaRequiredCapabilities());
+ return result;
+ }
+
+ public List<ITouchpointData> getTouchpointData() {
+ List<ITouchpointData> originalTouchpointData = original.getTouchpointData();
+ if (fragments.length == 0)
+ return originalTouchpointData;
+
+ ArrayList<ITouchpointData> result = new ArrayList<ITouchpointData>(originalTouchpointData);
+ for (int i = 0; i < fragments.length; i++)
+ result.addAll(fragments[i].getTouchpointData());
+ return result;
}
public ITouchpointType getTouchpointType() {
@@ -108,10 +124,6 @@ public class ResolvedInstallableUnit implements IInstallableUnit {
return original.getVersion();
}
- public boolean isFragment() {
- return original.isFragment();
- }
-
public boolean isSingleton() {
return original.isSingleton();
}
@@ -139,14 +151,11 @@ public class ResolvedInstallableUnit implements IInstallableUnit {
return original;
}
- public int compareTo(Object toCompareTo) {
- if (!(toCompareTo instanceof IInstallableUnit)) {
- return -1;
- }
- IInstallableUnit other = (IInstallableUnit) toCompareTo;
- if (getId().compareTo(other.getId()) == 0)
- return (getVersion().compareTo(other.getVersion()));
- return getId().compareTo(other.getId());
+ public int compareTo(IInstallableUnit other) {
+ int cmp = getId().compareTo(other.getId());
+ if (cmp == 0)
+ cmp = getVersion().compareTo(other.getVersion());
+ return cmp;
}
public boolean isResolved() {
@@ -161,20 +170,24 @@ public class ResolvedInstallableUnit implements IInstallableUnit {
return original.getUpdateDescriptor();
}
- public ILicense getLicense() {
- return original.getLicense();
+ public Collection<ILicense> getLicenses() {
+ return original.getLicenses();
+ }
+
+ public ILicense[] getLicenses(String locale) {
+ return original.getLicenses(locale);
}
public ICopyright getCopyright() {
return original.getCopyright();
}
- public boolean satisfies(IRequiredCapability candidate) {
- IProvidedCapability[] provides = getProvidedCapabilities();
- for (int i = 0; i < provides.length; i++)
- if (provides[i].satisfies(candidate))
- return true;
- return false;
+ public ICopyright getCopyright(String locale) {
+ return original.getCopyright(locale);
+ }
+
+ public boolean satisfies(IRequirement candidate) {
+ return candidate.isMatch(this);
}
}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TouchpointData.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TouchpointData.java
index 9ea83d479..791363dd5 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TouchpointData.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TouchpointData.java
@@ -11,9 +11,12 @@
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
-import java.util.*;
+import java.util.Collections;
+import java.util.Map;
import java.util.Map.Entry;
-import org.eclipse.equinox.internal.provisional.p2.metadata.*;
+import org.eclipse.equinox.internal.provisional.p2.metadata.MetadataFactory;
+import org.eclipse.equinox.p2.metadata.ITouchpointData;
+import org.eclipse.equinox.p2.metadata.ITouchpointInstruction;
/**
* Touchpoint data instances contain the additional information needed by a touchpoint
@@ -31,7 +34,7 @@ public class TouchpointData implements ITouchpointData {
* of keys supported is up to the touchpoint that will process these
* instructions. This map is never null.
*/
- private Map instructions;
+ private Map<String, ITouchpointInstruction> instructions;
public int hashCode() {
return 31 * 1 + ((instructions == null) ? 0 : instructions.hashCode());
@@ -56,7 +59,7 @@ public class TouchpointData implements ITouchpointData {
/**
* Clients must use the factory method on {@link MetadataFactory}.
*/
- public TouchpointData(Map instructions) {
+ public TouchpointData(Map<String, ITouchpointInstruction> instructions) {
this.instructions = instructions;
}
@@ -64,7 +67,7 @@ public class TouchpointData implements ITouchpointData {
* Returns the touchpoint instruction corresponding to the given key.
*/
public ITouchpointInstruction getInstruction(String instructionKey) {
- return (ITouchpointInstruction) instructions.get(instructionKey);
+ return instructions.get(instructionKey);
}
/**
@@ -73,7 +76,7 @@ public class TouchpointData implements ITouchpointData {
*
* @return the touchpoint instructions
*/
- public Map getInstructions() {
+ public Map<String, ITouchpointInstruction> getInstructions() {
return Collections.unmodifiableMap(instructions);
}
@@ -82,8 +85,7 @@ public class TouchpointData implements ITouchpointData {
*/
public String toString() {
StringBuffer result = new StringBuffer();
- for (Iterator iterator = instructions.entrySet().iterator(); iterator.hasNext();) {
- Entry instruction = (Entry) iterator.next();
+ for (Entry<String, ITouchpointInstruction> instruction : instructions.entrySet()) {
result.append(instruction.getKey()).append(" -> ").append(instruction.getValue()).append('\n'); //$NON-NLS-1$
}
return result.toString();
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TouchpointInstruction.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TouchpointInstruction.java
index e59e9beea..5928639a1 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TouchpointInstruction.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TouchpointInstruction.java
@@ -11,10 +11,10 @@
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
-import java.util.Iterator;
import java.util.Map;
-import org.eclipse.equinox.internal.provisional.p2.metadata.ITouchpointInstruction;
+import java.util.Map.Entry;
import org.eclipse.equinox.internal.provisional.p2.metadata.MetadataFactory;
+import org.eclipse.equinox.p2.metadata.ITouchpointInstruction;
/**
* A touchpoint instruction contains either a sequence of instruction statements
@@ -65,16 +65,18 @@ public class TouchpointInstruction implements ITouchpointInstruction {
* where the keys are parameter names, and the values are parameter values
* @return An encoded touchpoint instruction statement
*/
- public static String encodeAction(String actionName, Map parameters) {
+ public static String encodeAction(String actionName, Map<String, String> parameters) {
StringBuffer result = new StringBuffer(actionName);
result.append('(');
- for (Iterator it = parameters.entrySet().iterator(); it.hasNext();) {
- Map.Entry entry = (Map.Entry) it.next();
+ boolean first = true;
+ for (Entry<String, String> entry : parameters.entrySet()) {
+ if (first)
+ first = false;
+ else
+ result.append(',');
result.append(entry.getKey());
result.append(':');
- appendEncoded(result, (String) entry.getValue());
- if (it.hasNext())
- result.append(',');
+ appendEncoded(result, entry.getValue());
}
result.append(')').append(';');
return result.toString();
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TouchpointType.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TouchpointType.java
index 4056d7f09..86bb05b87 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TouchpointType.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TouchpointType.java
@@ -11,8 +11,9 @@
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
-import org.eclipse.equinox.internal.provisional.p2.metadata.ITouchpointType;
-import org.eclipse.equinox.internal.provisional.p2.metadata.Version;
+import org.eclipse.equinox.p2.metadata.Version;
+
+import org.eclipse.equinox.p2.metadata.ITouchpointType;
/**
* Identifies a particular touchpoint. A touchpoint is identified by an id
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
new file mode 100644
index 000000000..e42289040
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/TranslationSupport.java
@@ -0,0 +1,299 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 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
+ * EclipseSource - ongoing development
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata;
+
+import java.lang.ref.SoftReference;
+import java.util.*;
+import org.eclipse.core.runtime.IStatus;
+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;
+
+/**
+ * TranslationSupport provides string translations for properties of an
+ * IInstallableUnit. Clients can specify an {@link IQueryable} that should be used
+ * to obtain the translation fragment IU's, as well as the locale that
+ * should be used for translations.
+ *
+ * @since 2.0
+ */
+public class TranslationSupport {
+ // TODO: these constants should come from API, eg. IInstallableUnit or ???
+ static final Locale DEFAULT_LOCALE = new Locale("df", "LT"); //$NON-NLS-1$//$NON-NLS-2$
+ private static TranslationSupport instance;
+
+ 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);
+
+ private LocaleProvider localeProvider;
+
+ public synchronized static TranslationSupport getInstance() {
+ if (instance == null)
+ instance = new TranslationSupport();
+ return instance;
+ }
+
+ /**
+ * Create an instance of TranslationSupport for the current locale.
+ * Unless otherwise specified, the currently running profile will serve
+ * as the source of the translation fragments.
+ *
+ * @since 2.0
+ */
+ public TranslationSupport() {
+ super();
+ }
+
+ /**
+ */
+ private List<String> buildLocaleVariants(String locale) {
+ ArrayList<String> result = new ArrayList<String>(4);
+ int lastSeparator;
+ while (true) {
+ result.add(locale);
+ lastSeparator = locale.lastIndexOf('_');
+ if (lastSeparator == -1)
+ break;
+ locale = locale.substring(0, lastSeparator);
+ }
+ // Add the default locale (most general)
+ result.add(DEFAULT_LOCALE.toString());
+ return result;
+ }
+
+ /**
+ * Cache the translated property value to optimize future retrieval of the same value.
+ * Currently we just cache on the installable unit object in memory. In future
+ * we should push support for localized property retrieval into IInstallableUnit
+ * so we aren't required to reach around the API here.
+ */
+ private String cacheResult(IInstallableUnit iu, String localizedKey, String localizedValue) {
+ if (iu instanceof InstallableUnit)
+ ((InstallableUnit) iu).setLocalizedProperty(localizedKey, localizedValue);
+ return localizedValue;
+ }
+
+ /**
+ * Return the copyright for the specified IInstallableUnit,
+ * localized for the receiver's locale.
+ *
+ * @param iu the IInstallableUnit in question
+ * @return the localized copyright defined by the IInstallableUnit
+ */
+ public ICopyright getCopyright(IInstallableUnit iu, String locale) {
+ if (locale == null)
+ locale = getCurrentLocale();
+ ICopyright copyright = iu.getCopyright();
+ String body = (copyright != null ? copyright.getBody() : null);
+ if (body == null || body.length() <= 1 || body.charAt(0) != '%')
+ return copyright;
+ final String actualKey = body.substring(1); // Strip off the %
+ body = getLocalizedIUProperty(iu, actualKey, locale);
+ return MetadataFactory.createCopyright(copyright.getLocation(), body);
+ }
+
+ private String getCurrentLocale() {
+ if (localeProvider != null)
+ return localeProvider.getLocale().toString();
+ return Locale.getDefault().toString();
+ }
+
+ /**
+ * Return the localized value for the specified IInstallableUnit
+ * property.
+ *
+ * @param iu the IInstallableUnit in question
+ * @param propertyKey the name of the property to be retrieved
+ * @param locale The locale to return the property for
+ * @return the localized property value, or <code>null</code> if no
+ * such property is defined.
+ */
+ public String getIUProperty(IInstallableUnit iu, String propertyKey, String locale) {
+ if (locale == null)
+ locale = getCurrentLocale();
+ String value = iu.getProperty(propertyKey);
+ if (value == null || value.length() <= 1 || value.charAt(0) != '%')
+ return value;
+ // else have a localizable property
+ final String actualKey = value.substring(1); // Strip off the %
+ return getLocalizedIUProperty(iu, actualKey, locale);
+ }
+
+ /**
+ * Return the localized value for the specified IInstallableUnit
+ * property using the default locale.
+ *
+ * @param iu the IInstallableUnit in question
+ * @param propertyKey the name of the property to be retrieved
+ * @return the localized property value, or <code>null</code> if no
+ * such property is defined.
+ */
+ public String getIUProperty(IInstallableUnit iu, String propertyKey) {
+ return getIUProperty(iu, propertyKey, null);
+ }
+
+ private ILicense getLicense(IInstallableUnit iu, ILicense license, String locale) {
+ String body = (license != null ? license.getBody() : null);
+ if (body == null || body.length() <= 1 || body.charAt(0) != '%')
+ return license;
+ final String actualKey = body.substring(1); // Strip off the %
+ body = getLocalizedIUProperty(iu, actualKey, locale);
+ return MetadataFactory.createLicense(license.getLocation(), body);
+ }
+
+ /**
+ * Return an array of licenses for the specified IInstallableUnit,
+ * localized for the receiver's locale.
+ *
+ * @param iu the IInstallableUnit in question
+ * @return the localized licenses defined by the IInstallableUnit
+ */
+ public ILicense[] getLicenses(IInstallableUnit iu, String locale) {
+ if (locale == null)
+ locale = getCurrentLocale();
+ Collection<ILicense> licenses = iu.getLicenses();
+ ILicense[] translatedLicenses = new ILicense[licenses.size()];
+ int i = 0;
+ for (ILicense iLicense : licenses) {
+ translatedLicenses[i++] = getLicense(iu, iLicense, locale);
+ }
+ return translatedLicenses;
+ }
+
+ /**
+ * Collects the installable unit fragments that contain locale data for the given locales.
+ */
+ private synchronized IQueryResult<IInstallableUnit> getLocalizationFragments(List<String> localeVariants, String locale) {
+ if (fragmentSource == null) {
+ LogHelper.log(new Status(IStatus.ERROR, MetadataActivator.PI_METADATA, "Profile registry unavailable. Default language will be used.", new RuntimeException())); //$NON-NLS-1$
+ return Collector.emptyCollector();
+ }
+
+ SoftReference<IQueryResult<IInstallableUnit>> queryResultReference = localeCollectorCache.get(locale);
+ if (queryResultReference != null) {
+ Collector<IInstallableUnit> cached = (Collector<IInstallableUnit>) queryResultReference.get();
+ if (cached != null)
+ return cached;
+ }
+
+ final List<String> locales = localeVariants;
+
+ @SuppressWarnings("unchecked")
+ IQuery<IInstallableUnit>[] localeQuery = new IQuery[locales.size()];
+ for (int j = 0; j < locales.size(); j++) {
+ 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));
+ IQueryResult<IInstallableUnit> collected = fragmentSource.query(iuQuery, null);
+ localeCollectorCache.put(locale, new SoftReference<IQueryResult<IInstallableUnit>>(collected));
+ return collected;
+ }
+
+ private String getLocalizedIUProperty(IInstallableUnit iu, String actualKey, String locale) {
+ String localizedKey = makeLocalizedKey(actualKey, locale);
+ String localizedValue = null;
+
+ //first check for a cached localized value
+ if (iu instanceof InstallableUnit)
+ localizedValue = ((InstallableUnit) iu).getLocalizedProperty(localizedKey);
+ //next check if the localized value is stored in the same IU (common case)
+ if (localizedValue == null)
+ localizedValue = iu.getProperty(localizedKey);
+ if (localizedValue != null)
+ return localizedValue;
+
+ final List<String> locales = buildLocaleVariants(locale);
+ final IInstallableUnit theUnit = iu;
+
+ IQueryResult<IInstallableUnit> localizationFragments = getLocalizationFragments(locales, locale);
+
+ IQuery<IInstallableUnit> hostLocalizationQuery = new MatchQuery<IInstallableUnit>() {
+ public boolean isMatch(IInstallableUnit object) {
+ boolean haveHost = false;
+ if (object instanceof IInstallableUnitFragment) {
+ IInstallableUnitFragment fragment = (IInstallableUnitFragment) object;
+ IRequirement[] hosts = fragment.getHost();
+ for (int i = 0; i < hosts.length; i++) {
+ if (theUnit.satisfies(hosts[i])) {
+ haveHost = true;
+ break;
+ }
+ }
+ }
+ return haveHost;
+ }
+ };
+
+ IQuery<IInstallableUnit> iuQuery = new PipedQuery<IInstallableUnit>(new FragmentQuery(), hostLocalizationQuery);
+ IQueryResult<IInstallableUnit> collected = iuQuery.perform(localizationFragments.iterator());
+ if (!collected.isEmpty()) {
+ String translation = null;
+ for (Iterator<IInstallableUnit> iter = collected.iterator(); iter.hasNext() && translation == null;) {
+ IInstallableUnit localizationIU = iter.next();
+ for (Iterator<String> jter = locales.iterator(); jter.hasNext();) {
+ String localeKey = makeLocalizedKey(actualKey, jter.next());
+ translation = localizationIU.getProperty(localeKey);
+ if (translation != null)
+ return cacheResult(iu, localizedKey, translation);
+ }
+ }
+ }
+
+ for (String nextLocale : locales) {
+ String localeKey = makeLocalizedKey(actualKey, nextLocale);
+ String nextValue = iu.getProperty(localeKey);
+ if (nextValue != null)
+ return cacheResult(iu, localizedKey, nextValue);
+ }
+
+ return cacheResult(iu, localizedKey, actualKey);
+ }
+
+ private String makeLocalizedKey(String actualKey, String localeImage) {
+ return localeImage + '.' + actualKey;
+ }
+
+ /**
+ * Set the locale that should be used when obtaining translations.
+ * @param provider the locale for which translations should be retrieved.
+ */
+ public void setLocaleProvider(LocaleProvider provider) {
+ this.localeProvider = provider;
+ }
+
+ /**
+ * Set the {@link IQueryable} that should be used to obtain translation fragment
+ * IUs. Returns the previous translation source.
+ *
+ * @param queryable an {@link IQueryable} that can supply the appropriate NLS
+ * translation fragments
+ */
+ public IQueryable<IInstallableUnit> setTranslationSource(IQueryable<IInstallableUnit> queryable) {
+ IQueryable<IInstallableUnit> previous = fragmentSource;
+ this.fragmentSource = queryable;
+ return previous;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/UpdateDescriptor.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/UpdateDescriptor.java
index d93a4500d..232f3abc6 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/UpdateDescriptor.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/UpdateDescriptor.java
@@ -8,7 +8,10 @@
******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
-import org.eclipse.equinox.internal.provisional.p2.metadata.*;
+import org.eclipse.equinox.p2.metadata.VersionRange;
+
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+import org.eclipse.equinox.p2.metadata.IUpdateDescriptor;
public class UpdateDescriptor implements IUpdateDescriptor {
private String description;
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionFormat.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionFormat.java
new file mode 100644
index 000000000..3582ad18b
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionFormat.java
@@ -0,0 +1,345 @@
+/*******************************************************************************
+ * 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;
+
+import java.io.Serializable;
+import java.util.*;
+import org.eclipse.equinox.internal.p2.metadata.VersionFormatParser.Fragment;
+import org.eclipse.equinox.p2.metadata.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * <p>The VersionFormat represents the Omni Version Format in compiled form. It
+ * is also a parser for versions of that format.</p>
+ * <p>An instance of VersionFormat is immutable and thus thread safe. The parser
+ * does not maintain any state.</p>
+ *
+ * @Immutable
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class VersionFormat implements IVersionFormat, Serializable {
+
+ /**
+ * The string representation of the Omni Version format used for parsing OSGi versions.
+ */
+ public static final String OSGI_FORMAT_STRING = "n[.n=0;[.n=0;[.S='';=[A-Za-z0-9_-];]]]"; //$NON-NLS-1$
+
+ /**
+ * The string representation of the Omni Version format used for parsing raw versions.
+ */
+ public static final String RAW_FORMAT_STRING = "r(.r)*p?"; //$NON-NLS-1$
+
+ private static final long serialVersionUID = -5689435955091405520L;
+
+ private static class StateInfo {
+ Fragment fragment;
+ int position;
+ int segmentCount;
+
+ StateInfo(int position, int segmentCount, Fragment fragment) {
+ this.fragment = fragment;
+ this.position = position;
+ this.segmentCount = segmentCount;
+ }
+ }
+
+ static class TreeInfo extends ArrayList<StateInfo> {
+ private static final long serialVersionUID = 4474591345244587260L;
+
+ private Comparable<?> padValue;
+ private int top;
+
+ TreeInfo(Fragment frag, int pos) {
+ add(new StateInfo(pos, 0, frag));
+ top = 0;
+ }
+
+ Comparable<?> getPadValue() {
+ return padValue;
+ }
+
+ int getPosition() {
+ return get(top).position;
+ }
+
+ void popState(List<Comparable<?>> segments, Fragment frag) {
+ int idx = top;
+ while (idx > 0) {
+ StateInfo si = get(idx);
+ if (si.fragment == frag) {
+ int nsegs = segments.size();
+ int segMax = si.segmentCount;
+ while (nsegs > segMax)
+ segments.remove(--nsegs);
+ top = idx - 1;
+ break;
+ }
+ }
+ }
+
+ void pushState(int segCount, Fragment fragment) {
+ int pos = get(top).position;
+ if (++top == size())
+ add(new StateInfo(pos, segCount, fragment));
+ else {
+ StateInfo si = get(top);
+ si.fragment = fragment;
+ si.position = pos;
+ si.segmentCount = segCount;
+ }
+ }
+
+ void setPadValue(Comparable<?> pad) {
+ padValue = pad;
+ }
+
+ void setPosition(int pos) {
+ get(top).position = pos;
+ }
+ }
+
+ private static final Map<String, VersionFormat> formatCache = Collections.synchronizedMap(new HashMap<String, VersionFormat>());
+
+ /**
+ * The predefined OSGi format that is used when parsing OSGi
+ * versions.
+ */
+ public static final VersionFormat OSGI_FORMAT;
+
+ /**
+ * The predefined OSGi format that is used when parsing raw
+ * versions.
+ */
+ public static final VersionFormat RAW_FORMAT;
+
+ static {
+ try {
+ VersionFormatParser parser = new VersionFormatParser();
+ OSGI_FORMAT = new VersionFormat(parser.compile(OSGI_FORMAT_STRING, 0, OSGI_FORMAT_STRING.length()));
+ formatCache.put(OSGI_FORMAT_STRING, OSGI_FORMAT);
+ RAW_FORMAT = new RawFormat(parser.compile(RAW_FORMAT_STRING, 0, RAW_FORMAT_STRING.length()));
+ formatCache.put(RAW_FORMAT_STRING, RAW_FORMAT);
+ } catch (VersionFormatException e) {
+ // If this happens, something is wrong with the actual
+ // implementation of the FormatCompiler.
+ //
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ /**
+ * Compile a version format string into a compiled format. This method is
+ * shorthand for:<pre>CompiledFormat.compile(format, 0, format.length())</pre>.
+ *
+ * @param format The format to compile.
+ * @return The compiled format
+ * @throws VersionFormatException If the format could not be compiled
+ */
+ public static IVersionFormat compile(String format) throws VersionFormatException {
+ return compile(format, 0, format.length());
+ }
+
+ /**
+ * Compile a version format string into a compiled format. The parsing starts
+ * at position start and ends at position end. The returned format is cached so
+ * subsequent calls to this method using the same format string will yield the
+ * same compiled format instance.
+ *
+ * @param format The format string to compile.
+ * @param start Start position in the format string
+ * @param end End position in the format string
+ * @return The compiled format
+ * @throws VersionFormatException If the format could not be compiled
+ */
+ public static VersionFormat compile(String format, int start, int end) throws VersionFormatException {
+ String fmtString = format.substring(start, end).intern();
+ synchronized (fmtString) {
+ VersionFormat fmt = formatCache.get(fmtString);
+ if (fmt == null) {
+ VersionFormatParser parser = new VersionFormatParser();
+ fmt = new VersionFormat(parser.compile(format, start, end));
+ formatCache.put(fmtString, fmt);
+ }
+ return fmt;
+ }
+ }
+
+ /**
+ * Parse a version string using the {@link #RAW_FORMAT} parser.
+ *
+ * @param version The version to parse.
+ * @param originalFormat The original format to assign to the created version. Can be <code>null</code>.
+ * @param original The original version string to assign to the created version. Can be <code>null</code>.
+ * @return A created version
+ * @throws IllegalArgumentException If the version string could not be parsed.
+ */
+ public static BasicVersion parseRaw(String version, IVersionFormat originalFormat, String original) {
+ Comparable<?>[] padReturn = new Comparable<?>[1];
+ Comparable<?>[] vector = RAW_FORMAT.parse(version, 0, version.length(), padReturn);
+ Comparable<?> pad = padReturn[0];
+ return (originalFormat == OSGI_FORMAT) ? OSGiVersion.fromVector(vector, pad) : OmniVersion.fromVector(vector, pad, originalFormat, original);
+ }
+
+ static void rawToString(StringBuffer sb, boolean forRange, Comparable<?> e) {
+ if (e instanceof String) {
+ writeQuotedString(sb, forRange, (String) e, '\'', 0, false);
+ } else if (e instanceof VersionVector) {
+ sb.append('<');
+ ((VersionVector) e).toString(sb, forRange);
+ sb.append('>');
+ } else
+ sb.append(e);
+ }
+
+ /**
+ * Write a string within quotes. If the string is found to contain the quote, an attempt is made
+ * to flip quote character (single quote becomes double quote and vice versa). A string that contains
+ * both will be written as several adjacent quoted strings so that each string is quoted with a
+ * quote character that it does not contain.
+ * @param sb The buffer that will receive the string
+ * @param rangeSafe Set to <code>true</code> if the resulting string will be used in a range string
+ * and hence need to escape the range delimiter characters
+ * @param s The string to be written
+ * @param quote The quote character to start with. Must be the single or double quote character.
+ * @param startPos The start position
+ * @param didFlip True if the call is recursive and thus, cannot switch quotes in the first string.
+ */
+ private static void writeQuotedString(StringBuffer sb, boolean rangeSafe, String s, char quote, int startPos, boolean didFlip) {
+ int quotePos = sb.length();
+ sb.append(quote);
+ boolean otherSeen = false;
+ int top = s.length();
+ for (int idx = startPos; idx < top; ++idx) {
+ char c = s.charAt(idx);
+ if (c == '\'' || c == '"') {
+ if (c == quote) {
+ char otherQuote = quote == '\'' ? '"' : '\'';
+ if (didFlip || otherSeen) {
+ // We can only flip once
+ sb.append(quote);
+ writeQuotedString(sb, rangeSafe, s, otherQuote, idx, true);
+ return;
+ }
+ quote = otherQuote;
+ sb.setCharAt(quotePos, quote);
+ didFlip = true;
+ } else
+ otherSeen = true;
+ }
+ if (rangeSafe && (c == '\\' || c == '[' || c == '(' || c == ']' || c == ')' || c == ',' || c <= ' '))
+ sb.append('\\');
+ sb.append(c);
+ }
+ sb.append(quote);
+ }
+
+ private String fmtString;
+
+ private final Fragment topFragment;
+
+ VersionFormat(Fragment topFragment) {
+ this.topFragment = topFragment;
+ }
+
+ TreeInfo createInfo(int start) {
+ return new TreeInfo(topFragment, start);
+ }
+
+ public boolean equals(Object o) {
+ return this == o || o instanceof VersionFormat && toString().equals(o.toString());
+ }
+
+ public int hashCode() {
+ return 11 * toString().hashCode();
+ }
+
+ public Version parse(String version) {
+ Comparable<?>[] padReturn = new Comparable<?>[1];
+ Comparable<?>[] vector = parse(version, 0, version.length(), padReturn);
+ Comparable<?> pad = padReturn[0];
+ return (this == OSGI_FORMAT) ? OSGiVersion.fromVector(vector, pad) : OmniVersion.fromVector(vector, pad, this, version);
+ }
+
+ Comparable<?>[] parse(String version, int start, int maxPos, Comparable<?>[] padReturn) {
+ if (start == maxPos)
+ throw new IllegalArgumentException(NLS.bind(Messages.format_0_unable_to_parse_empty_version, this, version.substring(start, maxPos)));
+ TreeInfo info = new TreeInfo(topFragment, start);
+ ArrayList<Comparable<?>> entries = new ArrayList<Comparable<?>>();
+ if (!(topFragment.parse(entries, version, maxPos, info) && info.getPosition() == maxPos))
+ throw new IllegalArgumentException(NLS.bind(Messages.format_0_unable_to_parse_1, this, version.substring(start, maxPos)));
+ padReturn[0] = VersionParser.removeRedundantTrail(entries, info.getPadValue());
+ return entries.toArray(new Comparable[entries.size()]);
+ }
+
+ // Preserve cache during deserialization
+ private Object readResolve() {
+ synchronized (formatCache) {
+ String string = toString();
+ VersionFormat fmt = formatCache.put(string, this);
+ if (fmt == null)
+ fmt = this;
+ else
+ // Put old format back
+ formatCache.put(string, fmt);
+ return fmt;
+ }
+ }
+
+ /**
+ * Returns the string representation of this compiled format
+ */
+ public synchronized String toString() {
+ if (fmtString == null) {
+ StringBuffer sb = new StringBuffer();
+ toString(sb);
+ }
+ return fmtString;
+ }
+
+ public synchronized void toString(StringBuffer sb) {
+ if (fmtString != null)
+ sb.append(fmtString);
+ else {
+ int start = sb.length();
+ sb.append("format"); //$NON-NLS-1$
+ if (topFragment.getPadValue() != null) {
+ sb.append('(');
+ topFragment.toString(sb);
+ sb.append(')');
+ } else
+ topFragment.toString(sb);
+ fmtString = sb.substring(start);
+ }
+ }
+}
+
+class RawFormat extends VersionFormat {
+ private static final long serialVersionUID = -6070590518921019745L;
+
+ RawFormat(Fragment topFragment) {
+ super(topFragment);
+ }
+
+ /**
+ * Parse but do not assign this format as the Version format nor the version
+ * string as the original.
+ */
+ public Version parse(String version, int start, int maxPos) {
+ Comparable<?>[] padReturn = new Comparable<?>[1];
+ Comparable<?>[] vector = parse(version, start, maxPos, padReturn);
+ return OmniVersion.fromVector(vector, padReturn[0], null, null);
+ }
+
+ // Preserve singleton when deserialized
+ private Object readResolve() {
+ return RAW_FORMAT;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionFormatParser.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionFormatParser.java
new file mode 100644
index 000000000..83170c9a7
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionFormatParser.java
@@ -0,0 +1,1548 @@
+/*******************************************************************************
+ * 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;
+
+import org.eclipse.equinox.p2.metadata.VersionFormatException;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.equinox.internal.p2.metadata.VersionFormat.TreeInfo;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * This is the Omni Version Format parser. It will parse a version format in string form
+ * into a group of {@link VersionFormatParser.Fragment} elements. That group, wrapped in a
+ * {@link VersionFormat}, becomes the parser for versions corresponding to the format.
+ *
+ * The class is not intended to included in a public API. Instead VersionFormats should
+ * be created using {@link VersionFormat#parse(String)}
+ *
+ */
+class VersionFormatParser {
+
+ static class Instructions {
+ char[] characters = null;
+ Comparable<?> defaultValue = null;
+ char oppositeTranslationChar = 0;
+ int oppositeTranslationRepeat = 0;
+ boolean ignore = false;
+ boolean inverted = false;
+ Comparable<?> padValue = null;
+ int rangeMax = Integer.MAX_VALUE;
+ int rangeMin = 0;
+ }
+
+ static final Qualifier EXACT_ONE_QUALIFIER = new Qualifier(1, 1);
+
+ static final Qualifier ONE_OR_MANY_QUALIFIER = new Qualifier(1, Integer.MAX_VALUE);
+
+ static final Qualifier ZERO_OR_MANY_QUALIFIER = new Qualifier(0, Integer.MAX_VALUE);
+
+ static final Qualifier ZERO_OR_ONE_QUALIFIER = new Qualifier(0, 1);
+
+ /**
+ * Represents one fragment of a format (i.e. auto, number, string, delimiter, etc.)
+ */
+ static abstract class Fragment implements Serializable {
+ private static final long serialVersionUID = 4109185333058622681L;
+
+ private final Qualifier qualifier;
+
+ Fragment(Qualifier qualifier) {
+ this.qualifier = qualifier;
+ }
+
+ public final boolean equals(Object f) {
+ return f == this || getClass().equals(f.getClass()) && qualifier.equals(((Fragment) f).qualifier);
+ }
+
+ public final int hashCode() {
+ return 11 * qualifier.hashCode();
+ }
+
+ public boolean isGroup() {
+ return false;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ toString(sb);
+ return sb.toString();
+ }
+
+ Comparable<?> getDefaultValue() {
+ return null;
+ }
+
+ Fragment getFirstLeaf() {
+ return this;
+ }
+
+ Comparable<?> getPadValue() {
+ return null;
+ }
+
+ Qualifier getQualifier() {
+ return qualifier;
+ }
+
+ boolean parse(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) {
+ return qualifier.parse(new Fragment[] {this}, 0, segments, version, maxPos, info);
+ }
+
+ abstract boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info);
+
+ void setDefaults(List<Comparable<?>> segments) {
+ // No-op at this level
+ }
+
+ void toString(StringBuffer sb) {
+ if (!(qualifier == VersionFormatParser.EXACT_ONE_QUALIFIER || (qualifier == VersionFormatParser.ZERO_OR_ONE_QUALIFIER && this.isGroup())))
+ qualifier.toString(sb);
+ }
+ }
+
+ /**
+ * Specifies the min and max occurrences of a fragment
+ */
+ static class Qualifier implements Serializable {
+ private static final long serialVersionUID = 7494021832824671685L;
+
+ private final int max;
+ private final int min;
+
+ Qualifier(int min, int max) {
+ this.min = min;
+ this.max = max;
+ }
+
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+ if (!(o instanceof Qualifier))
+ return false;
+ Qualifier oq = (Qualifier) o;
+ return min == oq.min && max == oq.max;
+ }
+
+ public int hashCode() {
+ return 31 * min + 67 * max;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ toString(sb);
+ return sb.toString();
+ }
+
+ int getMax() {
+ return max;
+ }
+
+ int getMin() {
+ return min;
+ }
+
+ boolean parse(Fragment[] fragments, int fragIdx, List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) {
+ Fragment fragment = fragments[fragIdx++];
+ int idx = 0;
+
+ // Do the required parsing. I.e. iterate this fragment
+ // min number of times.
+ //
+ for (; idx < min; ++idx)
+ if (!fragment.parseOne(segments, version, maxPos, info))
+ return false;
+
+ for (; idx < max; ++idx) {
+ // We are greedy. Continue parsing until we get an exception
+ // and remember the state before each parse is performed.
+ //
+ info.pushState(segments.size(), fragment);
+ if (!fragment.parseOne(segments, version, maxPos, info)) {
+ info.popState(segments, fragment);
+ break;
+ }
+ }
+ int maxParsed = idx;
+
+ for (;;) {
+ // Pad with default values unless the max is unbounded
+ //
+ if (idx < max) {
+ if (max != Integer.MAX_VALUE) {
+ for (; idx < max; ++idx)
+ fragment.setDefaults(segments);
+ }
+ } else {
+ if (fragment instanceof StringFragment) {
+ // Check for translations if we default to for MINS or MAXS
+ StringFragment stringFrag = (StringFragment) fragment;
+ Comparable<?> opposite = stringFrag.getOppositeDefaultValue();
+ if (opposite != null) {
+ idx = segments.size() - 1;
+ if (stringFrag.isOppositeTranslation(segments.get(idx)))
+ segments.set(idx, opposite);
+ }
+ }
+ }
+
+ if (fragIdx == fragments.length)
+ // We are the last segment
+ //
+ return true;
+
+ // Try to parse the next segment. If it fails, pop the state of
+ // this segment (or a child thereof) and try again
+ //
+ if (fragments[fragIdx].getQualifier().parse(fragments, fragIdx, segments, version, maxPos, info))
+ return true;
+
+ // Be less greedy, step back one position and try again.
+ //
+ if (maxParsed <= min)
+ // We have no more states to pop. Tell previous that we failed.
+ //
+ return false;
+
+ info.popState(segments, fragment);
+ idx = --maxParsed; // segments now have room for one more default value
+ }
+ }
+
+ void toString(StringBuffer sb) {
+ if (min == 0) {
+ if (max == 1)
+ sb.append('?');
+ else if (max == Integer.MAX_VALUE)
+ sb.append('*');
+ else {
+ sb.append('{');
+ sb.append(min);
+ sb.append(',');
+ sb.append(max);
+ sb.append('}');
+ }
+ } else if (max == Integer.MAX_VALUE) {
+ if (min == 1)
+ sb.append('+');
+ else {
+ sb.append('{');
+ sb.append(min);
+ sb.append(",}"); //$NON-NLS-1$
+ }
+ } else {
+ sb.append('{');
+ sb.append(min);
+ if (min != max) {
+ sb.append(',');
+ sb.append(max);
+ }
+ sb.append('}');
+ }
+ }
+
+ // Preserve singleton when deserialized
+ private Object readResolve() {
+ Qualifier q = this;
+ if (min == 0) {
+ if (max == 1)
+ q = VersionFormatParser.ZERO_OR_ONE_QUALIFIER;
+ else if (max == Integer.MAX_VALUE)
+ q = VersionFormatParser.ZERO_OR_MANY_QUALIFIER;
+ } else if (min == 1) {
+ if (max == 1)
+ q = VersionFormatParser.EXACT_ONE_QUALIFIER;
+ else if (max == Integer.MAX_VALUE)
+ q = VersionFormatParser.ONE_OR_MANY_QUALIFIER;
+ }
+ return q;
+ }
+ }
+
+ private static class AutoFragment extends RangeFragment {
+ private static final long serialVersionUID = -1016534328164247755L;
+
+ AutoFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
+ super(instr, qualifier);
+ }
+
+ boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) {
+ int pos = info.getPosition();
+ maxPos = checkRange(pos, maxPos);
+ if (maxPos < 0)
+ return false;
+
+ char c = version.charAt(pos);
+ if (VersionParser.isDigit(c) && isAllowed(c)) {
+ // Parse to next non-digit
+ //
+ int start = pos;
+ int value = c - '0';
+ while (++pos < maxPos) {
+ c = version.charAt(pos);
+ if (!(VersionParser.isDigit(c) && isAllowed(c)))
+ break;
+ value *= 10;
+ value += (c - '0');
+ }
+ int len = pos - start;
+ if (rangeMin > len || len > rangeMax)
+ return false;
+
+ if (!isIgnored())
+ segments.add(VersionParser.valueOf(value));
+ info.setPosition(pos);
+ return true;
+ }
+
+ if (!(VersionParser.isLetter(c) && isAllowed(c)))
+ return false;
+
+ // Parse to next non-letter or next delimiter
+ //
+ int start = pos++;
+ for (; pos < maxPos; ++pos) {
+ c = version.charAt(pos);
+ if (!(VersionParser.isLetter(c) && isAllowed(c)))
+ break;
+ }
+ int len = pos - start;
+ if (rangeMin > len || len > rangeMax)
+ return false;
+
+ if (!isIgnored())
+ segments.add(version.substring(start, pos));
+ info.setPosition(pos);
+ return true;
+ }
+
+ void toString(StringBuffer sb) {
+ sb.append('a');
+ super.toString(sb);
+ }
+ }
+
+ private static class DelimiterFragment extends Fragment {
+ private static final long serialVersionUID = 8173654376143370605L;
+ private final char[] delimChars;
+ private final boolean inverted;
+
+ DelimiterFragment(VersionFormatParser.Instructions ep, Qualifier qualifier) {
+ super(qualifier);
+ if (ep == null) {
+ delimChars = null;
+ inverted = false;
+ } else {
+ inverted = ep.inverted;
+ delimChars = ep.characters;
+ }
+ }
+
+ boolean isMatch(String version, int pos) {
+ char c = version.charAt(pos);
+ if (delimChars != null) {
+ for (int idx = 0; idx < delimChars.length; ++idx)
+ if (c == delimChars[idx])
+ return !inverted;
+ return inverted;
+ } else if (VersionParser.isLetterOrDigit(c))
+ return false;
+
+ return true;
+ }
+
+ boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) {
+ int pos = info.getPosition();
+ if (pos < maxPos && isMatch(version, pos)) {
+ // Just swallow, a delimiter does not contribute to the vector.
+ //
+ info.setPosition(pos + 1);
+ return true;
+ }
+ return false;
+ }
+
+ void toString(StringBuffer sb) {
+ sb.append('d');
+ if (delimChars != null)
+ appendCharacterRange(sb, delimChars, inverted);
+ super.toString(sb);
+ }
+ }
+
+ static void appendCharacterRange(StringBuffer sb, char[] range, boolean inverted) {
+ sb.append('=');
+ sb.append('[');
+ if (inverted)
+ sb.append('^');
+ int top = range.length;
+ for (int idx = 0; idx < top; ++idx) {
+ char b = range[idx];
+ if (b == '\\' || b == ']' || (b == '-' && idx + 1 < top))
+ sb.append('\\');
+
+ sb.append(b);
+ int ndx = idx + 1;
+ if (ndx + 2 < top) {
+ char c = b;
+ for (; ndx < top; ++ndx) {
+ char n = range[ndx];
+ if (c + 1 != n)
+ break;
+ c = n;
+ }
+ if (ndx <= idx + 3)
+ continue;
+
+ sb.append('-');
+ if (c == '\\' || c == ']' || (c == '-' && idx + 1 < top))
+ sb.append('\\');
+ sb.append(c);
+ idx = ndx - 1;
+ }
+ }
+ sb.append(']');
+ sb.append(';');
+ }
+
+ static Fragment createAutoFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
+ return new AutoFragment(instr, qualifier);
+ }
+
+ static Fragment createDelimiterFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
+ return new DelimiterFragment(instr, qualifier);
+ }
+
+ static Fragment createGroupFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, Fragment[] fragments, boolean array) {
+ return new GroupFragment(instr, qualifier, fragments, array);
+ }
+
+ static Fragment createLiteralFragment(Qualifier qualifier, String literal) {
+ return new LiteralFragment(qualifier, literal);
+ }
+
+ static Fragment createNumberFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean signed) {
+ return new NumberFragment(instr, qualifier, signed);
+ }
+
+ static Fragment createPadFragment(Qualifier qualifier) {
+ return new PadFragment(qualifier);
+ }
+
+ static Fragment createQuotedFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
+ return new QuotedFragment(instr, qualifier);
+ }
+
+ static Fragment createRawFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
+ return new RawFragment(instr, qualifier);
+ }
+
+ static Fragment createStringFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean unbound) {
+ return new StringFragment(instr, qualifier, unbound);
+ }
+
+ static boolean equalsAllowNull(Object a, Object b) {
+ return (a == null) ? (b == null) : (b != null && a.equals(b));
+ }
+
+ private static abstract class ElementFragment extends Fragment {
+ private static final long serialVersionUID = -6834591415456539713L;
+ private final Comparable<?> defaultValue;
+ private final boolean ignored;
+ private final Comparable<?> padValue;
+
+ ElementFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
+ super(qualifier);
+ if (instr != null) {
+ ignored = instr.ignore;
+ defaultValue = instr.defaultValue;
+ padValue = instr.padValue;
+ } else {
+ ignored = false;
+ defaultValue = null;
+ padValue = null;
+ }
+ }
+
+ Comparable<?> getDefaultValue() {
+ return defaultValue;
+ }
+
+ Comparable<?> getPadValue() {
+ return padValue;
+ }
+
+ boolean isIgnored() {
+ return ignored;
+ }
+
+ void setDefaults(List<Comparable<?>> segments) {
+ Comparable<?> defaultVal = getDefaultValue();
+ if (defaultVal != null)
+ segments.add(defaultVal);
+ }
+
+ void toString(StringBuffer sb) {
+ if (ignored) {
+ sb.append('=');
+ sb.append('!');
+ sb.append(';');
+ }
+ if (defaultValue != null) {
+ sb.append('=');
+ VersionFormat.rawToString(sb, false, defaultValue);
+ sb.append(';');
+ }
+ if (padValue != null) {
+ sb.append('=');
+ sb.append('p');
+ VersionFormat.rawToString(sb, false, padValue);
+ sb.append(';');
+ }
+ super.toString(sb);
+ }
+ }
+
+ private static class GroupFragment extends ElementFragment {
+ private static final long serialVersionUID = 9219978678087669699L;
+ private final boolean array;
+ private final Fragment[] fragments;
+
+ GroupFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, Fragment[] fragments, boolean array) {
+ super(instr, qualifier);
+ this.fragments = fragments;
+ this.array = array;
+ }
+
+ public boolean isGroup() {
+ return !array;
+ }
+
+ Fragment getFirstLeaf() {
+ return fragments[0].getFirstLeaf();
+ }
+
+ boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) {
+ if (array) {
+ ArrayList<Comparable<?>> subSegs = new ArrayList<Comparable<?>>();
+ boolean success = fragments[0].getQualifier().parse(fragments, 0, subSegs, version, maxPos, info);
+ if (!success || subSegs.isEmpty())
+ return false;
+
+ Comparable<?> padValue = info.getPadValue();
+ if (padValue != null)
+ info.setPadValue(null); // Prevent outer group from getting this.
+ else
+ padValue = getPadValue();
+
+ padValue = VersionParser.removeRedundantTrail(segments, padValue);
+ segments.add(new VersionVector(subSegs.toArray(new Comparable[subSegs.size()]), padValue));
+ return true;
+ }
+
+ if (fragments[0].getQualifier().parse(fragments, 0, segments, version, maxPos, info)) {
+ Comparable<?> padValue = getPadValue();
+ if (padValue != null)
+ info.setPadValue(padValue);
+ return true;
+ }
+ return false;
+ }
+
+ void setDefaults(List<Comparable<?>> segments) {
+ Comparable<?> dflt = getDefaultValue();
+ if (dflt != null) {
+ // A group default overrides any defaults within the
+ // group fragments
+ super.setDefaults(segments);
+ } else {
+ // Assign defaults for all fragments
+ for (int idx = 0; idx < fragments.length; ++idx)
+ fragments[idx].setDefaults(segments);
+ }
+ }
+
+ void toString(StringBuffer sb) {
+ if (array) {
+ sb.append('<');
+ for (int idx = 0; idx < fragments.length; ++idx)
+ fragments[idx].toString(sb);
+ sb.append('>');
+ } else {
+ if (getQualifier() == VersionFormatParser.ZERO_OR_ONE_QUALIFIER) {
+ sb.append('[');
+ for (int idx = 0; idx < fragments.length; ++idx)
+ fragments[idx].toString(sb);
+ sb.append(']');
+ } else {
+ sb.append('(');
+ for (int idx = 0; idx < fragments.length; ++idx)
+ fragments[idx].toString(sb);
+ sb.append(')');
+ }
+ }
+ super.toString(sb);
+ }
+ }
+
+ private static class LiteralFragment extends Fragment {
+ private static final long serialVersionUID = 6210696245839471802L;
+ private final String string;
+
+ LiteralFragment(Qualifier qualifier, String string) {
+ super(qualifier);
+ this.string = string;
+ }
+
+ boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) {
+ int pos = info.getPosition();
+ int litLen = string.length();
+ if (pos + litLen > maxPos)
+ return false;
+
+ for (int idx = 0; idx < litLen; ++idx, ++pos) {
+ if (string.charAt(idx) != version.charAt(pos))
+ return false;
+ }
+ info.setPosition(pos);
+ return true;
+ }
+
+ void toString(StringBuffer sb) {
+ String str = string;
+ if (str.length() != 1) {
+ sb.append('\'');
+ VersionFormatParser.toStringEscaped(sb, str, "\'"); //$NON-NLS-1$
+ sb.append('\'');
+ } else {
+ char c = str.charAt(0);
+ switch (c) {
+ case '\'' :
+ case '\\' :
+ case '<' :
+ case '[' :
+ case '(' :
+ case '{' :
+ case '?' :
+ case '*' :
+ case '+' :
+ case '=' :
+ sb.append('\\');
+ sb.append(c);
+ break;
+ default :
+ if (VersionParser.isLetterOrDigit(c)) {
+ sb.append('\\');
+ sb.append(c);
+ } else
+ sb.append(c);
+ }
+ }
+ super.toString(sb);
+ }
+ }
+
+ private static class NumberFragment extends RangeFragment {
+ private static final long serialVersionUID = -8552754381106711507L;
+ private final boolean signed;
+
+ NumberFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean signed) {
+ super(instr, qualifier);
+ this.signed = signed;
+ }
+
+ boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) {
+ int pos = info.getPosition();
+ maxPos = checkRange(pos, maxPos);
+ if (maxPos < 0)
+ return false;
+
+ // Parse to next non-digit
+ //
+ int start = pos;
+ int value;
+
+ char c = version.charAt(pos);
+ if (signed || characters != null) {
+ boolean negate = false;
+ if (signed && c == '-' && pos + 1 < maxPos) {
+ negate = true;
+ c = version.charAt(++pos);
+ }
+
+ if (!(c >= '0' && c <= '9' && isAllowed(c)))
+ return false;
+
+ // Parse to next non-digit
+ //
+ value = c - '0';
+ while (++pos < maxPos) {
+ c = version.charAt(pos);
+ if (!(c >= '0' && c <= '9' && isAllowed(c)))
+ break;
+ value *= 10;
+ value += (c - '0');
+ }
+ if (negate)
+ value = -value;
+ } else {
+ if (c < '0' || c > '9')
+ return false;
+
+ // Parse to next non-digit
+ //
+ value = c - '0';
+ while (++pos < maxPos) {
+ c = version.charAt(pos);
+ if (c < '0' || c > '9')
+ break;
+ value *= 10;
+ value += (c - '0');
+ }
+ }
+
+ int len = pos - start;
+ if (rangeMin > len || len > rangeMax)
+ return false;
+
+ if (!isIgnored())
+ segments.add(VersionParser.valueOf(value));
+ info.setPosition(pos);
+ return true;
+ }
+
+ void toString(StringBuffer sb) {
+ sb.append(signed ? 'N' : 'n');
+ super.toString(sb);
+ }
+ }
+
+ private static class PadFragment extends ElementFragment {
+ private static final long serialVersionUID = 5052010199974380170L;
+
+ PadFragment(Qualifier qualifier) {
+ super(null, qualifier);
+ }
+
+ boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) {
+ int pos = info.getPosition();
+ if (pos >= maxPos || version.charAt(pos) != 'p')
+ return false;
+
+ int[] position = new int[] {++pos};
+ Comparable<?> v = VersionParser.parseRawElement(version, position, maxPos);
+ if (v == null)
+ return false;
+
+ if (!isIgnored())
+ info.setPadValue(v);
+ info.setPosition(position[0]);
+ return true;
+ }
+
+ void toString(StringBuffer sb) {
+ sb.append('p');
+ super.toString(sb);
+ }
+ }
+
+ private static class QuotedFragment extends RangeFragment {
+ private static final long serialVersionUID = 6057751133533608969L;
+
+ QuotedFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
+ super(instr, qualifier);
+ }
+
+ boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) {
+ int pos = info.getPosition();
+ if (pos >= maxPos)
+ return false;
+
+ char endQuote;
+ char quote = version.charAt(pos);
+ switch (quote) {
+ case '<' :
+ endQuote = '>';
+ break;
+ case '{' :
+ endQuote = '}';
+ break;
+ case '(' :
+ endQuote = ')';
+ break;
+ case '[' :
+ endQuote = ']';
+ break;
+ case '>' :
+ endQuote = '<';
+ break;
+ case '}' :
+ endQuote = '{';
+ break;
+ case ')' :
+ endQuote = '(';
+ break;
+ case ']' :
+ endQuote = '[';
+ break;
+ default :
+ if (VersionParser.isLetterOrDigit(quote))
+ return false;
+ endQuote = quote;
+ }
+ int start = ++pos;
+ char c = version.charAt(pos);
+ while (c != endQuote && isAllowed(c) && ++pos < maxPos)
+ c = version.charAt(pos);
+
+ if (c != endQuote || rangeMin > pos - start)
+ // End quote not found
+ return false;
+
+ int len = pos - start;
+ if (rangeMin > len || len > rangeMax)
+ return false;
+
+ if (!isIgnored())
+ segments.add(version.substring(start, pos));
+ info.setPosition(++pos); // Skip quote
+ return true;
+ }
+
+ void toString(StringBuffer sb) {
+ sb.append('q');
+ super.toString(sb);
+ }
+ }
+
+ private static abstract class RangeFragment extends ElementFragment {
+ private static final long serialVersionUID = -6680402803630334708L;
+ final char[] characters;
+ final boolean inverted;
+ final int rangeMax;
+ final int rangeMin;
+
+ RangeFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) {
+ super(instr, qualifier);
+ if (instr == null) {
+ characters = null;
+ inverted = false;
+ rangeMin = 0;
+ rangeMax = Integer.MAX_VALUE;
+ } else {
+ characters = instr.characters;
+ inverted = instr.inverted;
+ rangeMin = instr.rangeMin;
+ rangeMax = instr.rangeMax;
+ }
+ }
+
+ /**
+ * Checks that pos is at a valid character position, that we
+ * have at least the required minimum characters left, and
+ * if a maximum number of characters is set, limits the
+ * returned value to a maxPos that reflects that maximum.
+ * @param pos the current position
+ * @param maxPos the current maxPos
+ * @return maxPos, possibly limited by rangeMax
+ */
+ int checkRange(int pos, int maxPos) {
+ int check = pos;
+ if (rangeMin == 0)
+ check++; // Verify one character
+ else
+ check += rangeMin;
+
+ if (check > maxPos)
+ // Less then min characters left
+ maxPos = -1;
+ else {
+ if (rangeMax != Integer.MAX_VALUE) {
+ check = pos + rangeMax;
+ if (check < maxPos)
+ maxPos = check;
+ }
+ }
+ return maxPos;
+ }
+
+ boolean isAllowed(char c) {
+ char[] crs = characters;
+ if (crs != null) {
+ int idx = crs.length;
+ while (--idx >= 0)
+ if (c == crs[idx])
+ return !inverted;
+ return inverted;
+ }
+ return true;
+ }
+
+ void toString(StringBuffer sb) {
+ if (characters != null)
+ appendCharacterRange(sb, characters, inverted);
+ if (rangeMin != 0 || rangeMax != Integer.MAX_VALUE) {
+ sb.append('=');
+ sb.append('{');
+ sb.append(rangeMin);
+ if (rangeMin != rangeMax) {
+ sb.append(',');
+ if (rangeMax != Integer.MAX_VALUE)
+ sb.append(rangeMax);
+ }
+ sb.append('}');
+ sb.append(';');
+ }
+ super.toString(sb);
+ }
+ }
+
+ private static class RawFragment extends ElementFragment {
+ private static final long serialVersionUID = 4107448125256042602L;
+
+ RawFragment(VersionFormatParser.Instructions processing, Qualifier qualifier) {
+ super(processing, qualifier);
+ }
+
+ boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) {
+ int[] position = new int[] {info.getPosition()};
+ Comparable<?> v = VersionParser.parseRawElement(version, position, maxPos);
+ if (v == null)
+ return false;
+
+ if (!isIgnored())
+ segments.add(v);
+ info.setPosition(position[0]);
+ return true;
+ }
+
+ void toString(StringBuffer sb) {
+ sb.append('r');
+ super.toString(sb);
+ }
+ }
+
+ private static class StringFragment extends RangeFragment {
+ private static final long serialVersionUID = -2265924553606430164L;
+ final boolean anyChar;
+ private final char oppositeTranslationChar;
+ private final int oppositeTranslationRepeat;
+
+ StringFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean noLimit) {
+ super(instr, qualifier);
+ anyChar = noLimit;
+ char otc = 0;
+ int otr = 0;
+ if (instr != null) {
+ otc = instr.oppositeTranslationChar;
+ otr = instr.oppositeTranslationRepeat;
+ if (instr.defaultValue == VersionVector.MINS_VALUE) {
+ if (otc == 0)
+ otc = 'z';
+ if (otr == 0)
+ otr = 3;
+ } else if (instr.defaultValue == VersionVector.MAXS_VALUE) {
+ if (otc == 0)
+ otc = '-';
+ otr = 1;
+ }
+ }
+ oppositeTranslationChar = otc;
+ oppositeTranslationRepeat = otr;
+ }
+
+ Comparable<?> getOppositeDefaultValue() {
+ Comparable<?> dflt = getDefaultValue();
+ return dflt == VersionVector.MAXS_VALUE ? VersionVector.MINS_VALUE : (dflt == VersionVector.MINS_VALUE ? VersionVector.MAXS_VALUE : null);
+ }
+
+ public boolean isOppositeTranslation(Object val) {
+ if (val instanceof String) {
+ String str = (String) val;
+ int idx = oppositeTranslationRepeat;
+ if (str.length() == idx) {
+ while (--idx >= 0)
+ if (str.charAt(idx) != oppositeTranslationChar)
+ break;
+ return idx < 0;
+ }
+ }
+ return false;
+ }
+
+ boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) {
+ int pos = info.getPosition();
+ maxPos = checkRange(pos, maxPos);
+ if (maxPos < 0)
+ return false;
+
+ // Parse to next delimiter or end of string
+ //
+ int start = pos;
+ if (characters != null) {
+ if (anyChar) {
+ // Swallow everything that matches the allowed characters
+ for (; pos < maxPos; ++pos) {
+ if (!isAllowed(version.charAt(pos)))
+ break;
+ }
+ } else {
+ // Swallow letters that matches the allowed characters
+ for (; pos < maxPos; ++pos) {
+ char c = version.charAt(pos);
+ if (!(VersionParser.isLetter(c) && isAllowed(c)))
+ break;
+ }
+ }
+ } else {
+ if (anyChar)
+ // Swallow all characters
+ pos = maxPos;
+ else {
+ // Swallow all letters
+ for (; pos < maxPos; ++pos) {
+ if (!VersionParser.isLetter(version.charAt(pos)))
+ break;
+ }
+ }
+ }
+ int len = pos - start;
+ if (len == 0 || rangeMin > len || len > rangeMax)
+ return false;
+
+ if (!isIgnored())
+ segments.add(version.substring(start, pos));
+ info.setPosition(pos);
+ return true;
+ }
+
+ void toString(StringBuffer sb) {
+ sb.append(anyChar ? 'S' : 's');
+ super.toString(sb);
+ }
+ }
+
+ private int current;
+
+ private List<Fragment> currentList;
+
+ private int eos;
+
+ private String format;
+
+ private int start;
+
+ Fragment compile(String fmt, int pos, int maxPos) throws VersionFormatException {
+ format = fmt;
+ if (start >= maxPos)
+ throw new VersionFormatException(Messages.format_is_empty);
+
+ start = pos;
+ current = pos;
+ eos = maxPos;
+ currentList = new ArrayList<Fragment>();
+ while (current < eos)
+ parseFragment();
+
+ Fragment topFrag;
+ switch (currentList.size()) {
+ case 0 :
+ throw new VersionFormatException(Messages.format_is_empty);
+ case 1 :
+ Fragment frag = currentList.get(0);
+ if (frag.isGroup()) {
+ topFrag = frag;
+ break;
+ }
+ // Fall through to default
+ default :
+ topFrag = createGroupFragment(null, EXACT_ONE_QUALIFIER, currentList.toArray(new Fragment[currentList.size()]), false);
+ }
+ currentList = null;
+ return topFrag;
+ }
+
+ private void assertChar(char expected) throws VersionFormatException {
+ if (current >= eos)
+ throw formatException(NLS.bind(Messages.premature_end_of_format_expected_0, new String(new char[] {expected})));
+
+ char c = format.charAt(current);
+ if (c != expected)
+ throw formatException(c, new String(new char[] {expected}));
+ ++current;
+ }
+
+ private VersionFormatException formatException(char found, String expected) {
+ return formatException(new String(new char[] {found}), expected);
+ }
+
+ private VersionFormatException formatException(String message) {
+ return new VersionFormatException(NLS.bind(Messages.syntax_error_in_version_format_0_1_2, new Object[] {format.substring(start, eos), new Integer(current), message}));
+ }
+
+ private VersionFormatException formatException(String found, String expected) {
+ return new VersionFormatException(NLS.bind(Messages.syntax_error_in_version_format_0_1_found_2_expected_3, new Object[] {format.substring(start, eos), new Integer(current), found, expected}));
+ }
+
+ private VersionFormatException illegalControlCharacter(char c) {
+ return formatException(NLS.bind(Messages.illegal_character_encountered_ascii_0, VersionParser.valueOf(c)));
+ }
+
+ private String parseAndConsiderEscapeUntil(char endChar) throws VersionFormatException {
+ StringBuffer sb = new StringBuffer();
+ while (current < eos) {
+ char c = format.charAt(current++);
+ if (c == endChar)
+ break;
+
+ if (c < 32)
+ throw illegalControlCharacter(c);
+
+ if (c == '\\') {
+ if (current == eos)
+ throw formatException(Messages.EOS_after_escape);
+ c = format.charAt(current++);
+ if (c < 32)
+ throw illegalControlCharacter(c);
+ }
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+ private void parseAuto() throws VersionFormatException {
+ VersionFormatParser.Instructions ep = parseProcessing();
+ if (ep != null) {
+ if (ep.padValue != null)
+ throw formatException(Messages.auto_can_not_have_pad_value);
+ }
+ currentList.add(createAutoFragment(ep, parseQualifier()));
+ }
+
+ private void parseBracketGroup() throws VersionFormatException {
+ List<Fragment> saveList = currentList;
+ currentList = new ArrayList<Fragment>();
+ while (current < eos && format.charAt(current) != ']')
+ parseFragment();
+
+ if (current == eos)
+ throw formatException(NLS.bind(Messages.premature_end_of_format_expected_0, "]")); //$NON-NLS-1$
+
+ ++current;
+ VersionFormatParser.Instructions ep = parseProcessing();
+ saveList.add(createGroupFragment(ep, ZERO_OR_ONE_QUALIFIER, currentList.toArray(new Fragment[currentList.size()]), false));
+ currentList = saveList;
+ }
+
+ private void parseCharacterGroup(VersionFormatParser.Instructions ep) throws VersionFormatException {
+ assertChar('[');
+
+ StringBuffer sb = new StringBuffer();
+ outer: for (; current < eos; ++current) {
+ char c = format.charAt(current);
+ switch (c) {
+ case '\\' :
+ if (current + 1 < eos) {
+ sb.append(format.charAt(++current));
+ continue;
+ }
+ throw formatException(Messages.premature_end_of_format);
+ case '^' :
+ if (sb.length() == 0)
+ ep.inverted = true;
+ else
+ sb.append(c);
+ continue;
+ case ']' :
+ break outer;
+ case '-' :
+ if (sb.length() > 0 && current + 1 < eos) {
+ char rangeEnd = format.charAt(++current);
+ if (rangeEnd == ']') {
+ // Use dash verbatim when last in range
+ sb.append(c);
+ break outer;
+ }
+
+ char rangeStart = sb.charAt(sb.length() - 1);
+ if (rangeEnd < rangeStart)
+ throw formatException(Messages.negative_character_range);
+ while (++rangeStart <= rangeEnd)
+ sb.append(rangeStart);
+ continue;
+ }
+ // Fall through to default
+ default :
+ if (c < 32)
+ throw illegalControlCharacter(c);
+ sb.append(c);
+ }
+ }
+ assertChar(']');
+ int top = sb.length();
+ char[] chars = new char[top];
+ sb.getChars(0, top, chars, 0);
+ ep.characters = chars;
+ }
+
+ private void parseDelimiter() throws VersionFormatException {
+ VersionFormatParser.Instructions ep = parseProcessing();
+ if (ep != null) {
+ if (ep.rangeMin != 0 || ep.rangeMax != Integer.MAX_VALUE)
+ throw formatException(Messages.delimiter_can_not_have_range);
+ if (ep.ignore)
+ throw formatException(Messages.delimiter_can_not_be_ignored);
+ if (ep.defaultValue != null)
+ throw formatException(Messages.delimiter_can_not_have_default_value);
+ if (ep.padValue != null)
+ throw formatException(Messages.delimiter_can_not_have_pad_value);
+ }
+ currentList.add(createDelimiterFragment(ep, parseQualifier()));
+ }
+
+ private void parseFragment() throws VersionFormatException {
+ if (current == eos)
+ throw formatException(Messages.premature_end_of_format);
+ char c = format.charAt(current++);
+ switch (c) {
+ case '(' :
+ parseGroup(false);
+ break;
+ case '<' :
+ parseGroup(true);
+ break;
+ case '[' :
+ parseBracketGroup();
+ break;
+ case 'a' :
+ parseAuto();
+ break;
+ case 'r' :
+ parseRaw();
+ break;
+ case 'n' :
+ parseNumber(false);
+ break;
+ case 'N' :
+ parseNumber(true);
+ break;
+ case 's' :
+ parseString(false);
+ break;
+ case 'S' :
+ parseString(true);
+ break;
+ case 'd' :
+ parseDelimiter();
+ break;
+ case 'q' :
+ parseQuotedString();
+ break;
+ case 'p' :
+ parsePad();
+ break;
+ default :
+ parseLiteral(c);
+ }
+ }
+
+ private void parseGroup(boolean array) throws VersionFormatException {
+ List<Fragment> saveList = currentList;
+ currentList = new ArrayList<Fragment>();
+ char expectedEnd = array ? '>' : ')';
+ while (current < eos && format.charAt(current) != expectedEnd)
+ parseFragment();
+ assertChar(expectedEnd);
+
+ VersionFormatParser.Instructions ep = parseProcessing();
+ if (ep != null) {
+ if (ep.characters != null)
+ throw formatException(Messages.array_can_not_have_character_group);
+ if (ep.rangeMax != Integer.MAX_VALUE && ep.padValue != null) {
+ throw formatException(Messages.cannot_combine_range_upper_bound_with_pad_value);
+ }
+ }
+
+ if (currentList.isEmpty())
+ throw formatException(array ? Messages.array_can_not_be_empty : Messages.group_can_not_be_empty);
+ saveList.add(createGroupFragment(ep, parseQualifier(), currentList.toArray(new Fragment[currentList.size()]), array));
+ currentList = saveList;
+ }
+
+ private int parseIntegerLiteral() throws VersionFormatException {
+ if (current == eos)
+ throw formatException(NLS.bind(Messages.premature_end_of_format_expected_0, "<integer>")); //$NON-NLS-1$
+
+ char c = format.charAt(current);
+ if (!VersionParser.isDigit(c))
+ throw formatException(c, "<integer>"); //$NON-NLS-1$
+
+ int value = c - '0';
+ while (++current < eos) {
+ c = format.charAt(current);
+ if (!VersionParser.isDigit(c))
+ break;
+ value *= 10;
+ value += (c - '0');
+ }
+ return value;
+ }
+
+ private void parseLiteral(char c) throws VersionFormatException {
+ String value;
+ switch (c) {
+ case '\'' :
+ value = parseAndConsiderEscapeUntil(c);
+ break;
+ case ')' :
+ case ']' :
+ case '{' :
+ case '}' :
+ case '?' :
+ case '*' :
+ throw formatException(c, "<literal>"); //$NON-NLS-1$
+ default :
+ if (VersionParser.isLetterOrDigit(c))
+ throw formatException(c, "<literal>"); //$NON-NLS-1$
+
+ if (c < 32)
+ throw illegalControlCharacter(c);
+
+ if (c == '\\') {
+ if (current == eos)
+ throw formatException(Messages.EOS_after_escape);
+ c = format.charAt(current++);
+ if (c < 32)
+ throw illegalControlCharacter(c);
+ }
+ value = new String(new char[] {c});
+ }
+ currentList.add(createLiteralFragment(parseQualifier(), value));
+ }
+
+ private int[] parseMinMax() throws VersionFormatException {
+
+ int max = Integer.MAX_VALUE;
+ ++current;
+ int min = parseIntegerLiteral();
+ char c = format.charAt(current);
+ if (c == '}') {
+ max = min;
+ if (max == 0)
+ throw formatException(Messages.range_max_cannot_be_zero);
+ ++current;
+ } else if (c == ',' && current + 1 < eos) {
+ if (format.charAt(++current) != '}') {
+ max = parseIntegerLiteral();
+ if (max == 0)
+ throw formatException(Messages.range_max_cannot_be_zero);
+ if (max < min)
+ throw formatException(Messages.range_max_cannot_be_less_then_range_min);
+ }
+ assertChar('}');
+ } else
+ throw formatException(c, "},"); //$NON-NLS-1$
+ return new int[] {min, max};
+ }
+
+ private void parseNumber(boolean signed) throws VersionFormatException {
+ VersionFormatParser.Instructions ep = parseProcessing();
+ if (ep != null) {
+ if (ep.padValue != null)
+ throw formatException(Messages.number_can_not_have_pad_value);
+ }
+ currentList.add(createNumberFragment(ep, parseQualifier(), signed));
+ }
+
+ private void parsePad() throws VersionFormatException {
+ currentList.add(createPadFragment(parseQualifier()));
+ }
+
+ private VersionFormatParser.Instructions parseProcessing() throws VersionFormatException {
+ if (current >= eos)
+ return null;
+
+ char c = format.charAt(current);
+ if (c != '=')
+ return null;
+
+ VersionFormatParser.Instructions ep = new VersionFormatParser.Instructions();
+ do {
+ current++;
+ parseProcessingInstruction(ep);
+ } while (current < eos && format.charAt(current) == '=');
+ return ep;
+ }
+
+ private void parseProcessingInstruction(VersionFormatParser.Instructions processing) throws VersionFormatException {
+ if (current == eos)
+ throw formatException(Messages.premature_end_of_format);
+
+ char c = format.charAt(current);
+ if (c == 'p') {
+ // =pad(<raw-element>);
+ //
+ if (processing.padValue != null)
+ throw formatException(Messages.pad_defined_more_then_once);
+ if (processing.ignore)
+ throw formatException(Messages.cannot_combine_ignore_with_other_instruction);
+ ++current;
+ processing.padValue = parseRawElement();
+ } else if (c == '!') {
+ // =ignore;
+ //
+ if (processing.ignore)
+ throw formatException(Messages.ignore_defined_more_then_once);
+ if (processing.padValue != null || processing.characters != null || processing.rangeMin != 0 || processing.rangeMax != Integer.MAX_VALUE || processing.defaultValue != null)
+ throw formatException(Messages.cannot_combine_ignore_with_other_instruction);
+ ++current;
+ processing.ignore = true;
+ } else if (c == '[') {
+ // =[<character group];
+ //
+ if (processing.characters != null)
+ throw formatException(Messages.character_group_defined_more_then_once);
+ if (processing.ignore)
+ throw formatException(Messages.cannot_combine_ignore_with_other_instruction);
+ parseCharacterGroup(processing);
+ } else if (c == '{') {
+ // ={min,max};
+ //
+ if (processing.rangeMin != 0 || processing.rangeMax != Integer.MAX_VALUE)
+ throw formatException(Messages.range_defined_more_then_once);
+ if (processing.ignore)
+ throw formatException(Messages.cannot_combine_ignore_with_other_instruction);
+ int[] minMax = parseMinMax();
+ processing.rangeMin = minMax[0];
+ processing.rangeMax = minMax[1];
+ } else {
+ // =<raw-element>;
+ if (processing.defaultValue != null)
+ throw formatException(Messages.default_defined_more_then_once);
+ if (processing.ignore)
+ throw formatException(Messages.cannot_combine_ignore_with_other_instruction);
+ Comparable<?> dflt = parseRawElement();
+ processing.defaultValue = dflt;
+ if (current < eos && format.charAt(current) == '{') {
+ // =m{<translated min char>}
+ // =''{<translated max char>,<max char repeat>}
+ if (++current == eos)
+ throw formatException(Messages.premature_end_of_format);
+ processing.oppositeTranslationChar = format.charAt(current++);
+ if (current == eos)
+ throw formatException(Messages.premature_end_of_format);
+
+ if (dflt == VersionVector.MINS_VALUE) {
+ processing.oppositeTranslationRepeat = 3;
+ if (format.charAt(current) == ',') {
+ ++current;
+ processing.oppositeTranslationRepeat = parseIntegerLiteral();
+ }
+ } else if (dflt != VersionVector.MAXS_VALUE) {
+ current -= 2;
+ throw formatException(Messages.only_max_and_empty_string_defaults_can_have_translations);
+ }
+ assertChar('}');
+ }
+ }
+ assertChar(';');
+ }
+
+ private Qualifier parseQualifier() throws VersionFormatException {
+ if (current >= eos)
+ return EXACT_ONE_QUALIFIER;
+
+ char c = format.charAt(current);
+ if (c == '?') {
+ ++current;
+ return ZERO_OR_ONE_QUALIFIER;
+ }
+
+ if (c == '*') {
+ ++current;
+ return ZERO_OR_MANY_QUALIFIER;
+ }
+
+ if (c == '+') {
+ ++current;
+ return ONE_OR_MANY_QUALIFIER;
+ }
+
+ if (c != '{')
+ return EXACT_ONE_QUALIFIER;
+
+ int[] minMax = parseMinMax();
+ int min = minMax[0];
+ int max = minMax[1];
+
+ // Use singletons for commonly used ranges
+ //
+ if (min == 0) {
+ if (max == 1)
+ return ZERO_OR_ONE_QUALIFIER;
+ if (max == Integer.MAX_VALUE)
+ return ZERO_OR_MANY_QUALIFIER;
+ } else if (min == 1) {
+ if (max == 1)
+ return EXACT_ONE_QUALIFIER;
+ if (max == Integer.MAX_VALUE)
+ return ONE_OR_MANY_QUALIFIER;
+ }
+ return new Qualifier(min, max);
+ }
+
+ private void parseQuotedString() throws VersionFormatException {
+ VersionFormatParser.Instructions ep = parseProcessing();
+ if (ep != null) {
+ if (ep.padValue != null)
+ throw formatException(Messages.string_can_not_have_pad_value);
+ }
+ currentList.add(createQuotedFragment(ep, parseQualifier()));
+ }
+
+ private void parseRaw() throws VersionFormatException {
+ VersionFormatParser.Instructions ep = parseProcessing();
+ if (ep != null) {
+ if (ep.padValue != null)
+ throw formatException(Messages.raw_element_can_not_have_pad_value);
+ }
+ currentList.add(createRawFragment(ep, parseQualifier()));
+ }
+
+ private Comparable<?> parseRawElement() throws VersionFormatException {
+ int[] position = new int[] {current};
+ Comparable<?> v = VersionParser.parseRawElement(format, position, eos);
+ if (v == null)
+ throw new VersionFormatException(NLS.bind(Messages.raw_element_expected_0, format));
+ current = position[0];
+ return v;
+ }
+
+ private void parseString(boolean unlimited) throws VersionFormatException {
+ VersionFormatParser.Instructions ep = parseProcessing();
+ if (ep != null) {
+ if (ep.padValue != null)
+ throw formatException(Messages.string_can_not_have_pad_value);
+ }
+ currentList.add(createStringFragment(ep, parseQualifier(), unlimited));
+ }
+
+ static void toStringEscaped(StringBuffer sb, String value, String escapes) {
+ for (int idx = 0; idx < value.length(); ++idx) {
+ char c = value.charAt(idx);
+ if (c == '\\' || escapes.indexOf(c) >= 0)
+ sb.append('\\');
+ sb.append(c);
+ }
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionParser.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionParser.java
new file mode 100644
index 000000000..0ef47d9bc
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionParser.java
@@ -0,0 +1,388 @@
+/*******************************************************************************
+ * 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;
+
+import org.eclipse.equinox.p2.metadata.Version;
+import org.eclipse.equinox.p2.metadata.VersionFormatException;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * The Omni Version parser. Not intended for public API. Instead use
+ * {@link Version#create(String)} or {@link Version#parseVersion(String)}.
+ *
+ * The class also contains some general purpose parser support methods
+ *
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public abstract class VersionParser {
+ public static final Integer ZERO_INT = new Integer(0);
+
+ public static final Integer MAX_INT_OBJ = new Integer(Integer.MAX_VALUE);
+
+ private static final Integer cache[] = new Integer[100];
+
+ static {
+ cache[0] = ZERO_INT;
+ for (int i = 1; i < cache.length; i++)
+ cache[i] = new Integer(i);
+ }
+
+ public static Integer valueOf(int i) {
+ try {
+ return cache[i];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return (i == Integer.MAX_VALUE) ? MAX_INT_OBJ : new Integer(i);
+ }
+ }
+
+ static Comparable<?> removeRedundantTrail(List<Comparable<?>> segments, Comparable<?> padValue) {
+ Comparable<?> redundantTrail;
+ if (padValue == null)
+ redundantTrail = VersionVector.MIN_VALUE;
+ else {
+ redundantTrail = padValue;
+ if (padValue == VersionVector.MIN_VALUE)
+ padValue = null;
+ }
+
+ int idx = segments.size();
+ while (--idx >= 0 && segments.get(idx).equals(redundantTrail))
+ segments.remove(idx);
+
+ return padValue;
+ }
+
+ private VersionParser() {
+ // Prevent class from being instantiated
+ }
+
+ /**
+ * Parse the <code>version</code> string and assing the parsed portions to the <code>receiver</code>.
+ * This method is called from the version string constructor.
+ *
+ * @param version The string to be parsed
+ * @param start Start position in the <code>version</code> string
+ * @param maxPos End position in the <code>version</code> string
+ * @returns a version if one indeed was parsed or <code>null</code> if the string
+ * contained only whitespace.
+ * @throws IllegalArgumentException if the version is malformed
+ */
+ public static Version parse(String version, int start, int maxPos) throws IllegalArgumentException {
+ // trim leading and trailing whitespace
+ int pos = skipWhite(version, start);
+ maxPos = skipTrailingWhite(version, start, maxPos);
+ if (pos == maxPos)
+ return null;
+
+ Comparable<?>[] padReturn = new Comparable[1];
+ Comparable<?>[] vector = null;
+ Comparable<?> pad = null;
+ VersionFormat fmt = null;
+ char c = version.charAt(pos);
+ if (isDigit(c)) {
+ vector = VersionFormat.OSGI_FORMAT.parse(version, pos, maxPos, padReturn);
+ return OSGiVersion.fromVector(vector, padReturn[0]);
+ }
+
+ if (!isLetter(c))
+ throw new IllegalArgumentException();
+
+ if (version.startsWith(Version.RAW_PREFIX, pos)) {
+ VersionFormat rawFmt = VersionFormat.RAW_FORMAT;
+ pos += 4;
+
+ // Find ending '/' that is neither quoted or escaped
+ int end = maxPos;
+ for (int idx = pos; idx < maxPos; ++idx) {
+ c = version.charAt(idx);
+ switch (c) {
+ case '/' :
+ end = idx;
+ break;
+ case '\\' :
+ ++idx;
+ continue;
+ case '\'' :
+ case '"' :
+ for (++idx; idx < maxPos; ++idx) {
+ char e = version.charAt(idx);
+ if (e == c) {
+ break;
+ }
+ if (e == '\\')
+ ++idx;
+ }
+ // fall through to default
+ default :
+ continue;
+ }
+ break;
+ }
+
+ vector = rawFmt.parse(version, pos, end, padReturn);
+ pad = padReturn[0];
+ pos = end;
+ if (pos == maxPos)
+ // This was a pure raw version
+ //
+ return OmniVersion.fromVector(vector, pad, null, null);
+
+ if (version.charAt(pos) != '/')
+ throw new IllegalArgumentException(NLS.bind(Messages.expected_slash_after_raw_vector_0, version.substring(start, maxPos)));
+ ++pos;
+
+ if (pos == maxPos)
+ throw new IllegalArgumentException(NLS.bind(Messages.expected_orignal_after_slash_0, version.substring(start, maxPos)));
+ }
+
+ if (version.startsWith("format(", pos)) { //$NON-NLS-1$
+ // Parse the format
+ //
+ pos += 7;
+ try {
+ // Find matching ')' that is neither quoted or escaped
+ //
+ int end = findEndOfFormat(version, pos, maxPos);
+ fmt = VersionFormat.compile(version, pos, end);
+ pos = end + 1;
+ } catch (VersionFormatException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ if (pos == maxPos) {
+ // This was a raw version with format but no original
+ //
+ if (vector == null)
+ throw new IllegalArgumentException(NLS.bind(Messages.only_format_specified_0, version.substring(start, maxPos)));
+ return fmt == VersionFormat.OSGI_FORMAT ? OSGiVersion.fromVector(vector, pad) : OmniVersion.fromVector(vector, pad, fmt, null);
+ }
+ }
+
+ if (fmt == null && vector == null)
+ throw new IllegalArgumentException(NLS.bind(Messages.neither_raw_vector_nor_format_specified_0, version.substring(start, maxPos)));
+
+ if (version.charAt(pos) != ':')
+ throw new IllegalArgumentException(NLS.bind(Messages.colon_expected_before_original_version_0, version.substring(start, maxPos)));
+
+ pos++;
+ if (pos == maxPos)
+ throw new IllegalArgumentException(NLS.bind(Messages.expected_orignal_after_colon_0, version.substring(start, maxPos)));
+
+ if (vector == null) {
+ // Vector and pad must be created by parsing the original
+ //
+ vector = fmt.parse(version, pos, maxPos, padReturn);
+ pad = padReturn[0];
+ }
+ return fmt == VersionFormat.OSGI_FORMAT ? OSGiVersion.fromVector(vector, pad) : OmniVersion.fromVector(vector, pad, fmt, version.substring(pos));
+ }
+
+ static boolean isDigit(char c) {
+ return c >= '0' && c <= '9';
+ }
+
+ public static boolean isLetter(char c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+ }
+
+ static boolean isLetterOrDigit(char c) {
+ return isDigit(c) || isLetter(c);
+ }
+
+ public static int findEndOfFormat(String string, int pos, int maxPos) {
+ int end = -1;
+ int depth = 1;
+ for (int idx = pos; idx < maxPos; ++idx) {
+ char c = string.charAt(idx);
+ switch (c) {
+ case ')' :
+ if (--depth == 0) {
+ end = idx;
+ break;
+ }
+ continue;
+ case '(' :
+ ++depth;
+ continue;
+ case '\\' :
+ ++idx;
+ continue;
+ case '\'' :
+ case '"' :
+ for (++idx; idx < maxPos; ++idx) {
+ char e = string.charAt(idx);
+ if (e == c) {
+ break;
+ }
+ if (e == '\\')
+ ++idx;
+ }
+ // fall through to default
+ default :
+ continue;
+ }
+ break;
+ }
+ if (depth != 0)
+ throw new IllegalArgumentException(NLS.bind(Messages.unbalanced_format_parenthesis, string.substring(pos - 1, maxPos)));
+ return end;
+ }
+
+ static Comparable<?> parseRawElement(String value, int[] position, int maxPos) {
+ int current = position[0];
+ if (current >= maxPos)
+ return null;
+
+ boolean negate = false;
+ char c = value.charAt(current);
+ Comparable<?> v;
+ switch (c) {
+ case '\'' :
+ case '"' : {
+ StringBuffer sb = new StringBuffer();
+ for (;;) {
+ char q = c;
+ if (++current == maxPos)
+ return null;
+ c = value.charAt(current);
+ while (c != q) {
+ if (c < 32)
+ return null;
+ sb.append(c);
+ if (++current == maxPos)
+ return null;
+ c = value.charAt(current);
+ }
+ if (++current == maxPos)
+ break;
+ c = value.charAt(current);
+ if (c != '\'' && c != '"')
+ break;
+ }
+ v = sb.length() == 0 ? VersionVector.MINS_VALUE : sb.toString();
+ break;
+ }
+ case '<' : {
+ if (++current == maxPos)
+ return null;
+
+ position[0] = current;
+ v = parseRawVector(value, position, maxPos);
+ if (v == null)
+ return null;
+ current = position[0];
+ break;
+ }
+ case 'm' :
+ v = VersionVector.MAXS_VALUE;
+ ++current;
+ break;
+ case 'M' :
+ v = VersionVector.MAX_VALUE;
+ ++current;
+ break;
+ case '-' :
+ if (++current >= maxPos)
+ return null;
+
+ c = value.charAt(current);
+ if (c == 'M') {
+ ++current;
+ v = VersionVector.MIN_VALUE;
+ break;
+ }
+ negate = true;
+ // Fall through to default
+ default : {
+ if (isDigit(c)) {
+ int start = current++;
+ while (current < maxPos && isDigit(value.charAt(current)))
+ ++current;
+ int val = Integer.parseInt(value.substring(start, current));
+ if (negate)
+ val = -val;
+ v = valueOf(val);
+ break;
+ }
+ return null;
+ }
+ }
+ position[0] = current;
+ return v;
+ }
+
+ private static Comparable<?> parseRawVector(String value, int[] position, int maxPos) {
+ int pos = position[0];
+ if (pos >= maxPos)
+ return null;
+
+ char c = value.charAt(pos);
+ if (c == '>')
+ return null;
+
+ ArrayList<Comparable<?>> rawList = new ArrayList<Comparable<?>>();
+ boolean padMarkerSeen = (c == 'p');
+ if (padMarkerSeen) {
+ if (++pos >= maxPos)
+ return null;
+ position[0] = pos;
+ }
+
+ Comparable<?> pad = null;
+ for (;;) {
+ Comparable<?> elem = parseRawElement(value, position, maxPos);
+ if (elem == null)
+ return null;
+
+ if (padMarkerSeen)
+ pad = elem;
+ else
+ rawList.add(elem);
+
+ pos = position[0];
+ if (pos >= maxPos)
+ return null;
+
+ c = value.charAt(pos);
+ position[0] = ++pos;
+ if (c == '>')
+ break;
+
+ if (padMarkerSeen || pos >= maxPos)
+ return null;
+
+ if (c == 'p') {
+ padMarkerSeen = true;
+ continue;
+ }
+
+ if (c != '.')
+ return null;
+ }
+ pad = removeRedundantTrail(rawList, pad);
+ return new VersionVector(rawList.toArray(new Comparable[rawList.size()]), pad);
+ }
+
+ public static int skipWhite(String string, int pos) {
+ int top = string.length();
+ while (pos < top && string.charAt(pos) <= ' ')
+ ++pos;
+ return pos;
+ }
+
+ public static int skipTrailingWhite(String string, int start, int end) {
+ while (end > start && string.charAt(end - 1) <= ' ')
+ --end;
+ return end;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionVector.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionVector.java
new file mode 100644
index 000000000..c723c8e6b
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionVector.java
@@ -0,0 +1,325 @@
+/*******************************************************************************
+ * 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;
+
+import java.io.Serializable;
+
+/**
+ * The VersionVector represents an array of Comparable objects. The array can be
+ * nested since a VersionVector is Comparable in itself.
+ *
+ * @Immutable
+ */
+public class VersionVector implements Comparable<VersionVector>, Serializable {
+
+ interface MinMaxComparable extends Comparable<Object>, Serializable {
+ //
+ }
+
+ private static final class MaxStringValue implements MinMaxComparable {
+ private static final long serialVersionUID = -4936252230441132767L;
+
+ MaxStringValue() {
+ // Empty constructor
+ }
+
+ public int compareTo(Object o) {
+ return o == this ? 0 : (o == MAX_VALUE || o instanceof Integer || o instanceof VersionVector ? -1 : 1);
+ }
+
+ // For singleton deserialization
+ private Object readResolve() {
+ return MAXS_VALUE;
+ }
+
+ public String toString() {
+ return "m"; //$NON-NLS-1$
+ }
+ }
+
+ private static final class MaxValue implements MinMaxComparable {
+ private static final long serialVersionUID = -5889641741635253589L;
+
+ MaxValue() {
+ // Empty constructor
+ }
+
+ public int compareTo(Object o) {
+ return o == this ? 0 : 1;
+ }
+
+ // For singleton deserialization
+ private Object readResolve() {
+ return MAX_VALUE;
+ }
+
+ public String toString() {
+ return "M"; //$NON-NLS-1$
+ }
+ }
+
+ private static class MinValue implements MinMaxComparable {
+ private static final long serialVersionUID = -1066323980049812226L;
+
+ MinValue() {
+ // Empty constructor
+ }
+
+ public int compareTo(Object o) {
+ return o == this ? 0 : -1;
+ }
+
+ private Object readResolve() {
+ return MIN_VALUE;
+ }
+
+ public String toString() {
+ return "-M"; //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * A value that is greater then any other value
+ */
+ public static final Comparable<Object> MAX_VALUE = new MaxValue();
+
+ /**
+ * A value that is greater then any string but less then {@link #MAX_VALUE} and
+ * any Integer or VersionVector.
+ */
+ public static final Comparable<Object> MAXS_VALUE = new MaxStringValue();
+
+ /**
+ * A value that is less then any other value
+ */
+ public static final Comparable<Object> MIN_VALUE = new MinValue();
+
+ /**
+ * A value that is greater then {@link #MIN_VALUE} and less then any string,
+ * Integer, or VersionVector (a.k.a. empty_string)
+ */
+ public static final String MINS_VALUE = ""; //$NON-NLS-1$
+
+ private static final long serialVersionUID = -8385373304298723744L;
+
+ static int compare(Comparable<?>[] vectorA, Comparable<?> padA, Comparable<?>[] vectorB, Comparable<?> padB) {
+ int top = vectorA.length;
+ if (top > vectorB.length)
+ top = vectorB.length;
+
+ for (int idx = 0; idx < top; ++idx) {
+ int cmp = compareSegments(vectorA[idx], vectorB[idx]);
+ if (cmp != 0)
+ return cmp;
+ }
+
+ // All elements compared equal up to this point. Check
+ // pad values
+ if (top < vectorA.length)
+ return (padB == null) ? 1 : compareReminder(top, vectorA, padA, padB);
+
+ if (top < vectorB.length)
+ return (padA == null) ? -1 : -compareReminder(top, vectorB, padB, padA);
+
+ // Lengths are equal. Compare pad values
+ return padA == null ? (padB == null ? 0 : -1) : (padB == null ? 1 : compareSegments(padA, padB));
+ }
+
+ static boolean equals(Comparable<?>[] vectorA, Comparable<?> padValueA, Comparable<?>[] vectorB, Comparable<?> padValueB) {
+ // We compare pad first since it is impossible for versions with
+ // different pad to be equal (versions are padded to infinity)
+ if (padValueA == null) {
+ if (padValueB != null)
+ return false;
+ } else {
+ if (padValueB == null || !padValueA.equals(padValueB))
+ return false;
+ }
+
+ int idx = vectorA.length;
+
+ // If the length of the vector differs, the versions cannot be equal
+ // since segments equal to pad are stripped by the parser
+ if (idx != vectorB.length)
+ return false;
+
+ while (--idx >= 0)
+ if (!vectorA[idx].equals(vectorB[idx]))
+ return false;
+
+ return true;
+ }
+
+ static int hashCode(Comparable<?>[] vector, Comparable<?> padValue) {
+ int hashCode = padValue == null ? 31 : padValue.hashCode();
+ int idx = vector.length;
+ while (--idx >= 0) {
+ Object elem = vector[idx];
+ if (elem != null)
+ hashCode += elem.hashCode();
+ hashCode = hashCode * 31;
+ }
+ return hashCode;
+ }
+
+ static void toString(StringBuffer sb, Comparable<?>[] vector, Comparable<?> padValue, boolean rangeSafe) {
+ int top = vector.length;
+ if (top == 0)
+ // Write one pad value as explicit. It will be considered
+ // redundant and removed by the parser but the raw format
+ // does not allow zero elements
+ VersionFormat.rawToString(sb, rangeSafe, padValue == null ? MIN_VALUE : padValue);
+ else {
+ for (int idx = 0; idx < top; ++idx) {
+ if (idx > 0)
+ sb.append('.');
+ VersionFormat.rawToString(sb, rangeSafe, vector[idx]);
+ }
+ }
+ if (padValue != null) {
+ sb.append('p');
+ VersionFormat.rawToString(sb, rangeSafe, padValue);
+ }
+ }
+
+ private static int compareReminder(int idx, Comparable<?>[] vector, Comparable<?> padValue, Comparable<?> othersPad) {
+ int cmp;
+ for (cmp = 0; idx < vector.length && cmp == 0; ++idx)
+ cmp = compareSegments(vector[idx], othersPad);
+ if (cmp == 0)
+ cmp = (padValue == null) ? -1 : compareSegments(padValue, othersPad);
+ return cmp;
+ }
+
+ static int compareSegments(Comparable<?> a, Comparable<?> b) {
+ if (a == b)
+ return 0;
+
+ if (a instanceof Integer && b instanceof Integer) {
+ int ai = ((Integer) a).intValue();
+ int bi = ((Integer) b).intValue();
+ return ai > bi ? 1 : (ai < bi ? -1 : 0);
+ }
+
+ if (a instanceof String && b instanceof String)
+ return ((String) a).compareTo((String) b);
+
+ if (a == MAX_VALUE || a == MIN_VALUE || a == MAXS_VALUE)
+ return ((MinMaxComparable) a).compareTo(b);
+
+ if (b == MAX_VALUE || b == MIN_VALUE || b == MAXS_VALUE)
+ return -((MinMaxComparable) b).compareTo(a);
+
+ if (a instanceof Integer)
+ return 1;
+ if (b instanceof Integer)
+ return -1;
+ if (a instanceof VersionVector)
+ return (b instanceof VersionVector) ? ((VersionVector) a).compareTo((VersionVector) b) : 1;
+
+ if (b instanceof VersionVector)
+ return -1;
+
+ throw new IllegalArgumentException();
+ }
+
+ private final Comparable<?> padValue;
+
+ private final Comparable<?>[] vector;
+
+ public VersionVector(Comparable<?>[] vector, Comparable<?> pad) {
+ this.vector = vector;
+ this.padValue = (pad == MIN_VALUE) ? null : pad;
+ }
+
+ public int compareTo(VersionVector ov) {
+ if (ov == this)
+ return 0;
+
+ return compare(vector, padValue, ov.vector, ov.padValue);
+ }
+
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+
+ if (!(o instanceof VersionVector))
+ return false;
+
+ VersionVector ov = (VersionVector) o;
+ return equals(vector, padValue, ov.vector, ov.padValue);
+ }
+
+ /**
+ * Returns the pad value used when comparing this versions to
+ * versions that has a raw vector with a larger number of elements
+ * @return The pad value or <code>null</code> if not set.
+ */
+ public Comparable<?> getPad() {
+ return padValue;
+ }
+
+ /**
+ * An element from the raw vector
+ * @param index The zero based index of the desired element
+ * @return An element from the raw vector
+ */
+ public Comparable<?> getSegment(int index) {
+ return vector[index];
+ }
+
+ /**
+ * Returns the number of elements in the raw vector
+ * @return The element count
+ */
+ public int getSegmentCount() {
+ return vector.length;
+ }
+
+ /**
+ * This method is package protected since it violates the immutable
+ * contract.
+ * @return The raw vector. Must be treated as read-only
+ */
+ Comparable<?>[] getVector() {
+ return vector;
+ }
+
+ public int hashCode() {
+ return hashCode(vector, padValue);
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ toString(sb);
+ return sb.toString();
+ }
+
+ /**
+ * Append the string representation of this instance to the
+ * <code>sb</code> buffer.
+ * @param sb The buffer to append to
+ */
+ public void toString(StringBuffer sb) {
+ toString(sb, vector, padValue, false);
+ }
+
+ /**
+ * Append the string representation of this instance to the
+ * <code>sb</code> buffer.
+ * @param sb The buffer to append to
+ * @param rangeSafe If <code>true</code>, the range delimiters will be escaped
+ * with backslash.
+ */
+ void toString(StringBuffer sb, boolean rangeSafe) {
+ toString(sb, vector, padValue, rangeSafe);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionedId.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionedId.java
new file mode 100644
index 000000000..1bf5ebd44
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionedId.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 Code 9 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:
+ * Code 9 - initial API and implementation
+ * EclipseSource - ongoing development
+ * Thomas Hallgren - Fix for bug 268659
+ * IBM - ongoing development
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata;
+
+import org.eclipse.equinox.p2.metadata.Version;
+
+import org.eclipse.equinox.internal.p2.core.helpers.StringHelper;
+import org.eclipse.equinox.p2.metadata.IVersionedId;
+
+/**
+ * An object representing a (id,version) pair.
+ *
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class VersionedId implements IVersionedId {
+ private final String id;
+ private final Version version;
+
+ /**
+ * Creates and returns a new {@link VersionedId} from the given string specification.
+ * The specification must be of the form "id/version", or just "id" if the version is absent
+ * <p>
+ * This factory method can be used to reconstruct a {@link VersionedId}
+ * instance from the string representation produced by a previous invocation of
+ * {@link #toString()}.
+ *
+ * @param spec the specification for the versioned id to create
+ * @return the parsed versioned id
+ * @throws IllegalArgumentException If <code>spec</code> is improperly
+ * formatted.
+ */
+ public static IVersionedId parse(String spec) {
+ String[] segments = StringHelper.getArrayFromString(spec, '/');
+ return new VersionedId(segments[0], segments.length == 1 ? null : segments[1]);
+ }
+
+ /**
+ * Creates a new versioned id with the given id and version.
+ *
+ * @param id The identifier
+ * @param version The version
+ * @throws IllegalArgumentException If <code>version</code> is improperly
+ * formatted.
+ */
+ public VersionedId(String id, String version) {
+ this.id = id;
+ this.version = Version.parseVersion(version);
+ }
+
+ /**
+ * Creates a new versioned id with the given id and version.
+ *
+ * @param id The identifier
+ * @param version The version
+ */
+ public VersionedId(String id, Version version) {
+ this.id = id;
+ this.version = (version == null) ? Version.emptyVersion : version;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+
+ if (!(obj instanceof VersionedId))
+ return false;
+
+ VersionedId vname = (VersionedId) obj;
+ return id.equals(vname.id) && version.equals(vname.version);
+ }
+
+ public int hashCode() {
+ return id.hashCode() * 31 + version.hashCode();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public Version getVersion() {
+ return version;
+ }
+
+ /**
+ * Returns a string representation of this versioned id.
+ * The result can be used to later construct an equal {@link VersionedId}
+ * instance using {{@link #parse(String)}.
+ * @return A string representation of this versioned id
+ */
+ public String toString() {
+ return Version.emptyVersion.equals(version) ? id : id + '/' + version.toString();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/All.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/All.java
new file mode 100644
index 000000000..4de975525
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/All.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.Iterator;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * A collection filter that yields true if the <code>filter</code> yields true for
+ * all of the elements of the <code>collection</code>
+ */
+final class All extends CollectionFilter {
+ All(Expression collection, LambdaExpression lambda) {
+ super(collection, lambda);
+ }
+
+ protected Object evaluate(IEvaluationContext context, Iterator<?> itor) {
+ Variable variable = lambda.getItemVariable();
+ while (itor.hasNext()) {
+ variable.setValue(context, itor.next());
+ if (lambda.evaluate(context) != Boolean.TRUE)
+ return Boolean.FALSE;
+ }
+ return Boolean.TRUE;
+ }
+
+ public int getExpressionType() {
+ return TYPE_ALL;
+ }
+
+ public String getOperator() {
+ return KEYWORD_ALL;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/And.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/And.java
new file mode 100644
index 000000000..7e8adf20e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/And.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * n-ary AND operator. The full evaluation is <code>true</code> if all its operands evaluate to
+ * <code>true</code>.
+ */
+final class And extends NAry {
+ And(Expression[] operands) {
+ super(assertLength(operands, 2, OPERATOR_AND));
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ for (int idx = 0; idx < operands.length; ++idx) {
+ if (operands[idx].evaluate(context) != Boolean.TRUE)
+ return Boolean.FALSE;
+ }
+ return Boolean.TRUE;
+ }
+
+ public int getExpressionType() {
+ return TYPE_AND;
+ }
+
+ public String getOperator() {
+ return OPERATOR_AND;
+ }
+
+ public int getPriority() {
+ return PRIORITY_AND;
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ buf.append("(&"); //$NON-NLS-1$
+ for (int idx = 0; idx < operands.length; ++idx)
+ operands[idx].toLDAPString(buf);
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/At.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/At.java
new file mode 100644
index 000000000..e089c1dbc
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/At.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.*;
+import org.eclipse.equinox.internal.p2.metadata.expression.Member.DynamicMember;
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * This class represents indexed or keyed access to an indexed collection
+ * or a map.
+ */
+public class At extends Binary {
+ protected At(Expression lhs, Expression rhs) {
+ super(lhs, rhs);
+ }
+
+ protected Object handleMember(IEvaluationContext context, Member member, Object instance, boolean[] handled) {
+ if (instance instanceof IInstallableUnit) {
+ if ("properties".equals(member.getName())) { //$NON-NLS-1$
+ // Avoid full copy of the properties map just to get one member
+ handled[0] = true;
+ return ((IInstallableUnit) instance).getProperty((String) rhs.evaluate(context));
+ }
+ }
+ return null;
+ }
+
+ public Object evaluate(org.eclipse.equinox.p2.metadata.expression.IEvaluationContext context) {
+ Object lval;
+ if (lhs instanceof DynamicMember) {
+ DynamicMember lm = (DynamicMember) lhs;
+ Object instance = lm.operand.evaluate(context);
+ boolean[] handled = new boolean[] {false};
+ Object result = handleMember(context, lm, instance, handled);
+ if (handled[0])
+ return result;
+ lval = lm.invoke(instance);
+ } else
+ lval = lhs.evaluate(context);
+
+ Object rval = rhs.evaluate(context);
+ if (lval == null)
+ throw new IllegalArgumentException("Unable to use [] on null"); //$NON-NLS-1$
+
+ if (lval instanceof Map<?, ?>)
+ return ((Map<?, ?>) lval).get(rval);
+
+ if (rval instanceof Number) {
+ if (lval instanceof List<?>)
+ return ((List<?>) lval).get(((Number) rval).intValue());
+ if (lval != null && lval.getClass().isArray())
+ return ((Object[]) lval)[((Number) rval).intValue()];
+ }
+
+ if (lval instanceof Dictionary<?, ?>)
+ return ((Dictionary<?, ?>) lval).get(rval);
+
+ throw new IllegalArgumentException("Unable to use [] on a " + lval.getClass().getName()); //$NON-NLS-1$
+ }
+
+ public Iterator<?> evaluateAsIterator(IEvaluationContext context) {
+ Object value = evaluate(context);
+ if (!(value instanceof Iterator<?>))
+ value = RepeatableIterator.create(value);
+ return (Iterator<?>) value;
+ }
+
+ public int getExpressionType() {
+ return TYPE_AT;
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ appendOperand(bld, rootVariable, lhs, getPriority());
+ bld.append('[');
+ appendOperand(bld, rootVariable, rhs, PRIORITY_MAX);
+ bld.append(']');
+ }
+
+ public String getOperator() {
+ return OPERATOR_AT;
+ }
+
+ public int getPriority() {
+ return PRIORITY_MEMBER;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Binary.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Binary.java
new file mode 100644
index 000000000..f4f7da504
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Binary.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.internal.p2.metadata.expression.Member.DynamicMember;
+import org.eclipse.equinox.p2.metadata.Version;
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * The abstract base class for all binary operations
+ */
+public abstract class Binary extends Expression {
+ public final Expression lhs;
+
+ public final Expression rhs;
+
+ protected Binary(Expression lhs, Expression rhs) {
+ this.lhs = lhs;
+ this.rhs = rhs;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return super.accept(visitor) && lhs.accept(visitor) && rhs.accept(visitor);
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0) {
+ Binary be = (Binary) e;
+ cmp = lhs.compareTo(be.lhs);
+ if (cmp == 0)
+ cmp = rhs.compareTo(be.rhs);
+ }
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ if (super.equals(o)) {
+ Binary bo = (Binary) o;
+ return lhs.equals(bo.lhs) && rhs.equals(bo.rhs);
+ }
+ return false;
+ }
+
+ public int getPriority() {
+ return PRIORITY_BINARY; // Default priority
+ }
+
+ public int hashCode() {
+ int result = 31 + lhs.hashCode();
+ return 31 * result + rhs.hashCode();
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ appendOperand(bld, rootVariable, lhs, getPriority());
+ bld.append(' ');
+ bld.append(getOperator());
+ bld.append(' ');
+ appendOperand(bld, rootVariable, rhs, getPriority());
+ }
+
+ /**
+ * Appends the LDAP filter attribute name from the lhs expression if
+ * possible.
+ * @throws UnsupportedOperationException when this expression does not conform to an
+ * LDAP filter binary expression
+ */
+ void appendLDAPAttribute(StringBuffer buf) {
+ if (lhs instanceof DynamicMember) {
+ DynamicMember attr = (DynamicMember) lhs;
+ if (attr.operand instanceof Variable) {
+ buf.append(attr.getName());
+ return;
+ }
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ private static char hexChar(int value) {
+ return (char) (value < 10 ? ('0' + value) : ('a' + (value - 10)));
+ }
+
+ static void appendLDAPEscaped(StringBuffer bld, String str) {
+ appendLDAPEscaped(bld, str, true);
+ }
+
+ static void appendLDAPEscaped(StringBuffer bld, String str, boolean escapeWild) {
+ int top = str.length();
+ for (int idx = 0; idx < top; ++idx) {
+ char c = str.charAt(idx);
+ if (!escapeWild) {
+ if (c == '*') {
+ bld.append(c);
+ continue;
+ } else if (c == '\\' && idx + 1 < top && str.charAt(idx + 1) == '*') {
+ bld.append("\\2a"); //$NON-NLS-1$
+ ++idx;
+ continue;
+ }
+ }
+ if (c == '(' || c == ')' || c == '*' || c == '\\' || c < ' ' || c > 127) {
+ short cs = (short) c;
+ bld.append('\\');
+ bld.append(hexChar((cs & 0x00f0) >> 4));
+ bld.append(hexChar(cs & 0x000f));
+ } else
+ bld.append(c);
+ }
+ }
+
+ /**
+ * Appends the LDAP filter value from the rhs expression if
+ * possible.
+ * @throws UnsupportedOperationException when this expression does not conform to an
+ * LDAP filter binary expression
+ */
+ void appendLDAPValue(StringBuffer buf) {
+ if (rhs instanceof Literal) {
+ Object value = rhs.evaluate(null);
+ if (value instanceof String || value instanceof Version) {
+ appendLDAPEscaped(buf, value.toString());
+ return;
+ }
+ }
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CoercingComparator.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CoercingComparator.java
new file mode 100644
index 000000000..c69e75e32
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CoercingComparator.java
@@ -0,0 +1,392 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Comparator;
+import org.eclipse.equinox.internal.p2.metadata.MetadataActivator;
+import org.eclipse.equinox.p2.metadata.Version;
+
+/**
+ * A comparator that performs coercion if needed before comparison.
+ * @param <T> The type for the comparator.
+ */
+public abstract class CoercingComparator<T> {
+ static class BooleanCoercer extends CoercingComparator<Boolean> {
+ public int compare(Boolean o1, Boolean o2) {
+ return o1.booleanValue() == o2.booleanValue() ? 0 : (o1.booleanValue() ? 1 : -1);
+ }
+
+ @Override
+ Boolean coerce(Object v) {
+ if (v instanceof Boolean)
+ return (Boolean) v;
+ if (v instanceof String) {
+ String sv = ((String) v).trim();
+ if (sv.equalsIgnoreCase("true")) //$NON-NLS-1$
+ return Boolean.TRUE;
+ if (sv.equalsIgnoreCase("false")) //$NON-NLS-1$
+ return Boolean.FALSE;
+ }
+ throw uncoercable(v);
+ }
+
+ @Override
+ Class<Boolean> getCoerceClass() {
+ return Boolean.class;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 7;
+ }
+ }
+
+ static class ClassCoercer extends CoercingComparator<Class<?>> {
+ public int compare(Class<?> o1, Class<?> o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+
+ @Override
+ Class<?> coerce(Object v) {
+ if (v instanceof Class<?>)
+ return (Class<?>) v;
+ if (v instanceof String) {
+ try {
+ return MetadataActivator.context.getBundle().loadClass(((String) v).trim());
+ } catch (Exception e) {
+ //
+ }
+ }
+ throw uncoercable(v);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ Class<Class<?>> getCoerceClass() {
+ Class<?> cls = Class.class;
+ return (Class<Class<?>>) cls;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 11;
+ }
+ }
+
+ static class FromStringCoercer<T extends Comparable<Object>> extends CoercingComparator<T> {
+ private final Class<T> coerceClass;
+ private final Constructor<T> constructor;
+
+ public FromStringCoercer(Class<T> coerceClass, Constructor<T> constructor) {
+ this.coerceClass = coerceClass;
+ this.constructor = constructor;
+ }
+
+ @Override
+ T coerce(Object v) {
+ if (v instanceof String) {
+ try {
+ return constructor.newInstance(new Object[] {((String) v).trim()});
+ } catch (Exception e) {
+ //
+ }
+ }
+ throw uncoercable(v);
+ }
+
+ @Override
+ int compare(T o1, T o2) {
+ return o1.compareTo(o2);
+ }
+
+ @Override
+ Class<T> getCoerceClass() {
+ return coerceClass;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 0;
+ }
+ }
+
+ static class IntegerCoercer extends CoercingComparator<Integer> {
+ public int compare(Integer o1, Integer o2) {
+ return o1.compareTo(o2);
+ }
+
+ @Override
+ Integer coerce(Object v) {
+ if (v instanceof Integer)
+ return (Integer) v;
+ if (v instanceof Number)
+ return new Integer(((Number) v).intValue());
+ if (v instanceof String) {
+ try {
+ return Integer.valueOf(((String) v).trim());
+ } catch (NumberFormatException e) {
+ //
+ }
+ }
+ throw uncoercable(v);
+ }
+
+ @Override
+ Class<Integer> getCoerceClass() {
+ return Integer.class;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 6;
+ }
+ }
+
+ static class LongCoercer extends CoercingComparator<Long> {
+ public int compare(Long o1, Long o2) {
+ return o1.compareTo(o2);
+ }
+
+ @Override
+ Long coerce(Object v) {
+ if (v instanceof Long)
+ return (Long) v;
+ if (v instanceof Number)
+ return new Long(((Number) v).longValue());
+ if (v instanceof String) {
+ try {
+ return Long.valueOf(((String) v).trim());
+ } catch (NumberFormatException e) {
+ //
+ }
+ }
+ throw uncoercable(v);
+ }
+
+ @Override
+ Class<Long> getCoerceClass() {
+ return Long.class;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 5;
+ }
+ }
+
+ static class StringCoercer extends CoercingComparator<String> {
+ public int compare(String o1, String o2) {
+ return o1.compareTo(o2);
+ }
+
+ @Override
+ String coerce(Object v) {
+ if (v instanceof Class<?>)
+ return ((Class<?>) v).getName();
+ return v.toString();
+ }
+
+ @Override
+ Class<String> getCoerceClass() {
+ return String.class;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 10;
+ }
+ }
+
+ static class VersionCoercer extends CoercingComparator<Version> {
+ public int compare(Version o1, Version o2) {
+ return o1.compareTo(o2);
+ }
+
+ boolean canCoerceTo(Class<?> cls) {
+ return Version.class.isAssignableFrom(cls);
+ }
+
+ @Override
+ Version coerce(Object v) {
+ if (v instanceof Version)
+ return (Version) v;
+ if (v instanceof String)
+ return Version.create((String) v);
+ if (v instanceof String) {
+ try {
+ return Version.create((String) v);
+ } catch (NumberFormatException e) {
+ //
+ }
+ }
+ throw uncoercable(v);
+ }
+
+ @Override
+ Class<Version> getCoerceClass() {
+ return Version.class;
+ }
+
+ @Override
+ int getCoercePrio() {
+ return 1;
+ }
+ }
+
+ private static class SetAccessibleAction implements PrivilegedAction<Object> {
+ private final AccessibleObject accessible;
+
+ SetAccessibleAction(AccessibleObject accessible) {
+ this.accessible = accessible;
+ }
+
+ public Object run() {
+ accessible.setAccessible(true);
+ return null;
+ }
+ }
+
+ private static CoercingComparator<?>[] coercers = {new ClassCoercer(), new BooleanCoercer(), new LongCoercer(), new IntegerCoercer(), new VersionCoercer(), new StringCoercer()};
+
+ private static final Class<?>[] constructorType = new Class<?>[] {String.class};
+
+ /**
+ * Finds the comparator for <code>a</code> and <code>b</code> and delegates the coercion/comparison to the comparator
+ * according to priority.
+ * @param o1 the first object to be compared.
+ * @param o2 the second object to be compared.
+ * @return The result of the comparison
+ * @throws IllegalArgumentException if no comparator was found or if coercion was impossible
+ * @see Comparator#compare(Object, Object)
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public static <TA extends Object, TB extends Object> int coerceAndCompare(TA o1, TB o2) throws IllegalArgumentException {
+ if (o1 == null || o2 == null)
+ throw new IllegalArgumentException("Cannot compare null to anything"); //$NON-NLS-1$
+
+ if (o1 instanceof Comparable && o1.getClass().isAssignableFrom(o2.getClass()))
+ return ((Comparable) o1).compareTo(o2);
+
+ if (o2 instanceof Comparable && o2.getClass().isAssignableFrom(o1.getClass()))
+ return -((Comparable) o2).compareTo(o1);
+
+ CoercingComparator<TA> ca = getComparator(o1, o2);
+ CoercingComparator<TB> cb = getComparator(o2, o1);
+ return ca.getCoercePrio() <= cb.getCoercePrio() ? ca.compare(o1, ca.coerce(o2)) : cb.compare(cb.coerce(o1), o2);
+ }
+
+ /**
+ * Finds the comparator for <code>a</code> and <code>b</code> and delegates the coercion/equal to the comparator
+ * according to priority.
+ * @param o1 the first object to be compared.
+ * @param o2 the second object to be compared.
+ * @return The result of the equality test
+ * @throws IllegalArgumentException if no comparator was found or if coercion was impossible
+ * @see Object#equals(Object)
+ */
+ public static <TA extends Object, TB extends Object> boolean coerceAndEquals(TA o1, TB o2) throws IllegalArgumentException {
+ if (o1 == o2)
+ return true;
+
+ if (o1 == null || o2 == null)
+ return false;
+
+ if (o1.getClass() != o2.getClass()) {
+ if (o1.getClass().isAssignableFrom(o2.getClass()))
+ return o1.equals(o2);
+ if (o2.getClass().isAssignableFrom(o1.getClass()))
+ return o2.equals(o1);
+ try {
+ CoercingComparator<TA> ca = getComparator(o1, o2);
+ CoercingComparator<TB> cb = getComparator(o2, o1);
+ return ca.getCoercePrio() <= cb.getCoercePrio() ? o1.equals(ca.coerce(o2)) : o2.equals(cb.coerce(o1));
+ } catch (IllegalArgumentException e) {
+ //
+ }
+ }
+ return o1.equals(o2);
+ }
+
+ /**
+ * Obtains the coercing comparator for the given <code>value</code>.
+ * @param value The value
+ * @return The coercing comparator
+ */
+ @SuppressWarnings("unchecked")
+ public static <V extends Object> CoercingComparator<V> getComparator(V value, Object v2) {
+ Class<V> vClass = (Class<V>) value.getClass();
+ CoercingComparator<?>[] carr = coercers;
+ int idx = carr.length;
+ while (--idx >= 0) {
+ CoercingComparator<?> c = carr[idx];
+ if (c.canCoerceTo(vClass)) {
+ CoercingComparator<V> cv = (CoercingComparator<V>) c;
+ return cv;
+ }
+ }
+
+ if (value instanceof Comparable<?> && v2 instanceof String) {
+ Class<Comparable<Object>> cClass = (Class<Comparable<Object>>) vClass;
+ Constructor<Comparable<Object>> constructor;
+ try {
+ constructor = cClass.getConstructor(constructorType);
+ if (!constructor.isAccessible())
+ AccessController.doPrivileged(new SetAccessibleAction(constructor));
+ synchronized (CoercingComparator.class) {
+ int top = coercers.length;
+ CoercingComparator<?>[] nc = new CoercingComparator<?>[top + 1];
+ System.arraycopy(coercers, 0, nc, 1, top);
+ CoercingComparator<V> cv = (CoercingComparator<V>) new FromStringCoercer<Comparable<Object>>(cClass, constructor);
+ nc[0] = cv;
+ coercers = nc;
+ return cv;
+ }
+ } catch (Exception e) {
+ //
+ }
+ }
+ throw new IllegalArgumentException("No comparator for " + vClass.getName()); //$NON-NLS-1$
+ }
+
+ protected IllegalArgumentException uncoercable(Object v) {
+ StringBuffer sb = new StringBuffer("Cannot coerce "); //$NON-NLS-1$
+ if (v instanceof String) {
+ sb.append('\'');
+ sb.append(v);
+ sb.append('\'');
+ } else if (v instanceof Number) {
+ sb.append("number "); //$NON-NLS-1$
+ sb.append(v);
+ } else {
+ sb.append("an object of instance "); //$NON-NLS-1$
+ sb.append(v.getClass().getName());
+ }
+ sb.append(" into a "); //$NON-NLS-1$
+ sb.append(getCoerceClass().getName());
+ return new IllegalArgumentException(sb.toString());
+ }
+
+ boolean canCoerceTo(Class<?> cls) {
+ return cls == getCoerceClass();
+ }
+
+ abstract T coerce(Object v);
+
+ abstract int compare(T o1, T o2);
+
+ abstract Class<T> getCoerceClass();
+
+ abstract int getCoercePrio();
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CollectionFilter.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CollectionFilter.java
new file mode 100644
index 000000000..bb845d373
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/CollectionFilter.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.Iterator;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * Some kind of operation that is performed for each element of a collection. I.e.
+ * <code>x.&lt;operation&gt;(y | &lt;expression&rt;)</code>
+ */
+public abstract class CollectionFilter extends Unary {
+ public static void appendProlog(StringBuffer bld, Variable rootVariable, Expression lhs, String operator) {
+ if (lhs != rootVariable) {
+ appendOperand(bld, rootVariable, lhs, PRIORITY_COLLECTION);
+ bld.append('.');
+ }
+ bld.append(operator);
+ bld.append('(');
+ }
+
+ public final LambdaExpression lambda;
+
+ protected CollectionFilter(Expression collection, LambdaExpression lambda) {
+ super(collection);
+ this.lambda = lambda;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return super.accept(visitor) && lambda.accept(visitor);
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = lambda.compareTo(((CollectionFilter) e).lambda);
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && lambda.equals(((CollectionFilter) o).lambda);
+ }
+
+ public final Object evaluate(IEvaluationContext context) {
+ Iterator<?> lval = operand.evaluateAsIterator(context);
+ context = lambda.prolog(context);
+ return evaluate(context, lval);
+ }
+
+ public final Iterator<?> evaluateAsIterator(IEvaluationContext context) {
+ Iterator<?> lval = operand.evaluateAsIterator(context);
+ context = lambda.prolog(context);
+ return evaluateAsIterator(context, lval);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ appendProlog(bld, rootVariable, operand, getOperator());
+ appendOperand(bld, rootVariable, lambda, PRIORITY_LAMBDA);
+ bld.append(')');
+ }
+
+ public int hashCode() {
+ int result = 31 + operand.hashCode();
+ return 31 * result + lambda.hashCode();
+ }
+
+ public int getPriority() {
+ return PRIORITY_COLLECTION;
+ }
+
+ protected abstract Object evaluate(final IEvaluationContext context, Iterator<?> iterator);
+
+ protected Iterator<?> evaluateAsIterator(IEvaluationContext context, Iterator<?> iterator) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Compare.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Compare.java
new file mode 100644
index 000000000..7e958e41d
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Compare.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * Comparisons for magnitude.
+ */
+final class Compare extends Binary {
+ static IllegalArgumentException uncomparable(Object lval, Object rval) {
+ return new IllegalArgumentException("Cannot compare a " + lval.getClass().getName() + " to a " + rval.getClass().getName()); //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ final boolean compareLess;
+
+ final boolean equalOK;
+
+ Compare(Expression lhs, Expression rhs, boolean compareLess, boolean equalOK) {
+ super(lhs, rhs);
+ this.compareLess = compareLess;
+ this.equalOK = equalOK;
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ int cmpResult = CoercingComparator.coerceAndCompare(lhs.evaluate(context), rhs.evaluate(context));
+ return Boolean.valueOf(cmpResult == 0 ? equalOK : (cmpResult < 0 ? compareLess : !compareLess));
+ }
+
+ public int getExpressionType() {
+ return compareLess ? (equalOK ? TYPE_LESS_EQUAL : TYPE_LESS) : (equalOK ? TYPE_GREATER_EQUAL : TYPE_GREATER);
+ }
+
+ public String getOperator() {
+ return compareLess ? (equalOK ? OPERATOR_LT_EQUAL : OPERATOR_LT) : (equalOK ? OPERATOR_GT_EQUAL : OPERATOR_GT);
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ if (!equalOK)
+ buf.append("(!"); //$NON-NLS-1$
+ buf.append('(');
+ appendLDAPAttribute(buf);
+ if (equalOK)
+ buf.append(compareLess ? OPERATOR_LT_EQUAL : OPERATOR_GT_EQUAL);
+ else
+ buf.append(compareLess ? OPERATOR_GT_EQUAL : OPERATOR_LT_EQUAL);
+ appendLDAPValue(buf);
+ buf.append(')');
+ if (!equalOK)
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Equals.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Equals.java
new file mode 100644
index 000000000..7452c3165
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Equals.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * An expression that performs the == and != comparisons
+ */
+final class Equals extends Binary {
+ final boolean negate;
+
+ Equals(Expression lhs, Expression rhs, boolean negate) {
+ super(lhs, rhs);
+ this.negate = negate;
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ boolean result = CoercingComparator.coerceAndEquals(lhs.evaluate(context), rhs.evaluate(context));
+ if (negate)
+ result = !result;
+ return Boolean.valueOf(result);
+ }
+
+ public int getExpressionType() {
+ return negate ? TYPE_NOT_EQUALS : TYPE_EQUALS;
+ }
+
+ public String getOperator() {
+ return negate ? OPERATOR_NOT_EQUALS : OPERATOR_EQUALS;
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ if (negate)
+ buf.append("(!"); //$NON-NLS-1$
+ buf.append('(');
+ appendLDAPAttribute(buf);
+ buf.append('=');
+ appendLDAPValue(buf);
+ buf.append(')');
+ if (negate)
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/EvaluationContext.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/EvaluationContext.java
new file mode 100644
index 000000000..8fb3665bc
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/EvaluationContext.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.IExpression;
+
+/**
+ * Highly specialized evaluation contexts optimized for misc purposes
+ */
+public class EvaluationContext implements IEvaluationContext {
+ public static class Parameters extends EvaluationContext {
+ private static final Object[] noParameters = new Object[0];
+
+ private final Object[] parameters;
+
+ public Parameters(IEvaluationContext parentContext, Object[] parameters) {
+ super(parentContext);
+ this.parameters = parameters == null ? noParameters : parameters;
+ }
+
+ public Object getParameter(int position) {
+ return position >= 0 && position < parameters.length ? parameters[position] : super.getParameter(position);
+ }
+ }
+
+ public static class SingleVariableContext implements IEvaluationContext {
+ private final IEvaluationContext parentContext;
+
+ private Object value;
+
+ private final IExpression variable;
+
+ public SingleVariableContext(IEvaluationContext parentContext, IExpression variable) {
+ this.parentContext = parentContext;
+ this.variable = variable;
+ }
+
+ public Object getParameter(int position) {
+ return parentContext.getParameter(position);
+ }
+
+ public Object getValue(IExpression var) {
+ return variable == var ? value : parentContext.getValue(var);
+ }
+
+ public void setValue(IExpression var, Object val) {
+ if (variable == var)
+ value = val;
+ else
+ parentContext.setValue(var, val);
+ }
+ }
+
+ static class MultiVariableContext implements IEvaluationContext {
+ private final IEvaluationContext parentContext;
+
+ private final Object[] values;
+
+ public MultiVariableContext(IEvaluationContext parentContext, IExpression[] variables) {
+ this.parentContext = parentContext;
+ values = new Object[variables.length * 2];
+ for (int idx = 0, ndx = 0; ndx < variables.length; ++ndx, idx += 2)
+ values[idx] = variables[ndx];
+ }
+
+ public Object getParameter(int position) {
+ return parentContext.getParameter(position);
+ }
+
+ public Object getValue(IExpression variable) {
+ for (int idx = 0; idx < values.length; ++idx)
+ if (values[idx++] == variable)
+ return values[idx];
+ return parentContext.getValue(variable);
+ }
+
+ public void setValue(IExpression variable, Object value) {
+ for (int idx = 0; idx < values.length; ++idx)
+ if (values[idx++] == variable) {
+ values[idx] = value;
+ return;
+ }
+ parentContext.setValue(variable, value);
+ }
+ }
+
+ public static final EvaluationContext INSTANCE = new EvaluationContext(null);
+
+ public static IEvaluationContext create() {
+ return INSTANCE;
+ }
+
+ public static IEvaluationContext create(IEvaluationContext parent, IExpression variable) {
+ return new SingleVariableContext(parent, variable);
+ }
+
+ public static IEvaluationContext create(IEvaluationContext parent, IExpression[] variables) {
+ return variables.length == 1 ? new SingleVariableContext(parent, variables[0]) : new MultiVariableContext(parent, variables);
+ }
+
+ public static IEvaluationContext create(IEvaluationContext parent, Object[] parameters) {
+ return new Parameters(parent, parameters);
+ }
+
+ public static IEvaluationContext create(IExpression variable) {
+ return new SingleVariableContext(null, variable);
+ }
+
+ public static IEvaluationContext create(IExpression[] variables) {
+ if (variables == null || variables.length == 0)
+ return INSTANCE;
+ return variables.length == 1 ? create(variables[0]) : new MultiVariableContext(INSTANCE, variables);
+ }
+
+ public static IEvaluationContext create(Object[] parameters, IExpression variable) {
+ return parameters == null || parameters.length == 0 ? create(variable) : new SingleVariableContext(new Parameters(null, parameters), variable);
+ }
+
+ public static IEvaluationContext create(Object[] parameters, IExpression[] variables) {
+ if (parameters == null || parameters.length == 0)
+ return create(variables);
+
+ Parameters pctx = new Parameters(null, parameters);
+ if (variables == null || variables.length == 0)
+ return pctx;
+ return create(pctx, variables);
+ }
+
+ protected EvaluationContext(IEvaluationContext parentContext) {
+ this.parentContext = parentContext;
+ }
+
+ private final IEvaluationContext parentContext;
+
+ public Object getParameter(int position) {
+ if (parentContext == null)
+ throw new IllegalArgumentException("No such parameter: $" + position); //$NON-NLS-1$
+ return parentContext.getParameter(position);
+ }
+
+ public Object getValue(IExpression variable) {
+ if (parentContext == null)
+ throw new IllegalArgumentException("No such variable: " + variable); //$NON-NLS-1$
+ return parentContext.getValue(variable);
+ }
+
+ public void setValue(IExpression variable, Object value) {
+ if (parentContext == null)
+ throw new IllegalArgumentException("No such variable: " + variable); //$NON-NLS-1$
+ parentContext.setValue(variable, value);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Exists.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Exists.java
new file mode 100644
index 000000000..14fbc5df4
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Exists.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.Iterator;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * A collection filter that yields true if the <code>filter</code> yields true for
+ * any of the elements of the <code>collection</code>
+ */
+final class Exists extends CollectionFilter {
+ Exists(Expression collection, LambdaExpression lambda) {
+ super(collection, lambda);
+ }
+
+ protected Object evaluate(IEvaluationContext context, Iterator<?> itor) {
+ Variable variable = lambda.getItemVariable();
+ while (itor.hasNext()) {
+ variable.setValue(context, itor.next());
+ if (lambda.evaluate(context) == Boolean.TRUE)
+ return Boolean.TRUE;
+ }
+ return Boolean.FALSE;
+ }
+
+ public int getExpressionType() {
+ return TYPE_EXISTS;
+ }
+
+ public String getOperator() {
+ return KEYWORD_EXISTS;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Expression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Expression.java
new file mode 100644
index 000000000..75f47dd94
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Expression.java
@@ -0,0 +1,341 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.*;
+import org.eclipse.equinox.p2.metadata.expression.*;
+
+/**
+ * The base class of the expression tree.
+ */
+public abstract class Expression implements IExpression, Comparable<Expression>, IExpressionConstants {
+
+ static final Expression[] emptyArray = new Expression[0];
+
+ public static void appendOperand(StringBuffer bld, Variable rootVariable, Expression operand, int priority) {
+ if (priority < operand.getPriority()) {
+ bld.append('(');
+ operand.toString(bld, rootVariable);
+ bld.append(')');
+ } else
+ operand.toString(bld, rootVariable);
+ }
+
+ public static Expression[] assertLength(Expression[] operands, int minLength, int maxLength, String operand) {
+ if (operands == null)
+ operands = emptyArray;
+ if (operands.length < minLength)
+ throw new IllegalArgumentException("Not enough operands for " + operand); //$NON-NLS-1$
+ if (operands.length > maxLength)
+ throw new IllegalArgumentException("Too many operands for " + operand); //$NON-NLS-1$
+ return operands;
+ }
+
+ public static Expression[] assertLength(Expression[] operands, int length, String operand) {
+ if (operands == null)
+ operands = emptyArray;
+ if (operands.length < length)
+ throw new IllegalArgumentException("Not enough operands for " + operand); //$NON-NLS-1$
+ return operands;
+ }
+
+ public static int compare(Expression[] arr1, Expression[] arr2) {
+ int max = arr1.length;
+ if (max > arr2.length)
+ max = arr2.length;
+ for (int idx = 0; idx < max; ++idx) {
+ int cmp = arr1[idx].compareTo(arr2[idx]);
+ if (cmp != 0)
+ return cmp;
+ }
+ if (max == arr2.length) {
+ if (max < arr1.length)
+ return 1;
+ return 0;
+ }
+ return -1;
+ }
+
+ public static boolean equals(Expression[] arr1, Expression[] arr2) {
+ int idx = arr1.length;
+ if (idx != arr2.length)
+ return false;
+ while (--idx >= 0)
+ if (!arr1[idx].equals(arr2[idx]))
+ return false;
+ return true;
+ }
+
+ public static int hashCode(Expression[] arr) {
+ int idx = arr.length;
+ int result = 1;
+ while (--idx >= 0)
+ result = 31 * result + arr[idx].hashCode();
+ return result;
+ }
+
+ public static void elementsToString(StringBuffer bld, Variable rootVariable, Expression[] elements) {
+ int top = elements.length;
+ if (top > 0) {
+ elements[0].toString(bld, rootVariable);
+ for (int idx = 1; idx < top; ++idx) {
+ bld.append(", "); //$NON-NLS-1$
+ appendOperand(bld, rootVariable, elements[idx], PRIORITY_MAX);
+ }
+ }
+ }
+
+ /**
+ * Let the visitor visit this instance and all expressions that this
+ * instance contains.
+ * @param visitor The visiting visitor.
+ * @return <code>true</code> if the visitor should continue visiting, <code>false</code> otherwise.
+ */
+ public boolean accept(IExpressionVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = getPriority() - e.getPriority();
+ if (cmp == 0) {
+ int e1 = getExpressionType();
+ int e2 = e.getExpressionType();
+ cmp = e1 > e2 ? 1 : (e1 == e2 ? 0 : -1);
+ }
+ return cmp;
+ }
+
+ public boolean equals(Object e) {
+ if (e == this)
+ return true;
+ if (e == null || getClass() != e.getClass())
+ return false;
+ return getExpressionType() == ((Expression) e).getExpressionType();
+ }
+
+ /**
+ * Evaluate this expression with given context and variables.
+ * @param context The evaluation context
+ * @return The result of the evaluation.
+ */
+ public abstract Object evaluate(IEvaluationContext context);
+
+ public Iterator<?> evaluateAsIterator(IEvaluationContext context) {
+ Object value = evaluate(context);
+ if (!(value instanceof Iterator<?>))
+ value = RepeatableIterator.create(value);
+ return (Iterator<?>) value;
+ }
+
+ public abstract String getOperator();
+
+ public abstract int getPriority();
+
+ public boolean isRootVariable() {
+ return false;
+ }
+
+ public final String toLDAPString() {
+ StringBuffer bld = new StringBuffer();
+ toLDAPString(bld);
+ return bld.toString();
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ throw new UnsupportedOperationException();
+ }
+
+ public final String toString() {
+ StringBuffer bld = new StringBuffer();
+ toString(bld);
+ return bld.toString();
+ }
+
+ public void toString(StringBuffer bld) {
+ toString(bld, ExpressionFactory.THIS);
+ }
+
+ public abstract void toString(StringBuffer bld, Variable rootVariable);
+
+ private static class Compacter {
+ private Expression base;
+
+ private List<Expression> parts;
+
+ private int op;
+
+ Compacter(Expression base, int op) {
+ this.base = base;
+ this.op = op;
+ }
+
+ Expression getResultingFilter() {
+ if (parts == null)
+ return base;
+
+ int partsOp = op == TYPE_AND ? TYPE_OR : TYPE_AND;
+ return addFilter(base, normalize(parts, partsOp), op);
+ }
+
+ boolean merge(Expression b) {
+ Expression[] aArr;
+ Expression[] bArr;
+ if (base.getExpressionType() == op)
+ aArr = getFilterImpls(base);
+ else
+ aArr = new Expression[] {base};
+
+ if (b.getExpressionType() == op)
+ bArr = getFilterImpls(b);
+ else
+ bArr = new Expression[] {b};
+
+ List<Expression> common = null;
+ List<Expression> onlyA = null;
+
+ int atop = aArr.length;
+ int btop = bArr.length;
+ int aidx;
+ int bidx;
+ for (aidx = 0; aidx < atop; ++aidx) {
+ Expression af = aArr[aidx];
+ for (bidx = 0; bidx < btop; ++bidx) {
+ Expression bf = bArr[bidx];
+ if (af.equals(bf)) {
+ if (common == null)
+ common = new ArrayList<Expression>();
+ common.add(af);
+ break;
+ }
+ }
+ if (bidx == btop) {
+ if (onlyA == null)
+ onlyA = new ArrayList<Expression>();
+ onlyA.add(af);
+ }
+ }
+ if (common == null)
+ // Nothing in common
+ return false;
+
+ if (onlyA == null && parts == null)
+ return true;
+
+ List<Expression> onlyB = null;
+ for (bidx = 0; bidx < btop; ++bidx) {
+ Expression bf = bArr[bidx];
+ for (aidx = 0; aidx < atop; ++aidx)
+ if (bf.equals(aArr[aidx]))
+ break;
+ if (aidx == atop) {
+ if (onlyB == null)
+ onlyB = new ArrayList<Expression>();
+ onlyB.add(bf);
+ }
+ }
+
+ if (onlyB == null && parts == null) {
+ // All of B is already covered by base
+ base = b;
+ return true;
+ }
+
+ if (parts == null)
+ parts = new ArrayList<Expression>();
+
+ if (onlyA != null) {
+ base = normalize(common, op);
+ Expression af = normalize(onlyA, op);
+ if (!parts.contains(af))
+ parts.add(af);
+ }
+ Expression bf = normalize(onlyB, op);
+ if (!parts.contains(bf))
+ parts.add(bf);
+ return true;
+ }
+ }
+
+ static Expression addFilter(Expression base, Expression subFilter, int expressionType) {
+ if (base.equals(subFilter))
+ return base;
+
+ ArrayList<Expression> filters = new ArrayList<Expression>(2);
+ filters.add(base);
+ filters.add(subFilter);
+ return normalize(filters, expressionType);
+ }
+
+ static Expression normalize(List<Expression> operands, int op) {
+ int top = operands.size();
+ if (top == 1)
+ return operands.get(0);
+
+ // a | (b | c) becomes a | b | c
+ // a & (b & c) becomes a & b & c
+ //
+ for (int idx = 0; idx < top; ++idx) {
+ Expression f = operands.get(idx);
+ if (f.getExpressionType() != op)
+ continue;
+
+ Expression[] sfs = getFilterImpls(f);
+ operands.remove(idx);
+ --top;
+ for (int ndx = 0; ndx < sfs.length; ++ndx) {
+ Expression nf = sfs[ndx];
+ if (!operands.contains(nf))
+ operands.add(nf);
+ }
+ }
+ top = operands.size();
+ if (top == 1)
+ return operands.get(0);
+
+ Collections.sort(operands);
+ List<Compacter> splits = new ArrayList<Compacter>();
+ int reverseOp = op == TYPE_AND ? TYPE_OR : TYPE_AND;
+
+ for (int idx = 0; idx < top; ++idx)
+ merge(splits, operands.get(idx), reverseOp);
+
+ operands.clear();
+ top = splits.size();
+ for (int idx = 0; idx < top; ++idx) {
+ Expression filter = splits.get(idx).getResultingFilter();
+ if (!operands.contains(filter))
+ operands.add(filter);
+ }
+ top = operands.size();
+ if (top == 1)
+ return operands.get(0);
+
+ Collections.sort(operands);
+ Expression[] expArray = operands.toArray(new Expression[top]);
+ return op == TYPE_AND ? new And(expArray) : new Or(expArray);
+ }
+
+ static void merge(List<Compacter> splits, Expression base, int op) {
+ int top = splits.size();
+ for (int idx = 0; idx < top; ++idx) {
+ Compacter split = splits.get(idx);
+ if (split.merge(base))
+ return;
+ }
+ splits.add(new Compacter(base, op));
+ }
+
+ static Expression[] getFilterImpls(Expression expression) {
+ if (expression instanceof NAry)
+ return ((NAry) expression).operands;
+ throw new IllegalArgumentException();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/ExpressionFactory.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/ExpressionFactory.java
new file mode 100644
index 000000000..a9e70e311
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/ExpressionFactory.java
@@ -0,0 +1,125 @@
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.List;
+import org.eclipse.equinox.p2.metadata.expression.*;
+
+public class ExpressionFactory implements IExpressionFactory, IExpressionConstants {
+ public static final IExpressionFactory INSTANCE = new ExpressionFactory();
+ public static final Variable THIS = new Variable(VARIABLE_THIS);
+
+ protected static Expression[] convertArray(IExpression[] operands) {
+ Expression[] ops = new Expression[operands.length];
+ System.arraycopy(operands, 0, ops, 0, operands.length);
+ return ops;
+ }
+
+ protected ExpressionFactory() {
+ // Maintain singleton
+ }
+
+ public IExpression all(IExpression collection, IExpression lambda) {
+ return new All((Expression) collection, (LambdaExpression) lambda);
+ }
+
+ public IExpression and(IExpression... operands) {
+ return new And(convertArray(operands));
+ }
+
+ public IExpression at(IExpression target, IExpression key) {
+ return new At((Expression) target, (Expression) key);
+ }
+
+ @SuppressWarnings("unchecked")
+ public IExpression normalize(List<? extends IExpression> operands, int expressionType) {
+ return Expression.normalize((List<Expression>) operands, expressionType);
+ }
+
+ public IExpression constant(Object value) {
+ return Literal.create(value);
+ }
+
+ public IEvaluationContext createContext(Object... parameters) {
+ return EvaluationContext.create(parameters, (Variable[]) null);
+ }
+
+ public IEvaluationContext createContext(IExpression[] variables, Object... parameters) {
+ return EvaluationContext.create(parameters, variables);
+ }
+
+ public IFilterExpression filterExpression(IExpression expression) {
+ return new LDAPFilter((Expression) expression);
+ }
+
+ public IExpression equals(IExpression lhs, IExpression rhs) {
+ return new Equals((Expression) lhs, (Expression) rhs, false);
+ }
+
+ public IExpression exists(IExpression collection, IExpression lambda) {
+ return new Exists((Expression) collection, (LambdaExpression) lambda);
+ }
+
+ public IExpression greater(IExpression lhs, IExpression rhs) {
+ return new Compare((Expression) lhs, (Expression) rhs, false, false);
+ }
+
+ public IExpression greaterEqual(IExpression lhs, IExpression rhs) {
+ return new Compare((Expression) lhs, (Expression) rhs, false, true);
+ }
+
+ public IExpression indexedParameter(int index) {
+ return new Parameter(index);
+ }
+
+ public IExpression lambda(IExpression variable, IExpression body) {
+ return new LambdaExpression((Variable) variable, (Expression) body);
+ }
+
+ public IExpression less(IExpression lhs, IExpression rhs) {
+ return new Compare((Expression) lhs, (Expression) rhs, true, false);
+ }
+
+ public IExpression lessEqual(IExpression lhs, IExpression rhs) {
+ return new Compare((Expression) lhs, (Expression) rhs, true, true);
+ }
+
+ public IExpression matches(IExpression lhs, IExpression rhs) {
+ return new Matches((Expression) lhs, (Expression) rhs);
+ }
+
+ public <T> IMatchExpression<T> matchExpression(IExpression expression, Object... parameters) {
+ return new MatchExpression<T>((Expression) expression, parameters);
+ }
+
+ public IExpression member(IExpression target, String name) {
+ return new Member.DynamicMember((Expression) target, name);
+ }
+
+ public IExpression not(IExpression operand) {
+ if (operand instanceof Equals) {
+ Equals eq = (Equals) operand;
+ return new Equals(eq.lhs, eq.rhs, !eq.negate);
+ }
+ if (operand instanceof Compare) {
+ Compare cmp = (Compare) operand;
+ return new Compare(cmp.lhs, cmp.rhs, !cmp.compareLess, !cmp.equalOK);
+ }
+ if (operand instanceof Not)
+ return ((Not) operand).operand;
+
+ return new Not((Expression) operand);
+ }
+
+ public IExpression or(IExpression... operands) {
+ return new Or(convertArray(operands));
+ }
+
+ public IExpression thisVariable() {
+ return THIS;
+ }
+
+ public IExpression variable(String name) {
+ if (VARIABLE_THIS.equals(name))
+ return THIS;
+ return new Variable(name);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IExpressionConstants.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IExpressionConstants.java
new file mode 100644
index 000000000..3da1dbfac
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IExpressionConstants.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+public interface IExpressionConstants {
+ String KEYWORD_ALL = "all"; //$NON-NLS-1$
+
+ String KEYWORD_BOOLEAN = "boolean"; //$NON-NLS-1$
+ String KEYWORD_CLASS = "class"; //$NON-NLS-1$
+ String KEYWORD_EXISTS = "exists"; //$NON-NLS-1$
+ String KEYWORD_FALSE = "false"; //$NON-NLS-1$
+ String KEYWORD_FILTER = "filter"; //$NON-NLS-1$
+ String KEYWORD_NULL = "null"; //$NON-NLS-1$
+ String KEYWORD_RANGE = "range"; //$NON-NLS-1$
+ String KEYWORD_TRUE = "true"; //$NON-NLS-1$
+
+ String KEYWORD_VERSION = "version"; //$NON-NLS-1$
+ String OPERATOR_AND = "&&"; //$NON-NLS-1$
+ String OPERATOR_AT = "[]"; //$NON-NLS-1$
+ String OPERATOR_EQUALS = "=="; //$NON-NLS-1$
+ String OPERATOR_GT = ">"; //$NON-NLS-1$
+ String OPERATOR_GT_EQUAL = ">="; //$NON-NLS-1$
+ String OPERATOR_LT = "<"; //$NON-NLS-1$
+ String OPERATOR_LT_EQUAL = "<="; //$NON-NLS-1$
+ String OPERATOR_MATCHES = "~="; //$NON-NLS-1$
+ String OPERATOR_MEMBER = "."; //$NON-NLS-1$
+ String OPERATOR_NOT = "!"; //$NON-NLS-1$
+ String OPERATOR_NOT_EQUALS = "!="; //$NON-NLS-1$
+
+ String OPERATOR_OR = "||"; //$NON-NLS-1$
+ String OPERATOR_PARAMETER = "$"; //$NON-NLS-1$
+
+ int PRIORITY_LITERAL = 1;
+ int PRIORITY_VARIABLE = 1;
+ int PRIORITY_FUNCTION = 2; // for extend query expressions
+ int PRIORITY_MEMBER = 3;
+ int PRIORITY_COLLECTION = 4;
+ int PRIORITY_NOT = 5;
+ int PRIORITY_BINARY = 6;
+ int PRIORITY_AND = 7;
+ int PRIORITY_OR = 8;
+ int PRIORITY_CONDITION = 9;
+ int PRIORITY_ASSIGNMENT = 10;
+ int PRIORITY_LAMBDA = 11;
+ int PRIORITY_COMMA = 12;
+ int PRIORITY_MAX = 20;
+
+ String VARIABLE_EVERYTHING = "everything"; //$NON-NLS-1$
+ String VARIABLE_THIS = "this"; //$NON-NLS-1$
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IRepeatableIterator.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IRepeatableIterator.java
new file mode 100644
index 000000000..f20e266bf
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IRepeatableIterator.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.Iterator;
+
+public interface IRepeatableIterator<T> extends Iterator<T> {
+ /**
+ * Returns a copy that will iterate over the same elements
+ * as this iterator. The contents or position of this iterator
+ * is left unchanged.
+ * @return A re-initialized copy of this iterator.
+ */
+ IRepeatableIterator<T> getCopy();
+
+ Object getIteratorProvider();
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPApproximation.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPApproximation.java
new file mode 100644
index 000000000..291a6ef6c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPApproximation.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.io.Serializable;
+
+/**
+ * Map a string for an LDAP APPROX (~=) comparison.
+ * This implementation removes white spaces and transforms everything to lower case.
+ */
+public final class LDAPApproximation implements Serializable, Comparable<LDAPApproximation> {
+ private static final long serialVersionUID = 4782295637798543587L;
+ private final String pattern;
+ private transient String approxPattern;
+
+ public LDAPApproximation(String pattern) {
+ this.pattern = pattern;
+ }
+
+ public int compareTo(LDAPApproximation o) {
+ return pattern.compareTo(o.pattern);
+ }
+
+ public boolean equals(Object o) {
+ return o == this || (o instanceof LDAPApproximation && ((LDAPApproximation) o).pattern.equals(pattern));
+ }
+
+ public int hashCode() {
+ return 3 * pattern.hashCode();
+ }
+
+ /**
+ * Matches the <code>value</code> with the compiled expression. The value
+ * is considered matching if all characters are matched by the expression. A
+ * partial match is not enough.
+ * @param value The value to match
+ * @return <code>true</code> if the value was a match.
+ */
+ public boolean isMatch(CharSequence value) {
+ if (value == null)
+ return false;
+ if (approxPattern == null)
+ approxPattern = approxString(pattern);
+ return approxString(value).equals(approxPattern);
+ }
+
+ public String toString() {
+ return pattern;
+ }
+
+ private static String approxString(CharSequence input) {
+ boolean changed = false;
+ char[] output = new char[input.length()];
+ int cursor = 0;
+ for (int i = 0, length = output.length; i < length; i++) {
+ char c = input.charAt(i);
+ if (Character.isWhitespace(c)) {
+ changed = true;
+ continue;
+ }
+ if (Character.isUpperCase(c)) {
+ changed = true;
+ c = Character.toLowerCase(c);
+ }
+ output[cursor++] = c;
+ }
+ return changed ? new String(output, 0, cursor) : input.toString();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPFilter.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPFilter.java
new file mode 100644
index 000000000..607334bea
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPFilter.java
@@ -0,0 +1,69 @@
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.Dictionary;
+import java.util.Map;
+import org.eclipse.equinox.p2.metadata.expression.*;
+import org.osgi.framework.Filter;
+import org.osgi.framework.ServiceReference;
+
+public class LDAPFilter extends Unary implements IFilterExpression {
+
+ LDAPFilter(Expression expression) {
+ super(expression);
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return operand.accept(visitor);
+ }
+
+ public boolean equals(Object o) {
+ return (o instanceof Filter && !(o instanceof LDAPFilter)) ? equals(ExpressionUtil.parseLDAP(o.toString())) : super.equals(o);
+ }
+
+ @Override
+ public String getOperator() {
+ return operand.getOperator();
+ }
+
+ @Override
+ public int getPriority() {
+ return operand.getPriority();
+ }
+
+ public int getExpressionType() {
+ return 0;
+ }
+
+ public boolean match(Map<String, ? extends Object> map) {
+ return isMatch(MemberProvider.create(map, true));
+ }
+
+ @SuppressWarnings("rawtypes")
+ public boolean match(Dictionary dictionary) {
+ return isMatch(dictionary == null ? MemberProvider.emptyProvider() : MemberProvider.create(dictionary, true));
+ }
+
+ private boolean isMatch(Object candidate) {
+ Variable self = ExpressionFactory.THIS;
+ IEvaluationContext ctx = EvaluationContext.create(self);
+ self.setValue(ctx, candidate);
+ return Boolean.TRUE == operand.evaluate(ctx);
+ }
+
+ public boolean match(ServiceReference reference) {
+ return isMatch(reference == null ? MemberProvider.emptyProvider() : MemberProvider.create(reference, true));
+ }
+
+ public boolean matchCase(Map<String, ? extends Object> map) {
+ return isMatch(map == null ? MemberProvider.emptyProvider() : MemberProvider.create(map, false));
+ }
+
+ @SuppressWarnings("rawtypes")
+ public boolean matchCase(Dictionary dictionary) {
+ return isMatch(dictionary == null ? MemberProvider.emptyProvider() : MemberProvider.create(dictionary, false));
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ operand.toLDAPString(bld);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LambdaExpression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LambdaExpression.java
new file mode 100644
index 000000000..c5bacf0ab
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LambdaExpression.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * A function that executes some code
+ */
+public class LambdaExpression extends Unary {
+ protected final Variable each;
+
+ protected LambdaExpression(Variable each, Expression body) {
+ super(body);
+ this.each = each;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return super.accept(visitor) && each.accept(visitor);
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = each.compareTo(((LambdaExpression) e).each);
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && each.equals(((LambdaExpression) o).each);
+ }
+
+ public int hashCode() {
+ int result = 31 + operand.hashCode();
+ return 31 * result + each.hashCode();
+ }
+
+ public int getExpressionType() {
+ return TYPE_LAMBDA;
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ each.toString(bld, rootVariable);
+ bld.append(" | "); //$NON-NLS-1$
+ appendOperand(bld, rootVariable, operand, IExpressionConstants.PRIORITY_COMMA);
+ }
+
+ public Variable getItemVariable() {
+ return each;
+ }
+
+ public String getOperator() {
+ return "|"; //$NON-NLS-1$
+ }
+
+ public int getPriority() {
+ return IExpressionConstants.PRIORITY_LAMBDA;
+ }
+
+ public IEvaluationContext prolog(IEvaluationContext context) {
+ return EvaluationContext.create(context, each);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Literal.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Literal.java
new file mode 100644
index 000000000..2884fb757
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Literal.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.Version;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.SimplePattern;
+import org.osgi.framework.Filter;
+
+/**
+ * An expression that represents a constant value.
+ */
+public final class Literal extends Expression {
+ public static final Literal FALSE_CONSTANT = new Literal(Boolean.FALSE);
+
+ public static final Literal NULL_CONSTANT = new Literal(null);
+
+ public static final Literal TRUE_CONSTANT = new Literal(Boolean.TRUE);
+
+ static Literal create(Object value) {
+ if (value == null)
+ return NULL_CONSTANT;
+ if (value == Boolean.TRUE)
+ return TRUE_CONSTANT;
+ if (value == Boolean.FALSE)
+ return FALSE_CONSTANT;
+ return new Literal(value);
+ }
+
+ public final Object value;
+
+ private Literal(Object value) {
+ this.value = value;
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp != 0)
+ return cmp;
+
+ Object eValue = ((Literal) e).value;
+ if (value == null)
+ return eValue == null ? 0 : -1;
+
+ if (eValue == null)
+ return 1;
+
+ if (eValue.getClass() == value.getClass())
+ return ((Comparable) value).compareTo(eValue);
+
+ return eValue.getClass().getName().compareTo(value.getClass().getName());
+ }
+
+ public boolean equals(Object o) {
+ if (super.equals(o)) {
+ Literal bo = (Literal) o;
+ return value == null ? bo.value == null : value.equals(bo.value);
+ }
+ return false;
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return value;
+ }
+
+ public int getExpressionType() {
+ return TYPE_LITERAL;
+ }
+
+ public String getOperator() {
+ return "<literal>"; //$NON-NLS-1$
+ }
+
+ public int getPriority() {
+ return PRIORITY_LITERAL;
+ }
+
+ public int hashCode() {
+ return 31 + value.hashCode();
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ if (!(value instanceof Filter))
+ throw new UnsupportedOperationException();
+ buf.append(value.toString());
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ if (value == null)
+ bld.append("null"); //$NON-NLS-1$
+ else if (value instanceof String || value instanceof Version) {
+ String str = value.toString();
+ char sep = str.indexOf('\'') >= 0 ? '"' : '\'';
+ bld.append(sep);
+ bld.append(str);
+ bld.append(sep);
+ } else if (value instanceof SimplePattern) {
+ appendEscaped(bld, '/', value.toString());
+ } else
+ bld.append(value);
+ }
+
+ private void appendEscaped(StringBuffer bld, char delimiter, String str) {
+ bld.append(delimiter);
+ int top = str.length();
+ for (int idx = 0; idx < top; ++idx) {
+ char c = str.charAt(idx);
+ if (c == delimiter)
+ bld.append('\\');
+ bld.append(c);
+ }
+ bld.append(delimiter);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MatchExpression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MatchExpression.java
new file mode 100644
index 000000000..efb636bb9
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MatchExpression.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.Arrays;
+import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils;
+import org.eclipse.equinox.p2.metadata.expression.*;
+
+/**
+ * The MatchExpression is a wrapper for an {@link IExpression} that is expected
+ * to return a boolean value. The wrapper provides the evaluation context needed
+ * to evaluate the expression.
+ */
+class MatchExpression<T> extends Unary implements IMatchExpression<T> {
+ private static final Object[] noParams = new Object[0];
+ private final Object[] parameters;
+
+ MatchExpression(Expression expression, Object[] parameters) {
+ super(expression);
+ this.parameters = parameters == null ? noParams : parameters;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return operand.accept(visitor);
+ }
+
+ public IEvaluationContext createContext() {
+ return EvaluationContext.create(parameters, ExpressionFactory.THIS);
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && Arrays.equals(parameters, ((MatchExpression<?>) o).parameters);
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return operand.evaluate(parameters.length == 0 ? context : EvaluationContext.create(context, parameters));
+ }
+
+ public int getExpressionType() {
+ return 0;
+ }
+
+ public String getOperator() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object[] getParameters() {
+ return parameters;
+ }
+
+ public int getPriority() {
+ return operand.getPriority();
+ }
+
+ public int hashCode() {
+ return operand.hashCode() * 31 + CollectionUtils.hashCode(parameters);
+ }
+
+ public boolean isMatch(IEvaluationContext context, T value) {
+ ExpressionFactory.THIS.setValue(context, value);
+ return Boolean.TRUE == operand.evaluate(context);
+ }
+
+ public boolean isMatch(T value) {
+ return isMatch(createContext(), value);
+ }
+
+ public void toLDAPString(StringBuffer bld) {
+ operand.toLDAPString(bld);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ operand.toString(bld, rootVariable);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Matches.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Matches.java
new file mode 100644
index 000000000..da041b398
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Matches.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.Version;
+import org.eclipse.equinox.p2.metadata.VersionRange;
+import org.eclipse.equinox.p2.metadata.expression.*;
+
+/**
+ * <p>A class that performs &quot;matching&quot; The actual algorithm used for
+ * performing the match varies depending on the types of the items to match.</p>
+ * <p>The following things can be matched:</p>
+ * <table border="1" cellpadding="3">
+ * <tr><th>LHS</th><th>RHS</th><th>Implemented as</th></tr>
+ * <tr><td>{@link String}</td><td>{@link SimplePattern}</td><td>rhs.isMatch(lhs)</td></tr>
+ * <tr><td>{@link String}</td><td>{@link LDAPApproximation}</td><td>rhs.isMatch(lhs)</td></tr>
+ * <tr><td>&lt;any&gt;</td><td>{@link Class}</td><td>rhs.isInstance(lhs)</td></tr>
+ * <tr><td>{@link Class}</td><td>{@link Class}</td><td>rhs.isAssignableFrom(lhs)</td></tr>
+ * </table>
+ */
+public class Matches extends Binary {
+ protected Matches(Expression lhs, Expression rhs) {
+ super(lhs, rhs);
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return Boolean.valueOf(match(lhs.evaluate(context), rhs.evaluate(context)));
+ }
+
+ protected boolean match(Object lval, Object rval) {
+ if (rval instanceof VersionRange) {
+ VersionRange range = (VersionRange) rval;
+ if (lval instanceof Version)
+ return range.isIncluded((Version) lval);
+ if (lval instanceof String)
+ return range.isIncluded(Version.create((String) lval));
+ }
+ if (rval instanceof SimplePattern) {
+ if (lval instanceof CharSequence)
+ return ((SimplePattern) rval).isMatch((CharSequence) lval);
+ if (lval instanceof Character || lval instanceof Number || lval instanceof Boolean)
+ return ((SimplePattern) rval).isMatch(lval.toString());
+
+ } else if (rval instanceof LDAPApproximation) {
+ if (lval instanceof CharSequence)
+ return ((LDAPApproximation) rval).isMatch((CharSequence) lval);
+ if (lval instanceof Character || lval instanceof Number || lval instanceof Boolean)
+ return ((LDAPApproximation) rval).isMatch(lval.toString());
+
+ } else if (rval instanceof Class<?>) {
+ Class<?> rclass = (Class<?>) rval;
+ return lval instanceof Class<?> ? rclass.isAssignableFrom((Class<?>) lval) : rclass.isInstance(lval);
+ }
+
+ if (lval == null || rval == null)
+ return false;
+
+ throw new IllegalArgumentException("Cannot match a " + lval.getClass().getName() + " with a " + rval.getClass().getName()); //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ public int getExpressionType() {
+ return TYPE_MATCHES;
+ }
+
+ public String getOperator() {
+ return OPERATOR_MATCHES;
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ if (!(rhs instanceof Literal))
+ throw new UnsupportedOperationException();
+
+ boolean escapeWild = true;
+ Object val = rhs.evaluate(null);
+ buf.append('(');
+ appendLDAPAttribute(buf);
+ if (val instanceof LDAPApproximation) {
+ buf.append(getOperator());
+ } else if (val instanceof SimplePattern) {
+ buf.append('=');
+ escapeWild = false;
+ } else
+ throw new UnsupportedOperationException();
+ appendLDAPEscaped(buf, val.toString(), escapeWild);
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java
new file mode 100644
index 000000000..5d442f62c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.lang.reflect.*;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * <p>An expression that performs member calls to obtain some value
+ * from some object instance. It uses standard bean semantics so
+ * that an attempt to obtain &quot;value&quot; will cause an
+ * attempt to call <code>getValue()</code> and if no such method
+ * exists, <code>isValue()</code> and if that doesn't work either,
+ * <code>value()</code>.</p>
+ */
+public abstract class Member extends Unary {
+
+ public static final class DynamicMember extends Member {
+ private static final String GET_PREFIX = "get"; //$NON-NLS-1$
+ private static final String IS_PREFIX = "is"; //$NON-NLS-1$
+ private static final Class<?>[] NO_ARG_TYPES = new Class[0];
+
+ private Class<?> lastClass;
+
+ private Method method;
+ private String methodName;
+
+ DynamicMember(Expression operand, String name) {
+ super(operand, name, Expression.emptyArray);
+ if (!(name.startsWith(GET_PREFIX) || name.startsWith(IS_PREFIX)))
+ name = GET_PREFIX + Character.toUpperCase(name.charAt(0)) + name.substring(1);
+ this.methodName = name;
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return invoke(operand.evaluate(context));
+ }
+
+ public Object invoke(Object self) {
+ if (self == null)
+ throw new IllegalArgumentException("Cannot access member \'" + name + "\' in null"); //$NON-NLS-1$//$NON-NLS-2$
+
+ if (self instanceof MemberProvider)
+ return ((MemberProvider) self).getMember(name);
+
+ Class<?> c = self.getClass();
+ if (lastClass == null || !lastClass.isAssignableFrom(c)) {
+ Method m;
+ for (;;) {
+ try {
+ m = c.getMethod(methodName, NO_ARG_TYPES);
+ if (!Modifier.isPublic(m.getModifiers()))
+ throw new NoSuchMethodException();
+ break;
+ } catch (NoSuchMethodException e) {
+ if (methodName.startsWith(GET_PREFIX))
+ // Switch from using getXxx() to isXxx()
+ methodName = IS_PREFIX + Character.toUpperCase(name.charAt(0)) + name.substring(1);
+ else if (methodName.startsWith(IS_PREFIX))
+ // Switch from using isXxx() to xxx()
+ methodName = name;
+ else
+ throw new IllegalArgumentException("Cannot find a public member \'" + name + "\' in a " + self.getClass().getName()); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ }
+
+ // Since we already checked that it's public. This will speed
+ // up the calls a bit.
+ m.setAccessible(true);
+ lastClass = c;
+ method = m;
+ }
+
+ Exception checked;
+ try {
+ return method.invoke(self, NO_ARGS);
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (IllegalAccessException e) {
+ checked = e;
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getTargetException();
+ if (cause instanceof RuntimeException)
+ throw (RuntimeException) cause;
+ if (cause instanceof Error)
+ throw (Error) cause;
+ checked = (Exception) cause;
+ }
+ throw new RuntimeException("Problem invoking " + methodName + " on a " + self.getClass().getName(), checked); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ static final Object[] NO_ARGS = new Object[0];
+
+ static Member createDynamicMember(Expression operand, String name) {
+ return new DynamicMember(operand, name);
+ }
+
+ protected final Expression[] argExpressions;
+
+ final String name;
+
+ protected Member(Expression operand, String name, Expression[] args) {
+ super(operand);
+ this.name = name;
+ this.argExpressions = args;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ if (super.accept(visitor))
+ for (int idx = 0; idx < argExpressions.length; ++idx)
+ if (!argExpressions[idx].accept(visitor))
+ return false;
+ return true;
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0) {
+ cmp = name.compareTo(((Member) e).name);
+ if (cmp == 0)
+ cmp = compare(argExpressions, ((Member) e).argExpressions);
+ }
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ if (super.equals(o)) {
+ Member mo = (Member) o;
+ return name.equals(mo.name) && equals(argExpressions, mo.argExpressions);
+ }
+ return false;
+ }
+
+ public int getExpressionType() {
+ return TYPE_MEMBER;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getOperator() {
+ return OPERATOR_MEMBER;
+ }
+
+ public int getPriority() {
+ return PRIORITY_MEMBER;
+ }
+
+ public int hashCode() {
+ int result = 31 + name.hashCode();
+ result = 31 * result + operand.hashCode();
+ return 31 * result + hashCode(argExpressions);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ if (operand != rootVariable) {
+ appendOperand(bld, rootVariable, operand, getPriority());
+ bld.append('.');
+ }
+ bld.append(name);
+ if (argExpressions.length > 0) {
+ bld.append('(');
+ elementsToString(bld, rootVariable, argExpressions);
+ bld.append(')');
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MemberProvider.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MemberProvider.java
new file mode 100644
index 000000000..9eb460a8d
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MemberProvider.java
@@ -0,0 +1,139 @@
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.*;
+import java.util.Map.Entry;
+import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils;
+import org.osgi.framework.ServiceReference;
+
+public abstract class MemberProvider {
+
+ static class DictionaryMemberProvider extends MemberProvider {
+ private final Dictionary<String, ? extends Object> dictionary;
+
+ public DictionaryMemberProvider(Dictionary<String, ? extends Object> dictionary) {
+ this.dictionary = dictionary;
+ }
+
+ @Override
+ public Object getMember(String memberName) {
+ return dictionary.get(memberName);
+ }
+ }
+
+ static class CIDictionaryMemberProvider extends DictionaryMemberProvider {
+ public CIDictionaryMemberProvider(Dictionary<String, ? extends Object> dictionary) {
+ super(lowerCaseKeys(dictionary));
+ }
+
+ @Override
+ public Object getMember(String memberName) {
+ return super.getMember(memberName == null ? null : memberName.toLowerCase());
+ }
+
+ private static Dictionary<String, ? extends Object> lowerCaseKeys(Dictionary<String, ? extends Object> dictionary) {
+ boolean hasUpperCase = false;
+ for (Enumeration<String> keys = dictionary.keys(); keys.hasMoreElements();) {
+ String key = keys.nextElement();
+ if (key.toLowerCase() != key) {
+ hasUpperCase = true;
+ break;
+ }
+ }
+ if (!hasUpperCase)
+ return dictionary;
+
+ Dictionary<String, Object> lcMap = new Hashtable<String, Object>(dictionary.size());
+ for (Enumeration<String> keys = dictionary.keys(); keys.hasMoreElements();) {
+ String key = keys.nextElement();
+ if (lcMap.put(key.toLowerCase(), dictionary.get(key)) != null)
+ throw new IllegalArgumentException("case variants of the same key name: '" + key + '\''); //$NON-NLS-1$
+ }
+ return lcMap;
+ }
+ }
+
+ static class MapMemberProvider extends MemberProvider {
+ private final Map<String, ? extends Object> map;
+
+ public MapMemberProvider(Map<String, ? extends Object> map) {
+ this.map = map;
+ }
+
+ @Override
+ public Object getMember(String memberName) {
+ return map.get(memberName);
+ }
+ }
+
+ static class CIMapMemberProvider extends MapMemberProvider {
+ public CIMapMemberProvider(Map<String, ? extends Object> map) {
+ super(lowerCaseKeys(map));
+ }
+
+ @Override
+ public Object getMember(String memberName) {
+ return super.getMember(memberName == null ? null : memberName.toLowerCase());
+ }
+
+ private static Map<String, ? extends Object> lowerCaseKeys(Map<String, ? extends Object> map) {
+ boolean hasUpperCase = false;
+ Set<? extends Entry<String, ? extends Object>> entrySet = map.entrySet();
+ for (Entry<String, ?> entry : entrySet) {
+ String key = entry.getKey();
+ String lowKey = key.toLowerCase();
+ if (key != lowKey) {
+ hasUpperCase = true;
+ break;
+ }
+ }
+ if (!hasUpperCase)
+ return map;
+
+ Map<String, Object> lcMap = new HashMap<String, Object>(map.size());
+ for (Entry<String, ?> entry : entrySet) {
+ if (lcMap.put(entry.getKey().toLowerCase(), entry.getValue()) != null)
+ throw new IllegalArgumentException("case variants of the same key name: '" + entry.getKey() + '\''); //$NON-NLS-1$
+ }
+ return lcMap;
+ }
+ }
+
+ static class ServiceRefMemberProvider extends MemberProvider {
+ private final ServiceReference serviceRef;
+
+ public ServiceRefMemberProvider(ServiceReference serviceRef) {
+ this.serviceRef = serviceRef;
+ }
+
+ @Override
+ public Object getMember(String memberName) {
+ return serviceRef.getProperty(memberName);
+ }
+ }
+
+ private static final MemberProvider emptyProvider = create(CollectionUtils.emptyMap(), false);
+
+ /**
+ * Create a new member provider on the given value. The value can be an instance of a {@link Map}, {@link Dictionary},
+ * or {@link ServiceReference}.
+ * @param value The value that provides the members
+ * @param caseInsensitive <code>true</code> if the members should be retrievable in a case insensitive way.
+ * @return A member provided that is backed by <code>value</code>.
+ */
+ @SuppressWarnings("unchecked")
+ public static MemberProvider create(Object value, boolean caseInsensitive) {
+ if (value instanceof Map<?, ?>)
+ return caseInsensitive ? new CIMapMemberProvider((Map<String, ?>) value) : new MapMemberProvider((Map<String, ?>) value);
+ if (value instanceof Dictionary<?, ?>)
+ return caseInsensitive ? new CIDictionaryMemberProvider((Dictionary<String, ?>) value) : new DictionaryMemberProvider((Dictionary<String, ?>) value);
+ if (value instanceof ServiceReference)
+ return new ServiceRefMemberProvider((ServiceReference) value);
+ throw new IllegalArgumentException();
+ }
+
+ public abstract Object getMember(String memberName);
+
+ public static MemberProvider emptyProvider() {
+ return emptyProvider;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/NAry.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/NAry.java
new file mode 100644
index 000000000..805cff2ab
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/NAry.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * The abstract baseclass for all N-ary expressions
+ */
+public abstract class NAry extends Expression {
+ public final Expression[] operands;
+
+ protected NAry(Expression[] operands) {
+ this.operands = operands;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ if (super.accept(visitor))
+ for (int idx = 0; idx < operands.length; ++idx)
+ if (!operands[idx].accept(visitor))
+ return false;
+ return true;
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = compare(operands, ((NAry) e).operands);
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && equals(operands, ((NAry) o).operands);
+ }
+
+ public abstract String getOperator();
+
+ public int hashCode() {
+ return hashCode(operands);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ appendOperand(bld, rootVariable, operands[0], getPriority());
+ for (int idx = 1; idx < operands.length; ++idx) {
+ bld.append(' ');
+ bld.append(getOperator());
+ bld.append(' ');
+ appendOperand(bld, rootVariable, operands[idx], getPriority());
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Not.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Not.java
new file mode 100644
index 000000000..5246859a3
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Not.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * An expression that yields <code>true</code> when its operand does not.
+ */
+final class Not extends Unary {
+ Not(Expression operand) {
+ super(operand);
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return Boolean.valueOf(operand.evaluate(context) != Boolean.TRUE);
+ }
+
+ public int getExpressionType() {
+ return TYPE_NOT;
+ }
+
+ public String getOperator() {
+ return OPERATOR_NOT;
+ }
+
+ public int getPriority() {
+ return PRIORITY_NOT;
+ }
+
+ public int hashCode() {
+ return 3 * operand.hashCode();
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ buf.append("(!"); //$NON-NLS-1$
+ operand.toLDAPString(buf);
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Or.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Or.java
new file mode 100644
index 000000000..32805042e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Or.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * n-ary OR operator. The full evaluation is <code>false</code> if none of its operands
+ * evaluate to <code>true</code>.
+ */
+final class Or extends NAry {
+ public Or(Expression[] operands) {
+ super(assertLength(operands, 2, OPERATOR_OR));
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ for (int idx = 0; idx < operands.length; ++idx) {
+ if (operands[idx].evaluate(context) == Boolean.TRUE)
+ return Boolean.TRUE;
+ }
+ return Boolean.FALSE;
+ }
+
+ public int getExpressionType() {
+ return TYPE_OR;
+ }
+
+ public String getOperator() {
+ return OPERATOR_OR;
+ }
+
+ public int getPriority() {
+ return PRIORITY_OR;
+ }
+
+ public void toLDAPString(StringBuffer buf) {
+ buf.append("(|"); //$NON-NLS-1$
+ for (int idx = 0; idx < operands.length; ++idx)
+ operands[idx].toLDAPString(buf);
+ buf.append(')');
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Parameter.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Parameter.java
new file mode 100644
index 000000000..b4c56346e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Parameter.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * The abstract base class for the indexed and keyed parameters
+ */
+public class Parameter extends Expression {
+ final int position;
+
+ Parameter(int position) {
+ this.position = position;
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = position - ((Parameter) e).position;
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+ if (o == null)
+ return false;
+ return getClass() == o.getClass() && position == ((Parameter) o).position;
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return context.getParameter(position);
+ }
+
+ public int getExpressionType() {
+ return TYPE_PARAMETER;
+ }
+
+ public String getOperator() {
+ return OPERATOR_PARAMETER;
+ }
+
+ public int getPriority() {
+ return PRIORITY_VARIABLE;
+ }
+
+ public int hashCode() {
+ return 31 * (1 + position);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ bld.append('$');
+ bld.append(position);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/RepeatableIterator.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/RepeatableIterator.java
new file mode 100644
index 000000000..e44a4e507
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/RepeatableIterator.java
@@ -0,0 +1,237 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.*;
+import org.eclipse.equinox.p2.query.IQueryResult;
+
+public class RepeatableIterator<T> implements IRepeatableIterator<T> {
+ private final List<T> values;
+ private int position = -1;
+
+ @SuppressWarnings("unchecked")
+ public static <T> IRepeatableIterator<T> create(Object unknown) {
+ if (unknown.getClass().isArray())
+ return create((T[]) unknown);
+ if (unknown instanceof Iterator<?>)
+ return create((Iterator<T>) unknown);
+ if (unknown instanceof List<?>)
+ return create((List<T>) unknown);
+ if (unknown instanceof Collection<?>)
+ return create((Collection<T>) unknown);
+ if (unknown instanceof Map<?, ?>)
+ return create((Set<T>) ((Map<?, ?>) unknown).entrySet());
+ if (unknown instanceof IQueryResult<?>)
+ return create((IQueryResult<T>) unknown);
+ throw new IllegalArgumentException("Cannot convert a " + unknown.getClass().getName() + " into an iterator"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ public static <T> IRepeatableIterator<T> create(Iterator<T> iterator) {
+ return iterator instanceof IRepeatableIterator<?> ? ((IRepeatableIterator<T>) iterator).getCopy() : new ElementRetainingIterator<T>(iterator);
+ }
+
+ public static <T> IRepeatableIterator<T> create(List<T> values) {
+ return new RepeatableIterator<T>(values);
+ }
+
+ public static <T> IRepeatableIterator<T> create(Collection<T> values) {
+ return new CollectionIterator<T>(values);
+ }
+
+ public static <T> IRepeatableIterator<T> create(IQueryResult<T> values) {
+ return new QueryResultIterator<T>(values);
+ }
+
+ public static <T> IRepeatableIterator<T> create(T[] values) {
+ return new ArrayIterator<T>(values);
+ }
+
+ RepeatableIterator(List<T> values) {
+ this.values = values;
+ }
+
+ public IRepeatableIterator<T> getCopy() {
+ return new RepeatableIterator<T>(values);
+ }
+
+ public boolean hasNext() {
+ return position + 1 < values.size();
+ }
+
+ public T next() {
+ if (++position == values.size()) {
+ --position;
+ throw new NoSuchElementException();
+ }
+ return values.get(position);
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object getIteratorProvider() {
+ return values;
+ }
+
+ void setPosition(int position) {
+ this.position = position;
+ }
+
+ List<T> getValues() {
+ return values;
+ }
+
+ static class ArrayIterator<T> implements IRepeatableIterator<T> {
+ private final T[] array;
+ private int position = -1;
+
+ public ArrayIterator(T[] array) {
+ this.array = array;
+ }
+
+ public Object getIteratorProvider() {
+ return array;
+ }
+
+ public boolean hasNext() {
+ return position + 1 < array.length;
+ }
+
+ public T next() {
+ if (++position >= array.length)
+ throw new NoSuchElementException();
+ return array[position];
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public IRepeatableIterator<T> getCopy() {
+ return new ArrayIterator<T>(array);
+ }
+ }
+
+ static class CollectionIterator<T> implements IRepeatableIterator<T> {
+ private final Collection<T> collection;
+
+ private final Iterator<T> iterator;
+
+ CollectionIterator(Collection<T> collection) {
+ this.collection = collection;
+ this.iterator = collection.iterator();
+ }
+
+ public IRepeatableIterator<T> getCopy() {
+ return new CollectionIterator<T>(collection);
+ }
+
+ public Object getIteratorProvider() {
+ return collection;
+ }
+
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ public T next() {
+ return iterator.next();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ static class QueryResultIterator<T> implements IRepeatableIterator<T> {
+ private final IQueryResult<T> queryResult;
+
+ private final Iterator<T> iterator;
+
+ QueryResultIterator(IQueryResult<T> queryResult) {
+ this.queryResult = queryResult;
+ this.iterator = queryResult.iterator();
+ }
+
+ public IRepeatableIterator<T> getCopy() {
+ return new QueryResultIterator<T>(queryResult);
+ }
+
+ public Object getIteratorProvider() {
+ return queryResult;
+ }
+
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ public T next() {
+ return iterator.next();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ static class ElementRetainingIterator<T> extends RepeatableIterator<T> {
+
+ private Iterator<T> innerIterator;
+
+ ElementRetainingIterator(Iterator<T> iterator) {
+ super(new ArrayList<T>());
+ innerIterator = iterator;
+ }
+
+ public synchronized boolean hasNext() {
+ if (innerIterator != null) {
+ if (innerIterator.hasNext())
+ return true;
+ innerIterator = null;
+ setPosition(getValues().size());
+ }
+ return super.hasNext();
+ }
+
+ public synchronized T next() {
+ if (innerIterator != null) {
+ T val = innerIterator.next();
+ getValues().add(val);
+ return val;
+ }
+ return super.next();
+ }
+
+ public synchronized IRepeatableIterator<T> getCopy() {
+ // If the current iterator still exists, we must exhaust it first
+ //
+ exhaustInnerIterator();
+ return super.getCopy();
+ }
+
+ public synchronized Object getIteratorProvider() {
+ exhaustInnerIterator();
+ return super.getIteratorProvider();
+ }
+
+ private void exhaustInnerIterator() {
+ if (innerIterator != null) {
+ List<T> values = getValues();
+ int savePos = values.size() - 1;
+ while (innerIterator.hasNext())
+ values.add(innerIterator.next());
+ innerIterator = null;
+ setPosition(savePos);
+ }
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Unary.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Unary.java
new file mode 100644
index 000000000..9c6b7d8a5
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Unary.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor;
+
+/**
+ * The abstract base class for all unary expressions
+ */
+public abstract class Unary extends Expression {
+ public final Expression operand;
+
+ protected Unary(Expression operand) {
+ this.operand = operand;
+ }
+
+ public boolean accept(IExpressionVisitor visitor) {
+ return super.accept(visitor) && operand.accept(visitor);
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = operand.compareTo(((Unary) e).operand);
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && operand.equals(((Unary) o).operand);
+ }
+
+ public Object evaluate(IEvaluationContext context) {
+ return operand.evaluate(context);
+ }
+
+ public int hashCode() {
+ return operand.hashCode() * 3 + operand.getExpressionType();
+ }
+
+ public Expression getOperand() {
+ return operand;
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ bld.append(getOperator());
+ appendOperand(bld, rootVariable, operand, getPriority());
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Variable.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Variable.java
new file mode 100644
index 000000000..a7adfb738
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Variable.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression;
+
+import java.util.Iterator;
+import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
+
+/**
+ * An expression representing a variable stack in the current thread.
+ */
+public class Variable extends Expression {
+
+ private final String name;
+
+ public Variable(String name) {
+ this.name = name;
+ }
+
+ public int compareTo(Expression e) {
+ int cmp = super.compareTo(e);
+ if (cmp == 0)
+ cmp = name.compareTo(((Variable) e).name);
+ return cmp;
+ }
+
+ public boolean equals(Object o) {
+ return super.equals(o) && name.equals(((Variable) o).name);
+ }
+
+ public final Object evaluate(IEvaluationContext context) {
+ return context.getValue(this);
+ }
+
+ public Iterator<?> evaluateAsIterator(IEvaluationContext context) {
+ Object value = context.getValue(this);
+ if (value instanceof IRepeatableIterator<?>)
+ return ((IRepeatableIterator<?>) value).getCopy();
+
+ Iterator<?> itor = RepeatableIterator.create(value);
+ setValue(context, itor);
+ return itor;
+ }
+
+ public int getExpressionType() {
+ return TYPE_VARIABLE;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getOperator() {
+ return "<variable>"; //$NON-NLS-1$
+ }
+
+ public int getPriority() {
+ return PRIORITY_VARIABLE;
+ }
+
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ public final void setValue(IEvaluationContext context, Object value) {
+ context.setValue(this, value);
+ }
+
+ public void toString(StringBuffer bld, Variable rootVariable) {
+ bld.append(name);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/ExpressionParser.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/ExpressionParser.java
new file mode 100644
index 000000000..905d0927c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/ExpressionParser.java
@@ -0,0 +1,617 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression.parser;
+
+import org.eclipse.equinox.internal.p2.metadata.expression.LDAPApproximation;
+
+import java.util.*;
+import org.eclipse.equinox.internal.p2.metadata.expression.IExpressionConstants;
+import org.eclipse.equinox.p2.metadata.expression.*;
+
+public class ExpressionParser extends Stack<IExpression> implements IExpressionConstants, IExpressionParser {
+ private static final long serialVersionUID = 5481439062356612378L;
+
+ protected static final int TOKEN_OR = 1;
+ protected static final int TOKEN_AND = 2;
+
+ protected static final int TOKEN_EQUAL = 10;
+ protected static final int TOKEN_NOT_EQUAL = 11;
+ protected static final int TOKEN_LESS = 12;
+ protected static final int TOKEN_LESS_EQUAL = 13;
+ protected static final int TOKEN_GREATER = 14;
+ protected static final int TOKEN_GREATER_EQUAL = 15;
+ protected static final int TOKEN_MATCHES = 16;
+
+ protected static final int TOKEN_NOT = 20;
+ protected static final int TOKEN_DOT = 21;
+ protected static final int TOKEN_COMMA = 22;
+ protected static final int TOKEN_PIPE = 23;
+ protected static final int TOKEN_DOLLAR = 24;
+ protected static final int TOKEN_IF = 25;
+ protected static final int TOKEN_ELSE = 26;
+
+ protected static final int TOKEN_LP = 30;
+ protected static final int TOKEN_RP = 31;
+ protected static final int TOKEN_LB = 32;
+ protected static final int TOKEN_RB = 33;
+ protected static final int TOKEN_LC = 34;
+ protected static final int TOKEN_RC = 35;
+
+ protected static final int TOKEN_IDENTIFIER = 40;
+ protected static final int TOKEN_LITERAL = 41;
+
+ protected static final int TOKEN_NULL = 50;
+ protected static final int TOKEN_TRUE = 51;
+ protected static final int TOKEN_FALSE = 52;
+
+ private static final int TOKEN_ALL = 60;
+ private static final int TOKEN_EXISTS = 61;
+
+ protected static final int TOKEN_END = 0;
+ protected static final int TOKEN_ERROR = -1;
+
+ protected static final Map<String, Integer> keywords;
+ static {
+ keywords = new HashMap<String, Integer>();
+ keywords.put(KEYWORD_FALSE, new Integer(TOKEN_FALSE));
+ keywords.put(KEYWORD_NULL, new Integer(TOKEN_NULL));
+ keywords.put(KEYWORD_TRUE, new Integer(TOKEN_TRUE));
+ keywords.put(KEYWORD_ALL, new Integer(TOKEN_ALL));
+ keywords.put(KEYWORD_EXISTS, new Integer(TOKEN_EXISTS));
+ }
+
+ protected final IExpressionFactory factory;
+
+ protected String expression;
+ protected int tokenPos;
+ protected int currentToken;
+ protected int lastTokenPos;
+ protected Object tokenValue;
+ protected String rootVariable;
+
+ public ExpressionParser(IExpressionFactory factory) {
+ this.factory = factory;
+ }
+
+ public synchronized IExpression parse(String exprString) {
+ expression = exprString;
+ tokenPos = 0;
+ currentToken = 0;
+ tokenValue = null;
+ IExpression thisVariable = factory.thisVariable();
+ rootVariable = ExpressionUtil.getName(thisVariable);
+ push(thisVariable);
+ try {
+ nextToken();
+ IExpression expr = currentToken == TOKEN_END ? factory.constant(Boolean.TRUE) : parseCondition();
+ assertToken(TOKEN_END);
+ return expr;
+ } finally {
+ if (thisVariable != null)
+ popVariable(); // pop item
+ }
+ }
+
+ protected Map<String, Integer> keywordToTokenMap() {
+ return keywords;
+ }
+
+ protected IExpression parseCondition() {
+ // Just a hook in this parser. Conditions are not supported
+ return parseOr();
+ }
+
+ protected IExpression parseOr() {
+ IExpression expr = parseAnd();
+ if (currentToken != TOKEN_OR)
+ return expr;
+
+ ArrayList<IExpression> exprs = new ArrayList<IExpression>();
+ exprs.add(expr);
+ do {
+ nextToken();
+ exprs.add(parseAnd());
+ } while (currentToken == TOKEN_OR);
+ return factory.or(exprs.toArray(new IExpression[exprs.size()]));
+ }
+
+ protected IExpression parseAnd() {
+ IExpression expr = parseBinary();
+ if (currentToken != TOKEN_AND)
+ return expr;
+
+ ArrayList<IExpression> exprs = new ArrayList<IExpression>();
+ exprs.add(expr);
+ do {
+ nextToken();
+ exprs.add(parseBinary());
+ } while (currentToken == TOKEN_AND);
+ return factory.and(exprs.toArray(new IExpression[exprs.size()]));
+ }
+
+ protected IExpression parseBinary() {
+ IExpression expr = parseNot();
+ for (;;) {
+ switch (currentToken) {
+ case TOKEN_OR :
+ case TOKEN_AND :
+ case TOKEN_RP :
+ case TOKEN_RB :
+ case TOKEN_RC :
+ case TOKEN_COMMA :
+ case TOKEN_IF :
+ case TOKEN_ELSE :
+ case TOKEN_END :
+ break;
+ case TOKEN_EQUAL :
+ case TOKEN_NOT_EQUAL :
+ case TOKEN_GREATER :
+ case TOKEN_GREATER_EQUAL :
+ case TOKEN_LESS :
+ case TOKEN_LESS_EQUAL :
+ case TOKEN_MATCHES :
+ int realToken = currentToken;
+ nextToken();
+ IExpression rhs;
+ if (realToken == TOKEN_MATCHES && currentToken == TOKEN_LITERAL && tokenValue instanceof String)
+ rhs = factory.constant(new LDAPApproximation((String) tokenValue));
+ else
+ rhs = parseNot();
+ switch (realToken) {
+ case TOKEN_EQUAL :
+ expr = factory.equals(expr, rhs);
+ break;
+ case TOKEN_NOT_EQUAL :
+ expr = factory.not(factory.equals(expr, rhs));
+ break;
+ case TOKEN_GREATER :
+ expr = factory.greater(expr, rhs);
+ break;
+ case TOKEN_GREATER_EQUAL :
+ expr = factory.greaterEqual(expr, rhs);
+ break;
+ case TOKEN_LESS :
+ expr = factory.less(expr, rhs);
+ break;
+ case TOKEN_LESS_EQUAL :
+ expr = factory.lessEqual(expr, rhs);
+ break;
+ default :
+ expr = factory.matches(expr, rhs);
+ }
+ continue;
+ default :
+ throw syntaxError();
+ }
+ break;
+ }
+ return expr;
+ }
+
+ protected IExpression parseNot() {
+ if (currentToken == TOKEN_NOT) {
+ nextToken();
+ IExpression expr = parseNot();
+ return factory.not(expr);
+ }
+ return parseCollectionExpression();
+ }
+
+ protected IExpression parseCollectionExpression() {
+ IExpression expr = parseCollectionLHS();
+ if (expr == null) {
+ expr = parseMember();
+ if (currentToken != TOKEN_DOT)
+ return expr;
+ nextToken();
+ }
+ for (;;) {
+ int funcToken = currentToken;
+ nextToken();
+ assertToken(TOKEN_LP);
+ nextToken();
+ expr = parseCollectionRHS(expr, funcToken);
+ if (currentToken != TOKEN_DOT)
+ break;
+ nextToken();
+ }
+ return expr;
+ }
+
+ protected IExpression parseCollectionLHS() {
+ IExpression expr = null;
+ switch (currentToken) {
+ case TOKEN_EXISTS :
+ case TOKEN_ALL :
+ expr = getVariableOrRootMember(rootVariable);
+ break;
+ }
+ return expr;
+ }
+
+ protected IExpression parseCollectionRHS(IExpression expr, int funcToken) {
+ switch (funcToken) {
+ case TOKEN_EXISTS :
+ expr = factory.exists(expr, parseLambdaDefinition());
+ break;
+ case TOKEN_ALL :
+ expr = factory.all(expr, parseLambdaDefinition());
+ break;
+ default :
+ throw syntaxError();
+ }
+ return expr;
+ }
+
+ protected IExpression parseLambdaDefinition() {
+ assertToken(TOKEN_IDENTIFIER);
+ IExpression each = factory.variable((String) tokenValue);
+ push(each);
+ try {
+ nextToken();
+ assertToken(TOKEN_PIPE);
+ nextToken();
+ IExpression body = parseCondition();
+ assertToken(TOKEN_RP);
+ nextToken();
+ return factory.lambda(each, body);
+ } finally {
+ pop();
+ }
+ }
+
+ protected IExpression parseMember() {
+ IExpression expr = parseUnary();
+ String name;
+ while (currentToken == TOKEN_DOT || currentToken == TOKEN_LB) {
+ int savePos = tokenPos;
+ int saveToken = currentToken;
+ Object saveTokenValue = tokenValue;
+ nextToken();
+ if (saveToken == TOKEN_DOT) {
+ switch (currentToken) {
+ case TOKEN_IDENTIFIER :
+ name = (String) tokenValue;
+ nextToken();
+ expr = factory.member(expr, name);
+ break;
+
+ default :
+ tokenPos = savePos;
+ currentToken = saveToken;
+ tokenValue = saveTokenValue;
+ return expr;
+ }
+ } else {
+ IExpression atExpr = parseMember();
+ assertToken(TOKEN_RB);
+ nextToken();
+ expr = factory.at(expr, atExpr);
+ }
+ }
+ return expr;
+ }
+
+ protected IExpression parseUnary() {
+ IExpression expr;
+ switch (currentToken) {
+ case TOKEN_LP :
+ nextToken();
+ expr = parseCondition();
+ assertToken(TOKEN_RP);
+ nextToken();
+ break;
+ case TOKEN_LITERAL :
+ expr = factory.constant(tokenValue);
+ nextToken();
+ break;
+ case TOKEN_IDENTIFIER :
+ expr = getVariableOrRootMember((String) tokenValue);
+ nextToken();
+ break;
+ case TOKEN_NULL :
+ expr = factory.constant(null);
+ nextToken();
+ break;
+ case TOKEN_TRUE :
+ expr = factory.constant(Boolean.TRUE);
+ nextToken();
+ break;
+ case TOKEN_FALSE :
+ expr = factory.constant(Boolean.FALSE);
+ nextToken();
+ break;
+ case TOKEN_DOLLAR :
+ expr = parseParameter();
+ break;
+ default :
+ throw syntaxError();
+ }
+ return expr;
+ }
+
+ private IExpression parseParameter() {
+ if (currentToken == TOKEN_DOLLAR) {
+ nextToken();
+ if (currentToken == TOKEN_LITERAL && tokenValue instanceof Integer) {
+ IExpression param = factory.indexedParameter(((Integer) tokenValue).intValue());
+ nextToken();
+ return param;
+ }
+ }
+ throw syntaxError();
+ }
+
+ protected IExpression[] parseArray() {
+ IExpression expr = parseCondition();
+ if (currentToken != TOKEN_COMMA)
+ return new IExpression[] {expr};
+
+ ArrayList<IExpression> operands = new ArrayList<IExpression>();
+ operands.add(expr);
+ do {
+ nextToken();
+ if (currentToken == TOKEN_LC)
+ // We don't allow lambdas in the array
+ break;
+ operands.add(parseCondition());
+ } while (currentToken == TOKEN_COMMA);
+ return operands.toArray(new IExpression[operands.size()]);
+ }
+
+ protected void assertToken(int token) {
+ if (currentToken != token)
+ throw syntaxError();
+ }
+
+ protected IExpression getVariableOrRootMember(String id) {
+ int idx = size();
+ while (--idx >= 0) {
+ IExpression v = get(idx);
+ if (id.equals(v.toString()))
+ return v;
+ }
+
+ if (rootVariable == null || rootVariable.equals(id))
+ throw syntaxError("No such variable: " + id); //$NON-NLS-1$
+
+ return factory.member(getVariableOrRootMember(rootVariable), id);
+ }
+
+ protected void nextToken() {
+ tokenValue = null;
+ int top = expression.length();
+ char c = 0;
+ while (tokenPos < top) {
+ c = expression.charAt(tokenPos);
+ if (!Character.isWhitespace(c))
+ break;
+ ++tokenPos;
+ }
+ if (tokenPos >= top) {
+ lastTokenPos = top;
+ currentToken = TOKEN_END;
+ return;
+ }
+
+ lastTokenPos = tokenPos;
+ switch (c) {
+ case '|' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '|') {
+ tokenValue = OPERATOR_OR;
+ currentToken = TOKEN_OR;
+ tokenPos += 2;
+ } else {
+ currentToken = TOKEN_PIPE;
+ ++tokenPos;
+ }
+ break;
+
+ case '&' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '&') {
+ tokenValue = OPERATOR_AND;
+ currentToken = TOKEN_AND;
+ tokenPos += 2;
+ } else
+ currentToken = TOKEN_ERROR;
+ break;
+
+ case '=' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
+ tokenValue = OPERATOR_EQUALS;
+ currentToken = TOKEN_EQUAL;
+ tokenPos += 2;
+ } else
+ currentToken = TOKEN_ERROR;
+ break;
+
+ case '!' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
+ tokenValue = OPERATOR_NOT_EQUALS;
+ currentToken = TOKEN_NOT_EQUAL;
+ tokenPos += 2;
+ } else {
+ currentToken = TOKEN_NOT;
+ ++tokenPos;
+ }
+ break;
+
+ case '~' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
+ tokenValue = OPERATOR_MATCHES;
+ currentToken = TOKEN_MATCHES;
+ tokenPos += 2;
+ } else
+ currentToken = TOKEN_ERROR;
+ break;
+
+ case '>' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
+ tokenValue = OPERATOR_GT_EQUAL;
+ currentToken = TOKEN_GREATER_EQUAL;
+ tokenPos += 2;
+ } else {
+ currentToken = TOKEN_GREATER;
+ ++tokenPos;
+ }
+ break;
+
+ case '<' :
+ if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
+ tokenValue = OPERATOR_LT_EQUAL;
+ currentToken = TOKEN_LESS_EQUAL;
+ tokenPos += 2;
+ } else {
+ currentToken = TOKEN_LESS;
+ ++tokenPos;
+ }
+ break;
+
+ case '?' :
+ currentToken = TOKEN_IF;
+ ++tokenPos;
+ break;
+
+ case ':' :
+ currentToken = TOKEN_ELSE;
+ ++tokenPos;
+ break;
+
+ case '.' :
+ currentToken = TOKEN_DOT;
+ ++tokenPos;
+ break;
+
+ case '$' :
+ currentToken = TOKEN_DOLLAR;
+ ++tokenPos;
+ break;
+
+ case '{' :
+ currentToken = TOKEN_LC;
+ ++tokenPos;
+ break;
+
+ case '}' :
+ currentToken = TOKEN_RC;
+ ++tokenPos;
+ break;
+
+ case '(' :
+ currentToken = TOKEN_LP;
+ ++tokenPos;
+ break;
+
+ case ')' :
+ currentToken = TOKEN_RP;
+ ++tokenPos;
+ break;
+
+ case '[' :
+ currentToken = TOKEN_LB;
+ ++tokenPos;
+ break;
+
+ case ']' :
+ currentToken = TOKEN_RB;
+ ++tokenPos;
+ break;
+
+ case ',' :
+ currentToken = TOKEN_COMMA;
+ ++tokenPos;
+ break;
+
+ case '"' :
+ case '\'' : {
+ int start = ++tokenPos;
+ while (tokenPos < top && expression.charAt(tokenPos) != c)
+ ++tokenPos;
+ if (tokenPos == top) {
+ tokenPos = start - 1;
+ currentToken = TOKEN_ERROR;
+ } else {
+ tokenValue = expression.substring(start, tokenPos++);
+ currentToken = TOKEN_LITERAL;
+ }
+ break;
+ }
+
+ case '/' : {
+ int start = ++tokenPos;
+ StringBuffer buf = new StringBuffer();
+ while (tokenPos < top) {
+ c = expression.charAt(tokenPos);
+ if (c == '\\' && tokenPos + 1 < top) {
+ c = expression.charAt(++tokenPos);
+ if (c != '/')
+ buf.append('\\');
+ } else if (c == '/')
+ break;
+ buf.append(c);
+ ++tokenPos;
+ }
+ if (tokenPos == top) {
+ tokenPos = start - 1;
+ currentToken = TOKEN_ERROR;
+ } else {
+ tokenValue = SimplePattern.compile(expression.substring(start, tokenPos++));
+ currentToken = TOKEN_LITERAL;
+ }
+ break;
+ }
+
+ default :
+ if (Character.isDigit(c)) {
+ int start = tokenPos++;
+ while (tokenPos < top && Character.isDigit(expression.charAt(tokenPos)))
+ ++tokenPos;
+ tokenValue = Integer.valueOf(expression.substring(start, tokenPos));
+ currentToken = TOKEN_LITERAL;
+ break;
+ }
+ if (Character.isJavaIdentifierStart(c)) {
+ int start = tokenPos++;
+ while (tokenPos < top && Character.isJavaIdentifierPart(expression.charAt(tokenPos)))
+ ++tokenPos;
+ String word = expression.substring(start, tokenPos);
+ Integer token = keywordToTokenMap().get(word);
+ if (token == null)
+ currentToken = TOKEN_IDENTIFIER;
+ else
+ currentToken = token.intValue();
+ tokenValue = word;
+ break;
+ }
+ throw syntaxError();
+ }
+ }
+
+ protected void popVariable() {
+ if (isEmpty())
+ throw syntaxError();
+ pop();
+ }
+
+ protected ExpressionParseException syntaxError() {
+ Object tv = tokenValue;
+ if (tv == null) {
+ if (lastTokenPos >= expression.length())
+ return syntaxError("Unexpeced end of expression"); //$NON-NLS-1$
+ tv = expression.substring(lastTokenPos, lastTokenPos + 1);
+ }
+ return syntaxError("Unexpected token \"" + tv + '"'); //$NON-NLS-1$
+ }
+
+ protected ExpressionParseException syntaxError(String message) {
+ return new ExpressionParseException(expression, message, tokenPos);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/LDAPFilterParser.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/LDAPFilterParser.java
new file mode 100644
index 000000000..b5fd6714c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/LDAPFilterParser.java
@@ -0,0 +1,268 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Cloudsmith Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.expression.parser;
+
+import java.util.ArrayList;
+import org.eclipse.equinox.internal.p2.metadata.Messages;
+import org.eclipse.equinox.internal.p2.metadata.expression.IExpressionConstants;
+import org.eclipse.equinox.internal.p2.metadata.expression.LDAPApproximation;
+import org.eclipse.equinox.p2.metadata.expression.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Parser class for OSGi filter strings. This class parses the complete filter string and builds a tree of Filter
+ * objects rooted at the parent.
+ */
+public class LDAPFilterParser {
+ private final IExpressionFactory factory;
+
+ private final IExpression self;
+
+ private final StringBuffer sb = new StringBuffer();
+
+ private String filterString;
+
+ private int position;
+
+ public LDAPFilterParser(IExpressionFactory factory) {
+ this.factory = factory;
+ self = factory.variable(IExpressionConstants.VARIABLE_THIS);
+ position = 0;
+ }
+
+ public synchronized IFilterExpression parse(String filter) {
+ filterString = filter;
+ position = 0;
+ try {
+ IExpression expr = parseFilter();
+ if (position != filterString.length())
+ throw syntaxException(Messages.filter_trailing_characters);
+ return factory.filterExpression(expr);
+ } catch (StringIndexOutOfBoundsException e) {
+ throw syntaxException(Messages.filter_premature_end);
+ }
+ }
+
+ private IExpression parseAnd() {
+ skipWhiteSpace();
+ char c = filterString.charAt(position);
+ if (c != '(')
+ throw syntaxException(Messages.filter_missing_leftparen);
+
+ ArrayList<IExpression> operands = new ArrayList<IExpression>();
+ while (c == '(') {
+ IExpression child = parseFilter();
+ if (!operands.contains(child))
+ operands.add(child);
+ c = filterString.charAt(position);
+ }
+ // int sz = operands.size();
+ // return sz == 1 ? operands.get(0) : factory.and(operands.toArray(new IExpression[sz]));
+ return factory.normalize(operands, IExpression.TYPE_AND);
+ }
+
+ private IExpression parseAttr() {
+ skipWhiteSpace();
+
+ int begin = position;
+ int end = position;
+
+ char c = filterString.charAt(begin);
+ while (!(c == '~' || c == '<' || c == '>' || c == '=' || c == '(' || c == ')')) {
+ position++;
+ if (!Character.isWhitespace(c))
+ end = position;
+ c = filterString.charAt(position);
+ }
+ if (end == begin)
+ throw syntaxException(Messages.filter_missing_attr);
+ return factory.member(self, filterString.substring(begin, end));
+ }
+
+ private IExpression parseFilter() {
+ IExpression filter;
+ skipWhiteSpace();
+
+ if (filterString.charAt(position) != '(')
+ throw syntaxException(Messages.filter_missing_leftparen);
+
+ position++;
+ filter = parseFiltercomp();
+
+ skipWhiteSpace();
+
+ if (filterString.charAt(position) != ')')
+ throw syntaxException(Messages.filter_missing_rightparen);
+
+ position++;
+ skipWhiteSpace();
+
+ return filter;
+ }
+
+ private IExpression parseFiltercomp() {
+ skipWhiteSpace();
+
+ char c = filterString.charAt(position);
+
+ switch (c) {
+ case '&' : {
+ position++;
+ return parseAnd();
+ }
+ case '|' : {
+ position++;
+ return parseOr();
+ }
+ case '!' : {
+ position++;
+ return parseNot();
+ }
+ }
+ return parseItem();
+ }
+
+ private IExpression parseItem() {
+ IExpression attr = parseAttr();
+
+ skipWhiteSpace();
+ String value;
+
+ boolean[] hasWild = {false};
+ char c = filterString.charAt(position);
+ switch (c) {
+ case '~' :
+ case '>' :
+ case '<' :
+ if (filterString.charAt(position + 1) != '=')
+ throw syntaxException(Messages.filter_invalid_operator);
+ position += 2;
+ int savePos = position;
+ value = parseValue(hasWild);
+ if (hasWild[0]) {
+ // Unescaped wildcard found. This is not legal for the given operator
+ position = savePos;
+ throw syntaxException(Messages.filter_invalid_value);
+ }
+ switch (c) {
+ case '>' :
+ return factory.greaterEqual(attr, factory.constant(value));
+ case '<' :
+ return factory.lessEqual(attr, factory.constant(value));
+ }
+ return factory.matches(attr, factory.constant(new LDAPApproximation(value)));
+ case '=' :
+ position++;
+ value = parseValue(hasWild);
+ return hasWild[0] ? factory.matches(attr, factory.constant(SimplePattern.compile(value))) : factory.equals(attr, factory.constant(value));
+ }
+ throw syntaxException(Messages.filter_invalid_operator);
+ }
+
+ private IExpression parseNot() {
+ skipWhiteSpace();
+
+ if (filterString.charAt(position) != '(')
+ throw syntaxException(Messages.filter_missing_leftparen);
+ return factory.not(parseFilter());
+ }
+
+ private IExpression parseOr() {
+ skipWhiteSpace();
+ char c = filterString.charAt(position);
+ if (c != '(')
+ throw syntaxException(Messages.filter_missing_leftparen);
+
+ ArrayList<IExpression> operands = new ArrayList<IExpression>();
+ while (c == '(') {
+ IExpression child = parseFilter();
+ operands.add(child);
+ c = filterString.charAt(position);
+ }
+ // int sz = operands.size();
+ // return sz == 1 ? operands.get(0) : factory.or(operands.toArray(new IExpression[sz]));
+ return factory.normalize(operands, IExpression.TYPE_OR);
+ }
+
+ private static int hexValue(char c) {
+ int v;
+ if (c <= '9')
+ v = c - '0';
+ else if (c <= 'F')
+ v = (c - 'A') + 10;
+ else
+ v = (c - 'a') + 10;
+ return v;
+ }
+
+ private String parseValue(boolean[] hasWildBin) {
+ sb.setLength(0);
+ int savePos = position;
+ boolean hasEscapedWild = false;
+ parseloop: while (true) {
+ char c = filterString.charAt(position);
+ switch (c) {
+ case '*' :
+ if (hasEscapedWild && !hasWildBin[0]) {
+ // We must redo the parse.
+ position = savePos;
+ hasWildBin[0] = true;
+ return parseValue(hasWildBin);
+ }
+ hasWildBin[0] = true;
+ sb.append(c);
+ position++;
+ break;
+
+ case ')' :
+ break parseloop;
+
+ case '(' :
+ throw syntaxException(Messages.filter_invalid_value);
+
+ case '\\' :
+ c = filterString.charAt(++position);
+ if (c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f' && position + 1 < filterString.length()) {
+ char nc = filterString.charAt(position + 1);
+ if (nc >= '0' && nc <= '9' || nc >= 'A' && nc <= 'F' || nc >= 'a' && nc <= 'f') {
+ // Assume proper \xx escape where xx are hex digits
+ ++position;
+ c = (char) (((hexValue(c) << 4) & 0xf0) | (hexValue(nc) & 0x0f));
+ if (c == '*' && hasWildBin != null) {
+ hasEscapedWild = true;
+ if (hasWildBin[0])
+ sb.append('\\');
+ }
+ }
+ }
+ /* fall through into default */
+
+ default :
+ sb.append(c);
+ position++;
+ break;
+ }
+ }
+ if (sb.length() == 0)
+ throw syntaxException(Messages.filter_missing_value);
+ return sb.toString();
+ }
+
+ private void skipWhiteSpace() {
+ for (int top = filterString.length(); position < top; ++position)
+ if (!Character.isWhitespace(filterString.charAt(position)))
+ break;
+ }
+
+ protected ExpressionParseException syntaxException(String message) {
+ return new ExpressionParseException(NLS.bind(message, filterString, Integer.toString(position)));
+ }
+}
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 2d675ff07..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
@@ -8,8 +8,6 @@
# Contributors:
# Cloudsmith Inc. - initial API and implementation
###############################################################################
-_0_is_not_a_positive_integer_in_osgi_1=The {0} value is not a positive in OSGi version: "{1}"
-_0_is_not_a_string_in_osgi_1=The {0} value is not a string in OSGi version: "{1}"
_0_is_not_a_valid_qualifier_in_osgi_1=The {0} value is invalid in OSGi version: "{1}"
array_can_not_be_empty=Array format can not be empty
array_can_not_have_character_group=Array format can not have a character group
@@ -27,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
@@ -34,16 +40,15 @@ format_must_be_delimited_by_colon_0=Format must be delimited by version range: {
group_can_not_be_empty=A group can not be empty
ignore_defined_more_then_once=More then one definition of ignore
illegal_character_encountered_ascii_0=An illegal character was encountered. Code = {0}
-illegal_number_of_entries_0_in_osgi_1=Illegal number of entries {0} in OSGi version: "{1}"
missing_comma_in_range_0=Missing comma in range "{0}"
negative_character_range=The character range is negative
neither_raw_vector_nor_format_specified_0=Neither raw version nor format was specified: {0}
number_can_not_have_pad_value=A number cannot have a pad value
only_format_specified_0=Only a format was specified: {0}
+only_max_and_empty_string_defaults_can_have_translations=Only max string and empty string defaults can have translations
original_must_start_with_colon_0=Original version must start with colon: {0}
original_stated_but_missing_0=Expected original version after colon: {0}
pad_defined_more_then_once=Pad was defined more then once
-pad_not_allowed_in_osgi_0=Pad is not allowed in an OSGi version: "{0}"
performing_subquery=Performing subquery
premature_end_of_format=Premature end of format
premature_end_of_format_expected_0=Premature end of format, "{0}" expected
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/IUPropertyQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/IUPropertyQuery.java
new file mode 100644
index 000000000..95f3f3375
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/IUPropertyQuery.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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.internal.p2.metadata.query;
+
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+import org.eclipse.equinox.p2.query.MatchQuery;
+
+/**
+ * A query that searches for {@link IInstallableUnit} instances that have
+ * a property whose value matches the provided value. If no property name is
+ * specified, then all {@link IInstallableUnit} instances are accepted.
+ */
+public class IUPropertyQuery extends MatchQuery<IInstallableUnit> {
+ private String propertyName;
+ private String propertyValue;
+
+ /**
+ * Creates a new query on the given property name and value.
+ */
+ public IUPropertyQuery(String propertyName, String propertyValue) {
+ this.propertyName = propertyName;
+ this.propertyValue = propertyValue;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.p2.query2.Query#isMatch(java.lang.Object)
+ */
+ public boolean isMatch(IInstallableUnit candidate) {
+ if (propertyName == null)
+ return true;
+ String value = getProperty(candidate, propertyName);
+ if (value != null && (value.equals(propertyValue) || propertyValue == null))
+ return true;
+ return false;
+ }
+
+ protected String getProperty(IInstallableUnit iu, String name) {
+ return iu.getProperty(name);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/LatestIUVersionQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/LatestIUVersionQuery.java
new file mode 100644
index 000000000..fe79d9404
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/LatestIUVersionQuery.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+* Copyright (c) 2009 EclipseSource 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:
+* EclipseSource - initial API and implementation
+******************************************************************************/
+package org.eclipse.equinox.internal.p2.metadata.query;
+
+import java.util.*;
+import org.eclipse.equinox.p2.metadata.IVersionedId;
+import org.eclipse.equinox.p2.query.*;
+
+/**
+ * This query returns the latest version for each unique VersionedID.
+ * All other elements are discarded.
+ */
+public class LatestIUVersionQuery<T extends IVersionedId> extends ContextQuery<T> {
+
+ /**
+ * Performs the LatestIUVersionQuery
+ */
+ public IQueryResult<T> perform(Iterator<T> iterator) {
+ HashMap<String, T> greatestIUVersion = new HashMap<String, T>();
+ while (iterator.hasNext()) {
+ T versionedID = iterator.next();
+ if (greatestIUVersion.containsKey(versionedID.getId())) {
+ T currentIU = greatestIUVersion.get(versionedID.getId());
+ if (currentIU.getVersion().compareTo(versionedID.getVersion()) < 0)
+ greatestIUVersion.put(versionedID.getId(), versionedID);
+ } else
+ greatestIUVersion.put(versionedID.getId(), versionedID);
+ }
+
+ Collection<T> values = greatestIUVersion.values();
+ Iterator<T> valuesIterator = values.iterator();
+ boolean continueGather = true;
+
+ Collector<T> result = new Collector<T>();
+ while (valuesIterator.hasNext() && continueGather) {
+ T nextIU = valuesIterator.next();
+ continueGather = result.accept(nextIU);
+ }
+ return result;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/ObjectMatchQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/ObjectMatchQuery.java
new file mode 100644
index 000000000..f48e50aac
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/ObjectMatchQuery.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * 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.query;
+
+import org.eclipse.equinox.p2.query.MatchQuery;
+
+/**
+ * Special implementation for use without generic support
+ */
+public abstract class ObjectMatchQuery extends MatchQuery<Object> {
+ public boolean isMatch(Object candidate) {
+ return true;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/UpdateQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/UpdateQuery.java
new file mode 100644
index 000000000..3ca68f9a0
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/UpdateQuery.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 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.internal.p2.metadata.query;
+
+import org.eclipse.equinox.p2.metadata.IUpdateDescriptor;
+
+import org.eclipse.equinox.p2.metadata.IInstallableUnitPatch;
+
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+import org.eclipse.equinox.p2.metadata.IRequirement;
+import org.eclipse.equinox.p2.query.MatchQuery;
+
+/**
+ * A query that finds all IUs that are considered an "Update" of the
+ * specified IU.
+ */
+public class UpdateQuery extends MatchQuery<IInstallableUnit> {
+ private IInstallableUnit updateFrom;
+
+ public UpdateQuery(IInstallableUnit updateFrom) {
+ this.updateFrom = updateFrom;
+ }
+
+ public boolean isMatch(IInstallableUnit candidate) {
+ if (candidate instanceof IInstallableUnitPatch && !(updateFrom instanceof IInstallableUnitPatch)) {
+ IInstallableUnitPatch potentialPatch = (IInstallableUnitPatch) candidate;
+ IRequirement lifeCycle = potentialPatch.getLifeCycle();
+ if (lifeCycle == null)
+ return false;
+ return updateFrom.satisfies(lifeCycle);
+ }
+ IUpdateDescriptor descriptor = candidate.getUpdateDescriptor();
+ if (descriptor != null && descriptor.isUpdateOf(updateFrom)) {
+ if (!updateFrom.getId().equals(candidate.getId()))
+ return true;
+ return updateFrom.getVersion().compareTo(candidate.getVersion()) < 0;
+ }
+ return false;
+ }
+}

Back to the top