diff options
Diffstat (limited to 'bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata')
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 > Integer > VersionVector > MAXS_VALUE > String > 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.<operation>(y | <expression&rt;)</code> + */ +public abstract class CollectionFilter extends Unary { + public static void appendProlog(StringBuffer bld, Variable rootVariable, Expression lhs, String operator) { + if (lhs != rootVariable) { + appendOperand(bld, rootVariable, lhs, PRIORITY_COLLECTION); + bld.append('.'); + } + bld.append(operator); + bld.append('('); + } + + public final LambdaExpression lambda; + + protected CollectionFilter(Expression collection, LambdaExpression lambda) { + super(collection); + this.lambda = lambda; + } + + public boolean accept(IExpressionVisitor visitor) { + return super.accept(visitor) && lambda.accept(visitor); + } + + public int compareTo(Expression e) { + int cmp = super.compareTo(e); + if (cmp == 0) + cmp = lambda.compareTo(((CollectionFilter) e).lambda); + return cmp; + } + + public boolean equals(Object o) { + return super.equals(o) && lambda.equals(((CollectionFilter) o).lambda); + } + + public final Object evaluate(IEvaluationContext context) { + Iterator<?> lval = operand.evaluateAsIterator(context); + context = lambda.prolog(context); + return evaluate(context, lval); + } + + public final Iterator<?> evaluateAsIterator(IEvaluationContext context) { + Iterator<?> lval = operand.evaluateAsIterator(context); + context = lambda.prolog(context); + return evaluateAsIterator(context, lval); + } + + public void toString(StringBuffer bld, Variable rootVariable) { + appendProlog(bld, rootVariable, operand, getOperator()); + appendOperand(bld, rootVariable, lambda, PRIORITY_LAMBDA); + bld.append(')'); + } + + public int hashCode() { + int result = 31 + operand.hashCode(); + return 31 * result + lambda.hashCode(); + } + + public int getPriority() { + return PRIORITY_COLLECTION; + } + + protected abstract Object evaluate(final IEvaluationContext context, Iterator<?> iterator); + + protected Iterator<?> evaluateAsIterator(IEvaluationContext context, Iterator<?> iterator) { + throw new UnsupportedOperationException(); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Compare.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Compare.java new file mode 100644 index 000000000..7e958e41d --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Compare.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2010 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext; + +/** + * Comparisons for magnitude. + */ +final class Compare extends Binary { + static IllegalArgumentException uncomparable(Object lval, Object rval) { + return new IllegalArgumentException("Cannot compare a " + lval.getClass().getName() + " to a " + rval.getClass().getName()); //$NON-NLS-1$//$NON-NLS-2$ + } + + final boolean compareLess; + + final boolean equalOK; + + Compare(Expression lhs, Expression rhs, boolean compareLess, boolean equalOK) { + super(lhs, rhs); + this.compareLess = compareLess; + this.equalOK = equalOK; + } + + public Object evaluate(IEvaluationContext context) { + int cmpResult = CoercingComparator.coerceAndCompare(lhs.evaluate(context), rhs.evaluate(context)); + return Boolean.valueOf(cmpResult == 0 ? equalOK : (cmpResult < 0 ? compareLess : !compareLess)); + } + + public int getExpressionType() { + return compareLess ? (equalOK ? TYPE_LESS_EQUAL : TYPE_LESS) : (equalOK ? TYPE_GREATER_EQUAL : TYPE_GREATER); + } + + public String getOperator() { + return compareLess ? (equalOK ? OPERATOR_LT_EQUAL : OPERATOR_LT) : (equalOK ? OPERATOR_GT_EQUAL : OPERATOR_GT); + } + + public void toLDAPString(StringBuffer buf) { + if (!equalOK) + buf.append("(!"); //$NON-NLS-1$ + buf.append('('); + appendLDAPAttribute(buf); + if (equalOK) + buf.append(compareLess ? OPERATOR_LT_EQUAL : OPERATOR_GT_EQUAL); + else + buf.append(compareLess ? OPERATOR_GT_EQUAL : OPERATOR_LT_EQUAL); + appendLDAPValue(buf); + buf.append(')'); + if (!equalOK) + buf.append(')'); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Equals.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Equals.java new file mode 100644 index 000000000..7452c3165 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Equals.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext; + +/** + * An expression that performs the == and != comparisons + */ +final class Equals extends Binary { + final boolean negate; + + Equals(Expression lhs, Expression rhs, boolean negate) { + super(lhs, rhs); + this.negate = negate; + } + + public Object evaluate(IEvaluationContext context) { + boolean result = CoercingComparator.coerceAndEquals(lhs.evaluate(context), rhs.evaluate(context)); + if (negate) + result = !result; + return Boolean.valueOf(result); + } + + public int getExpressionType() { + return negate ? TYPE_NOT_EQUALS : TYPE_EQUALS; + } + + public String getOperator() { + return negate ? OPERATOR_NOT_EQUALS : OPERATOR_EQUALS; + } + + public void toLDAPString(StringBuffer buf) { + if (negate) + buf.append("(!"); //$NON-NLS-1$ + buf.append('('); + appendLDAPAttribute(buf); + buf.append('='); + appendLDAPValue(buf); + buf.append(')'); + if (negate) + buf.append(')'); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/EvaluationContext.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/EvaluationContext.java new file mode 100644 index 000000000..8fb3665bc --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/EvaluationContext.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext; +import org.eclipse.equinox.p2.metadata.expression.IExpression; + +/** + * Highly specialized evaluation contexts optimized for misc purposes + */ +public class EvaluationContext implements IEvaluationContext { + public static class Parameters extends EvaluationContext { + private static final Object[] noParameters = new Object[0]; + + private final Object[] parameters; + + public Parameters(IEvaluationContext parentContext, Object[] parameters) { + super(parentContext); + this.parameters = parameters == null ? noParameters : parameters; + } + + public Object getParameter(int position) { + return position >= 0 && position < parameters.length ? parameters[position] : super.getParameter(position); + } + } + + public static class SingleVariableContext implements IEvaluationContext { + private final IEvaluationContext parentContext; + + private Object value; + + private final IExpression variable; + + public SingleVariableContext(IEvaluationContext parentContext, IExpression variable) { + this.parentContext = parentContext; + this.variable = variable; + } + + public Object getParameter(int position) { + return parentContext.getParameter(position); + } + + public Object getValue(IExpression var) { + return variable == var ? value : parentContext.getValue(var); + } + + public void setValue(IExpression var, Object val) { + if (variable == var) + value = val; + else + parentContext.setValue(var, val); + } + } + + static class MultiVariableContext implements IEvaluationContext { + private final IEvaluationContext parentContext; + + private final Object[] values; + + public MultiVariableContext(IEvaluationContext parentContext, IExpression[] variables) { + this.parentContext = parentContext; + values = new Object[variables.length * 2]; + for (int idx = 0, ndx = 0; ndx < variables.length; ++ndx, idx += 2) + values[idx] = variables[ndx]; + } + + public Object getParameter(int position) { + return parentContext.getParameter(position); + } + + public Object getValue(IExpression variable) { + for (int idx = 0; idx < values.length; ++idx) + if (values[idx++] == variable) + return values[idx]; + return parentContext.getValue(variable); + } + + public void setValue(IExpression variable, Object value) { + for (int idx = 0; idx < values.length; ++idx) + if (values[idx++] == variable) { + values[idx] = value; + return; + } + parentContext.setValue(variable, value); + } + } + + public static final EvaluationContext INSTANCE = new EvaluationContext(null); + + public static IEvaluationContext create() { + return INSTANCE; + } + + public static IEvaluationContext create(IEvaluationContext parent, IExpression variable) { + return new SingleVariableContext(parent, variable); + } + + public static IEvaluationContext create(IEvaluationContext parent, IExpression[] variables) { + return variables.length == 1 ? new SingleVariableContext(parent, variables[0]) : new MultiVariableContext(parent, variables); + } + + public static IEvaluationContext create(IEvaluationContext parent, Object[] parameters) { + return new Parameters(parent, parameters); + } + + public static IEvaluationContext create(IExpression variable) { + return new SingleVariableContext(null, variable); + } + + public static IEvaluationContext create(IExpression[] variables) { + if (variables == null || variables.length == 0) + return INSTANCE; + return variables.length == 1 ? create(variables[0]) : new MultiVariableContext(INSTANCE, variables); + } + + public static IEvaluationContext create(Object[] parameters, IExpression variable) { + return parameters == null || parameters.length == 0 ? create(variable) : new SingleVariableContext(new Parameters(null, parameters), variable); + } + + public static IEvaluationContext create(Object[] parameters, IExpression[] variables) { + if (parameters == null || parameters.length == 0) + return create(variables); + + Parameters pctx = new Parameters(null, parameters); + if (variables == null || variables.length == 0) + return pctx; + return create(pctx, variables); + } + + protected EvaluationContext(IEvaluationContext parentContext) { + this.parentContext = parentContext; + } + + private final IEvaluationContext parentContext; + + public Object getParameter(int position) { + if (parentContext == null) + throw new IllegalArgumentException("No such parameter: $" + position); //$NON-NLS-1$ + return parentContext.getParameter(position); + } + + public Object getValue(IExpression variable) { + if (parentContext == null) + throw new IllegalArgumentException("No such variable: " + variable); //$NON-NLS-1$ + return parentContext.getValue(variable); + } + + public void setValue(IExpression variable, Object value) { + if (parentContext == null) + throw new IllegalArgumentException("No such variable: " + variable); //$NON-NLS-1$ + parentContext.setValue(variable, value); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Exists.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Exists.java new file mode 100644 index 000000000..14fbc5df4 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Exists.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import java.util.Iterator; +import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext; + +/** + * A collection filter that yields true if the <code>filter</code> yields true for + * any of the elements of the <code>collection</code> + */ +final class Exists extends CollectionFilter { + Exists(Expression collection, LambdaExpression lambda) { + super(collection, lambda); + } + + protected Object evaluate(IEvaluationContext context, Iterator<?> itor) { + Variable variable = lambda.getItemVariable(); + while (itor.hasNext()) { + variable.setValue(context, itor.next()); + if (lambda.evaluate(context) == Boolean.TRUE) + return Boolean.TRUE; + } + return Boolean.FALSE; + } + + public int getExpressionType() { + return TYPE_EXISTS; + } + + public String getOperator() { + return KEYWORD_EXISTS; + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Expression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Expression.java new file mode 100644 index 000000000..75f47dd94 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Expression.java @@ -0,0 +1,341 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import java.util.*; +import org.eclipse.equinox.p2.metadata.expression.*; + +/** + * The base class of the expression tree. + */ +public abstract class Expression implements IExpression, Comparable<Expression>, IExpressionConstants { + + static final Expression[] emptyArray = new Expression[0]; + + public static void appendOperand(StringBuffer bld, Variable rootVariable, Expression operand, int priority) { + if (priority < operand.getPriority()) { + bld.append('('); + operand.toString(bld, rootVariable); + bld.append(')'); + } else + operand.toString(bld, rootVariable); + } + + public static Expression[] assertLength(Expression[] operands, int minLength, int maxLength, String operand) { + if (operands == null) + operands = emptyArray; + if (operands.length < minLength) + throw new IllegalArgumentException("Not enough operands for " + operand); //$NON-NLS-1$ + if (operands.length > maxLength) + throw new IllegalArgumentException("Too many operands for " + operand); //$NON-NLS-1$ + return operands; + } + + public static Expression[] assertLength(Expression[] operands, int length, String operand) { + if (operands == null) + operands = emptyArray; + if (operands.length < length) + throw new IllegalArgumentException("Not enough operands for " + operand); //$NON-NLS-1$ + return operands; + } + + public static int compare(Expression[] arr1, Expression[] arr2) { + int max = arr1.length; + if (max > arr2.length) + max = arr2.length; + for (int idx = 0; idx < max; ++idx) { + int cmp = arr1[idx].compareTo(arr2[idx]); + if (cmp != 0) + return cmp; + } + if (max == arr2.length) { + if (max < arr1.length) + return 1; + return 0; + } + return -1; + } + + public static boolean equals(Expression[] arr1, Expression[] arr2) { + int idx = arr1.length; + if (idx != arr2.length) + return false; + while (--idx >= 0) + if (!arr1[idx].equals(arr2[idx])) + return false; + return true; + } + + public static int hashCode(Expression[] arr) { + int idx = arr.length; + int result = 1; + while (--idx >= 0) + result = 31 * result + arr[idx].hashCode(); + return result; + } + + public static void elementsToString(StringBuffer bld, Variable rootVariable, Expression[] elements) { + int top = elements.length; + if (top > 0) { + elements[0].toString(bld, rootVariable); + for (int idx = 1; idx < top; ++idx) { + bld.append(", "); //$NON-NLS-1$ + appendOperand(bld, rootVariable, elements[idx], PRIORITY_MAX); + } + } + } + + /** + * Let the visitor visit this instance and all expressions that this + * instance contains. + * @param visitor The visiting visitor. + * @return <code>true</code> if the visitor should continue visiting, <code>false</code> otherwise. + */ + public boolean accept(IExpressionVisitor visitor) { + return visitor.visit(this); + } + + public int compareTo(Expression e) { + int cmp = getPriority() - e.getPriority(); + if (cmp == 0) { + int e1 = getExpressionType(); + int e2 = e.getExpressionType(); + cmp = e1 > e2 ? 1 : (e1 == e2 ? 0 : -1); + } + return cmp; + } + + public boolean equals(Object e) { + if (e == this) + return true; + if (e == null || getClass() != e.getClass()) + return false; + return getExpressionType() == ((Expression) e).getExpressionType(); + } + + /** + * Evaluate this expression with given context and variables. + * @param context The evaluation context + * @return The result of the evaluation. + */ + public abstract Object evaluate(IEvaluationContext context); + + public Iterator<?> evaluateAsIterator(IEvaluationContext context) { + Object value = evaluate(context); + if (!(value instanceof Iterator<?>)) + value = RepeatableIterator.create(value); + return (Iterator<?>) value; + } + + public abstract String getOperator(); + + public abstract int getPriority(); + + public boolean isRootVariable() { + return false; + } + + public final String toLDAPString() { + StringBuffer bld = new StringBuffer(); + toLDAPString(bld); + return bld.toString(); + } + + public void toLDAPString(StringBuffer buf) { + throw new UnsupportedOperationException(); + } + + public final String toString() { + StringBuffer bld = new StringBuffer(); + toString(bld); + return bld.toString(); + } + + public void toString(StringBuffer bld) { + toString(bld, ExpressionFactory.THIS); + } + + public abstract void toString(StringBuffer bld, Variable rootVariable); + + private static class Compacter { + private Expression base; + + private List<Expression> parts; + + private int op; + + Compacter(Expression base, int op) { + this.base = base; + this.op = op; + } + + Expression getResultingFilter() { + if (parts == null) + return base; + + int partsOp = op == TYPE_AND ? TYPE_OR : TYPE_AND; + return addFilter(base, normalize(parts, partsOp), op); + } + + boolean merge(Expression b) { + Expression[] aArr; + Expression[] bArr; + if (base.getExpressionType() == op) + aArr = getFilterImpls(base); + else + aArr = new Expression[] {base}; + + if (b.getExpressionType() == op) + bArr = getFilterImpls(b); + else + bArr = new Expression[] {b}; + + List<Expression> common = null; + List<Expression> onlyA = null; + + int atop = aArr.length; + int btop = bArr.length; + int aidx; + int bidx; + for (aidx = 0; aidx < atop; ++aidx) { + Expression af = aArr[aidx]; + for (bidx = 0; bidx < btop; ++bidx) { + Expression bf = bArr[bidx]; + if (af.equals(bf)) { + if (common == null) + common = new ArrayList<Expression>(); + common.add(af); + break; + } + } + if (bidx == btop) { + if (onlyA == null) + onlyA = new ArrayList<Expression>(); + onlyA.add(af); + } + } + if (common == null) + // Nothing in common + return false; + + if (onlyA == null && parts == null) + return true; + + List<Expression> onlyB = null; + for (bidx = 0; bidx < btop; ++bidx) { + Expression bf = bArr[bidx]; + for (aidx = 0; aidx < atop; ++aidx) + if (bf.equals(aArr[aidx])) + break; + if (aidx == atop) { + if (onlyB == null) + onlyB = new ArrayList<Expression>(); + onlyB.add(bf); + } + } + + if (onlyB == null && parts == null) { + // All of B is already covered by base + base = b; + return true; + } + + if (parts == null) + parts = new ArrayList<Expression>(); + + if (onlyA != null) { + base = normalize(common, op); + Expression af = normalize(onlyA, op); + if (!parts.contains(af)) + parts.add(af); + } + Expression bf = normalize(onlyB, op); + if (!parts.contains(bf)) + parts.add(bf); + return true; + } + } + + static Expression addFilter(Expression base, Expression subFilter, int expressionType) { + if (base.equals(subFilter)) + return base; + + ArrayList<Expression> filters = new ArrayList<Expression>(2); + filters.add(base); + filters.add(subFilter); + return normalize(filters, expressionType); + } + + static Expression normalize(List<Expression> operands, int op) { + int top = operands.size(); + if (top == 1) + return operands.get(0); + + // a | (b | c) becomes a | b | c + // a & (b & c) becomes a & b & c + // + for (int idx = 0; idx < top; ++idx) { + Expression f = operands.get(idx); + if (f.getExpressionType() != op) + continue; + + Expression[] sfs = getFilterImpls(f); + operands.remove(idx); + --top; + for (int ndx = 0; ndx < sfs.length; ++ndx) { + Expression nf = sfs[ndx]; + if (!operands.contains(nf)) + operands.add(nf); + } + } + top = operands.size(); + if (top == 1) + return operands.get(0); + + Collections.sort(operands); + List<Compacter> splits = new ArrayList<Compacter>(); + int reverseOp = op == TYPE_AND ? TYPE_OR : TYPE_AND; + + for (int idx = 0; idx < top; ++idx) + merge(splits, operands.get(idx), reverseOp); + + operands.clear(); + top = splits.size(); + for (int idx = 0; idx < top; ++idx) { + Expression filter = splits.get(idx).getResultingFilter(); + if (!operands.contains(filter)) + operands.add(filter); + } + top = operands.size(); + if (top == 1) + return operands.get(0); + + Collections.sort(operands); + Expression[] expArray = operands.toArray(new Expression[top]); + return op == TYPE_AND ? new And(expArray) : new Or(expArray); + } + + static void merge(List<Compacter> splits, Expression base, int op) { + int top = splits.size(); + for (int idx = 0; idx < top; ++idx) { + Compacter split = splits.get(idx); + if (split.merge(base)) + return; + } + splits.add(new Compacter(base, op)); + } + + static Expression[] getFilterImpls(Expression expression) { + if (expression instanceof NAry) + return ((NAry) expression).operands; + throw new IllegalArgumentException(); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/ExpressionFactory.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/ExpressionFactory.java new file mode 100644 index 000000000..a9e70e311 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/ExpressionFactory.java @@ -0,0 +1,125 @@ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import java.util.List; +import org.eclipse.equinox.p2.metadata.expression.*; + +public class ExpressionFactory implements IExpressionFactory, IExpressionConstants { + public static final IExpressionFactory INSTANCE = new ExpressionFactory(); + public static final Variable THIS = new Variable(VARIABLE_THIS); + + protected static Expression[] convertArray(IExpression[] operands) { + Expression[] ops = new Expression[operands.length]; + System.arraycopy(operands, 0, ops, 0, operands.length); + return ops; + } + + protected ExpressionFactory() { + // Maintain singleton + } + + public IExpression all(IExpression collection, IExpression lambda) { + return new All((Expression) collection, (LambdaExpression) lambda); + } + + public IExpression and(IExpression... operands) { + return new And(convertArray(operands)); + } + + public IExpression at(IExpression target, IExpression key) { + return new At((Expression) target, (Expression) key); + } + + @SuppressWarnings("unchecked") + public IExpression normalize(List<? extends IExpression> operands, int expressionType) { + return Expression.normalize((List<Expression>) operands, expressionType); + } + + public IExpression constant(Object value) { + return Literal.create(value); + } + + public IEvaluationContext createContext(Object... parameters) { + return EvaluationContext.create(parameters, (Variable[]) null); + } + + public IEvaluationContext createContext(IExpression[] variables, Object... parameters) { + return EvaluationContext.create(parameters, variables); + } + + public IFilterExpression filterExpression(IExpression expression) { + return new LDAPFilter((Expression) expression); + } + + public IExpression equals(IExpression lhs, IExpression rhs) { + return new Equals((Expression) lhs, (Expression) rhs, false); + } + + public IExpression exists(IExpression collection, IExpression lambda) { + return new Exists((Expression) collection, (LambdaExpression) lambda); + } + + public IExpression greater(IExpression lhs, IExpression rhs) { + return new Compare((Expression) lhs, (Expression) rhs, false, false); + } + + public IExpression greaterEqual(IExpression lhs, IExpression rhs) { + return new Compare((Expression) lhs, (Expression) rhs, false, true); + } + + public IExpression indexedParameter(int index) { + return new Parameter(index); + } + + public IExpression lambda(IExpression variable, IExpression body) { + return new LambdaExpression((Variable) variable, (Expression) body); + } + + public IExpression less(IExpression lhs, IExpression rhs) { + return new Compare((Expression) lhs, (Expression) rhs, true, false); + } + + public IExpression lessEqual(IExpression lhs, IExpression rhs) { + return new Compare((Expression) lhs, (Expression) rhs, true, true); + } + + public IExpression matches(IExpression lhs, IExpression rhs) { + return new Matches((Expression) lhs, (Expression) rhs); + } + + public <T> IMatchExpression<T> matchExpression(IExpression expression, Object... parameters) { + return new MatchExpression<T>((Expression) expression, parameters); + } + + public IExpression member(IExpression target, String name) { + return new Member.DynamicMember((Expression) target, name); + } + + public IExpression not(IExpression operand) { + if (operand instanceof Equals) { + Equals eq = (Equals) operand; + return new Equals(eq.lhs, eq.rhs, !eq.negate); + } + if (operand instanceof Compare) { + Compare cmp = (Compare) operand; + return new Compare(cmp.lhs, cmp.rhs, !cmp.compareLess, !cmp.equalOK); + } + if (operand instanceof Not) + return ((Not) operand).operand; + + return new Not((Expression) operand); + } + + public IExpression or(IExpression... operands) { + return new Or(convertArray(operands)); + } + + public IExpression thisVariable() { + return THIS; + } + + public IExpression variable(String name) { + if (VARIABLE_THIS.equals(name)) + return THIS; + return new Variable(name); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IExpressionConstants.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IExpressionConstants.java new file mode 100644 index 000000000..3da1dbfac --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IExpressionConstants.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +public interface IExpressionConstants { + String KEYWORD_ALL = "all"; //$NON-NLS-1$ + + String KEYWORD_BOOLEAN = "boolean"; //$NON-NLS-1$ + String KEYWORD_CLASS = "class"; //$NON-NLS-1$ + String KEYWORD_EXISTS = "exists"; //$NON-NLS-1$ + String KEYWORD_FALSE = "false"; //$NON-NLS-1$ + String KEYWORD_FILTER = "filter"; //$NON-NLS-1$ + String KEYWORD_NULL = "null"; //$NON-NLS-1$ + String KEYWORD_RANGE = "range"; //$NON-NLS-1$ + String KEYWORD_TRUE = "true"; //$NON-NLS-1$ + + String KEYWORD_VERSION = "version"; //$NON-NLS-1$ + String OPERATOR_AND = "&&"; //$NON-NLS-1$ + String OPERATOR_AT = "[]"; //$NON-NLS-1$ + String OPERATOR_EQUALS = "=="; //$NON-NLS-1$ + String OPERATOR_GT = ">"; //$NON-NLS-1$ + String OPERATOR_GT_EQUAL = ">="; //$NON-NLS-1$ + String OPERATOR_LT = "<"; //$NON-NLS-1$ + String OPERATOR_LT_EQUAL = "<="; //$NON-NLS-1$ + String OPERATOR_MATCHES = "~="; //$NON-NLS-1$ + String OPERATOR_MEMBER = "."; //$NON-NLS-1$ + String OPERATOR_NOT = "!"; //$NON-NLS-1$ + String OPERATOR_NOT_EQUALS = "!="; //$NON-NLS-1$ + + String OPERATOR_OR = "||"; //$NON-NLS-1$ + String OPERATOR_PARAMETER = "$"; //$NON-NLS-1$ + + int PRIORITY_LITERAL = 1; + int PRIORITY_VARIABLE = 1; + int PRIORITY_FUNCTION = 2; // for extend query expressions + int PRIORITY_MEMBER = 3; + int PRIORITY_COLLECTION = 4; + int PRIORITY_NOT = 5; + int PRIORITY_BINARY = 6; + int PRIORITY_AND = 7; + int PRIORITY_OR = 8; + int PRIORITY_CONDITION = 9; + int PRIORITY_ASSIGNMENT = 10; + int PRIORITY_LAMBDA = 11; + int PRIORITY_COMMA = 12; + int PRIORITY_MAX = 20; + + String VARIABLE_EVERYTHING = "everything"; //$NON-NLS-1$ + String VARIABLE_THIS = "this"; //$NON-NLS-1$ +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IRepeatableIterator.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IRepeatableIterator.java new file mode 100644 index 000000000..f20e266bf --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/IRepeatableIterator.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import java.util.Iterator; + +public interface IRepeatableIterator<T> extends Iterator<T> { + /** + * Returns a copy that will iterate over the same elements + * as this iterator. The contents or position of this iterator + * is left unchanged. + * @return A re-initialized copy of this iterator. + */ + IRepeatableIterator<T> getCopy(); + + Object getIteratorProvider(); +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPApproximation.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPApproximation.java new file mode 100644 index 000000000..291a6ef6c --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPApproximation.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2010 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import java.io.Serializable; + +/** + * Map a string for an LDAP APPROX (~=) comparison. + * This implementation removes white spaces and transforms everything to lower case. + */ +public final class LDAPApproximation implements Serializable, Comparable<LDAPApproximation> { + private static final long serialVersionUID = 4782295637798543587L; + private final String pattern; + private transient String approxPattern; + + public LDAPApproximation(String pattern) { + this.pattern = pattern; + } + + public int compareTo(LDAPApproximation o) { + return pattern.compareTo(o.pattern); + } + + public boolean equals(Object o) { + return o == this || (o instanceof LDAPApproximation && ((LDAPApproximation) o).pattern.equals(pattern)); + } + + public int hashCode() { + return 3 * pattern.hashCode(); + } + + /** + * Matches the <code>value</code> with the compiled expression. The value + * is considered matching if all characters are matched by the expression. A + * partial match is not enough. + * @param value The value to match + * @return <code>true</code> if the value was a match. + */ + public boolean isMatch(CharSequence value) { + if (value == null) + return false; + if (approxPattern == null) + approxPattern = approxString(pattern); + return approxString(value).equals(approxPattern); + } + + public String toString() { + return pattern; + } + + private static String approxString(CharSequence input) { + boolean changed = false; + char[] output = new char[input.length()]; + int cursor = 0; + for (int i = 0, length = output.length; i < length; i++) { + char c = input.charAt(i); + if (Character.isWhitespace(c)) { + changed = true; + continue; + } + if (Character.isUpperCase(c)) { + changed = true; + c = Character.toLowerCase(c); + } + output[cursor++] = c; + } + return changed ? new String(output, 0, cursor) : input.toString(); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPFilter.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPFilter.java new file mode 100644 index 000000000..607334bea --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LDAPFilter.java @@ -0,0 +1,69 @@ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import java.util.Dictionary; +import java.util.Map; +import org.eclipse.equinox.p2.metadata.expression.*; +import org.osgi.framework.Filter; +import org.osgi.framework.ServiceReference; + +public class LDAPFilter extends Unary implements IFilterExpression { + + LDAPFilter(Expression expression) { + super(expression); + } + + public boolean accept(IExpressionVisitor visitor) { + return operand.accept(visitor); + } + + public boolean equals(Object o) { + return (o instanceof Filter && !(o instanceof LDAPFilter)) ? equals(ExpressionUtil.parseLDAP(o.toString())) : super.equals(o); + } + + @Override + public String getOperator() { + return operand.getOperator(); + } + + @Override + public int getPriority() { + return operand.getPriority(); + } + + public int getExpressionType() { + return 0; + } + + public boolean match(Map<String, ? extends Object> map) { + return isMatch(MemberProvider.create(map, true)); + } + + @SuppressWarnings("rawtypes") + public boolean match(Dictionary dictionary) { + return isMatch(dictionary == null ? MemberProvider.emptyProvider() : MemberProvider.create(dictionary, true)); + } + + private boolean isMatch(Object candidate) { + Variable self = ExpressionFactory.THIS; + IEvaluationContext ctx = EvaluationContext.create(self); + self.setValue(ctx, candidate); + return Boolean.TRUE == operand.evaluate(ctx); + } + + public boolean match(ServiceReference reference) { + return isMatch(reference == null ? MemberProvider.emptyProvider() : MemberProvider.create(reference, true)); + } + + public boolean matchCase(Map<String, ? extends Object> map) { + return isMatch(map == null ? MemberProvider.emptyProvider() : MemberProvider.create(map, false)); + } + + @SuppressWarnings("rawtypes") + public boolean matchCase(Dictionary dictionary) { + return isMatch(dictionary == null ? MemberProvider.emptyProvider() : MemberProvider.create(dictionary, false)); + } + + public void toString(StringBuffer bld, Variable rootVariable) { + operand.toLDAPString(bld); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LambdaExpression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LambdaExpression.java new file mode 100644 index 000000000..c5bacf0ab --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/LambdaExpression.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext; +import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor; + +/** + * A function that executes some code + */ +public class LambdaExpression extends Unary { + protected final Variable each; + + protected LambdaExpression(Variable each, Expression body) { + super(body); + this.each = each; + } + + public boolean accept(IExpressionVisitor visitor) { + return super.accept(visitor) && each.accept(visitor); + } + + public int compareTo(Expression e) { + int cmp = super.compareTo(e); + if (cmp == 0) + cmp = each.compareTo(((LambdaExpression) e).each); + return cmp; + } + + public boolean equals(Object o) { + return super.equals(o) && each.equals(((LambdaExpression) o).each); + } + + public int hashCode() { + int result = 31 + operand.hashCode(); + return 31 * result + each.hashCode(); + } + + public int getExpressionType() { + return TYPE_LAMBDA; + } + + public void toString(StringBuffer bld, Variable rootVariable) { + each.toString(bld, rootVariable); + bld.append(" | "); //$NON-NLS-1$ + appendOperand(bld, rootVariable, operand, IExpressionConstants.PRIORITY_COMMA); + } + + public Variable getItemVariable() { + return each; + } + + public String getOperator() { + return "|"; //$NON-NLS-1$ + } + + public int getPriority() { + return IExpressionConstants.PRIORITY_LAMBDA; + } + + public IEvaluationContext prolog(IEvaluationContext context) { + return EvaluationContext.create(context, each); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Literal.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Literal.java new file mode 100644 index 000000000..2884fb757 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Literal.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import org.eclipse.equinox.p2.metadata.Version; +import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext; +import org.eclipse.equinox.p2.metadata.expression.SimplePattern; +import org.osgi.framework.Filter; + +/** + * An expression that represents a constant value. + */ +public final class Literal extends Expression { + public static final Literal FALSE_CONSTANT = new Literal(Boolean.FALSE); + + public static final Literal NULL_CONSTANT = new Literal(null); + + public static final Literal TRUE_CONSTANT = new Literal(Boolean.TRUE); + + static Literal create(Object value) { + if (value == null) + return NULL_CONSTANT; + if (value == Boolean.TRUE) + return TRUE_CONSTANT; + if (value == Boolean.FALSE) + return FALSE_CONSTANT; + return new Literal(value); + } + + public final Object value; + + private Literal(Object value) { + this.value = value; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public int compareTo(Expression e) { + int cmp = super.compareTo(e); + if (cmp != 0) + return cmp; + + Object eValue = ((Literal) e).value; + if (value == null) + return eValue == null ? 0 : -1; + + if (eValue == null) + return 1; + + if (eValue.getClass() == value.getClass()) + return ((Comparable) value).compareTo(eValue); + + return eValue.getClass().getName().compareTo(value.getClass().getName()); + } + + public boolean equals(Object o) { + if (super.equals(o)) { + Literal bo = (Literal) o; + return value == null ? bo.value == null : value.equals(bo.value); + } + return false; + } + + public Object evaluate(IEvaluationContext context) { + return value; + } + + public int getExpressionType() { + return TYPE_LITERAL; + } + + public String getOperator() { + return "<literal>"; //$NON-NLS-1$ + } + + public int getPriority() { + return PRIORITY_LITERAL; + } + + public int hashCode() { + return 31 + value.hashCode(); + } + + public void toLDAPString(StringBuffer buf) { + if (!(value instanceof Filter)) + throw new UnsupportedOperationException(); + buf.append(value.toString()); + } + + public void toString(StringBuffer bld, Variable rootVariable) { + if (value == null) + bld.append("null"); //$NON-NLS-1$ + else if (value instanceof String || value instanceof Version) { + String str = value.toString(); + char sep = str.indexOf('\'') >= 0 ? '"' : '\''; + bld.append(sep); + bld.append(str); + bld.append(sep); + } else if (value instanceof SimplePattern) { + appendEscaped(bld, '/', value.toString()); + } else + bld.append(value); + } + + private void appendEscaped(StringBuffer bld, char delimiter, String str) { + bld.append(delimiter); + int top = str.length(); + for (int idx = 0; idx < top; ++idx) { + char c = str.charAt(idx); + if (c == delimiter) + bld.append('\\'); + bld.append(c); + } + bld.append(delimiter); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MatchExpression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MatchExpression.java new file mode 100644 index 000000000..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 "matching" The actual algorithm used for + * performing the match varies depending on the types of the items to match.</p> + * <p>The following things can be matched:</p> + * <table border="1" cellpadding="3"> + * <tr><th>LHS</th><th>RHS</th><th>Implemented as</th></tr> + * <tr><td>{@link String}</td><td>{@link SimplePattern}</td><td>rhs.isMatch(lhs)</td></tr> + * <tr><td>{@link String}</td><td>{@link LDAPApproximation}</td><td>rhs.isMatch(lhs)</td></tr> + * <tr><td><any></td><td>{@link Class}</td><td>rhs.isInstance(lhs)</td></tr> + * <tr><td>{@link Class}</td><td>{@link Class}</td><td>rhs.isAssignableFrom(lhs)</td></tr> + * </table> + */ +public class Matches extends Binary { + protected Matches(Expression lhs, Expression rhs) { + super(lhs, rhs); + } + + public Object evaluate(IEvaluationContext context) { + return Boolean.valueOf(match(lhs.evaluate(context), rhs.evaluate(context))); + } + + protected boolean match(Object lval, Object rval) { + if (rval instanceof VersionRange) { + VersionRange range = (VersionRange) rval; + if (lval instanceof Version) + return range.isIncluded((Version) lval); + if (lval instanceof String) + return range.isIncluded(Version.create((String) lval)); + } + if (rval instanceof SimplePattern) { + if (lval instanceof CharSequence) + return ((SimplePattern) rval).isMatch((CharSequence) lval); + if (lval instanceof Character || lval instanceof Number || lval instanceof Boolean) + return ((SimplePattern) rval).isMatch(lval.toString()); + + } else if (rval instanceof LDAPApproximation) { + if (lval instanceof CharSequence) + return ((LDAPApproximation) rval).isMatch((CharSequence) lval); + if (lval instanceof Character || lval instanceof Number || lval instanceof Boolean) + return ((LDAPApproximation) rval).isMatch(lval.toString()); + + } else if (rval instanceof Class<?>) { + Class<?> rclass = (Class<?>) rval; + return lval instanceof Class<?> ? rclass.isAssignableFrom((Class<?>) lval) : rclass.isInstance(lval); + } + + if (lval == null || rval == null) + return false; + + throw new IllegalArgumentException("Cannot match a " + lval.getClass().getName() + " with a " + rval.getClass().getName()); //$NON-NLS-1$//$NON-NLS-2$ + } + + public int getExpressionType() { + return TYPE_MATCHES; + } + + public String getOperator() { + return OPERATOR_MATCHES; + } + + public void toLDAPString(StringBuffer buf) { + if (!(rhs instanceof Literal)) + throw new UnsupportedOperationException(); + + boolean escapeWild = true; + Object val = rhs.evaluate(null); + buf.append('('); + appendLDAPAttribute(buf); + if (val instanceof LDAPApproximation) { + buf.append(getOperator()); + } else if (val instanceof SimplePattern) { + buf.append('='); + escapeWild = false; + } else + throw new UnsupportedOperationException(); + appendLDAPEscaped(buf, val.toString(), escapeWild); + buf.append(')'); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java new file mode 100644 index 000000000..5d442f62c --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java @@ -0,0 +1,178 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import java.lang.reflect.*; +import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext; +import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor; + +/** + * <p>An expression that performs member calls to obtain some value + * from some object instance. It uses standard bean semantics so + * that an attempt to obtain "value" will cause an + * attempt to call <code>getValue()</code> and if no such method + * exists, <code>isValue()</code> and if that doesn't work either, + * <code>value()</code>.</p> + */ +public abstract class Member extends Unary { + + public static final class DynamicMember extends Member { + private static final String GET_PREFIX = "get"; //$NON-NLS-1$ + private static final String IS_PREFIX = "is"; //$NON-NLS-1$ + private static final Class<?>[] NO_ARG_TYPES = new Class[0]; + + private Class<?> lastClass; + + private Method method; + private String methodName; + + DynamicMember(Expression operand, String name) { + super(operand, name, Expression.emptyArray); + if (!(name.startsWith(GET_PREFIX) || name.startsWith(IS_PREFIX))) + name = GET_PREFIX + Character.toUpperCase(name.charAt(0)) + name.substring(1); + this.methodName = name; + } + + public Object evaluate(IEvaluationContext context) { + return invoke(operand.evaluate(context)); + } + + public Object invoke(Object self) { + if (self == null) + throw new IllegalArgumentException("Cannot access member \'" + name + "\' in null"); //$NON-NLS-1$//$NON-NLS-2$ + + if (self instanceof MemberProvider) + return ((MemberProvider) self).getMember(name); + + Class<?> c = self.getClass(); + if (lastClass == null || !lastClass.isAssignableFrom(c)) { + Method m; + for (;;) { + try { + m = c.getMethod(methodName, NO_ARG_TYPES); + if (!Modifier.isPublic(m.getModifiers())) + throw new NoSuchMethodException(); + break; + } catch (NoSuchMethodException e) { + if (methodName.startsWith(GET_PREFIX)) + // Switch from using getXxx() to isXxx() + methodName = IS_PREFIX + Character.toUpperCase(name.charAt(0)) + name.substring(1); + else if (methodName.startsWith(IS_PREFIX)) + // Switch from using isXxx() to xxx() + methodName = name; + else + throw new IllegalArgumentException("Cannot find a public member \'" + name + "\' in a " + self.getClass().getName()); //$NON-NLS-1$//$NON-NLS-2$ + } + } + + // Since we already checked that it's public. This will speed + // up the calls a bit. + m.setAccessible(true); + lastClass = c; + method = m; + } + + Exception checked; + try { + return method.invoke(self, NO_ARGS); + } catch (IllegalArgumentException e) { + throw e; + } catch (IllegalAccessException e) { + checked = e; + } catch (InvocationTargetException e) { + Throwable cause = e.getTargetException(); + if (cause instanceof RuntimeException) + throw (RuntimeException) cause; + if (cause instanceof Error) + throw (Error) cause; + checked = (Exception) cause; + } + throw new RuntimeException("Problem invoking " + methodName + " on a " + self.getClass().getName(), checked); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + static final Object[] NO_ARGS = new Object[0]; + + static Member createDynamicMember(Expression operand, String name) { + return new DynamicMember(operand, name); + } + + protected final Expression[] argExpressions; + + final String name; + + protected Member(Expression operand, String name, Expression[] args) { + super(operand); + this.name = name; + this.argExpressions = args; + } + + public boolean accept(IExpressionVisitor visitor) { + if (super.accept(visitor)) + for (int idx = 0; idx < argExpressions.length; ++idx) + if (!argExpressions[idx].accept(visitor)) + return false; + return true; + } + + public int compareTo(Expression e) { + int cmp = super.compareTo(e); + if (cmp == 0) { + cmp = name.compareTo(((Member) e).name); + if (cmp == 0) + cmp = compare(argExpressions, ((Member) e).argExpressions); + } + return cmp; + } + + public boolean equals(Object o) { + if (super.equals(o)) { + Member mo = (Member) o; + return name.equals(mo.name) && equals(argExpressions, mo.argExpressions); + } + return false; + } + + public int getExpressionType() { + return TYPE_MEMBER; + } + + public String getName() { + return name; + } + + public String getOperator() { + return OPERATOR_MEMBER; + } + + public int getPriority() { + return PRIORITY_MEMBER; + } + + public int hashCode() { + int result = 31 + name.hashCode(); + result = 31 * result + operand.hashCode(); + return 31 * result + hashCode(argExpressions); + } + + public void toString(StringBuffer bld, Variable rootVariable) { + if (operand != rootVariable) { + appendOperand(bld, rootVariable, operand, getPriority()); + bld.append('.'); + } + bld.append(name); + if (argExpressions.length > 0) { + bld.append('('); + elementsToString(bld, rootVariable, argExpressions); + bld.append(')'); + } + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MemberProvider.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MemberProvider.java new file mode 100644 index 000000000..9eb460a8d --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/MemberProvider.java @@ -0,0 +1,139 @@ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import java.util.*; +import java.util.Map.Entry; +import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils; +import org.osgi.framework.ServiceReference; + +public abstract class MemberProvider { + + static class DictionaryMemberProvider extends MemberProvider { + private final Dictionary<String, ? extends Object> dictionary; + + public DictionaryMemberProvider(Dictionary<String, ? extends Object> dictionary) { + this.dictionary = dictionary; + } + + @Override + public Object getMember(String memberName) { + return dictionary.get(memberName); + } + } + + static class CIDictionaryMemberProvider extends DictionaryMemberProvider { + public CIDictionaryMemberProvider(Dictionary<String, ? extends Object> dictionary) { + super(lowerCaseKeys(dictionary)); + } + + @Override + public Object getMember(String memberName) { + return super.getMember(memberName == null ? null : memberName.toLowerCase()); + } + + private static Dictionary<String, ? extends Object> lowerCaseKeys(Dictionary<String, ? extends Object> dictionary) { + boolean hasUpperCase = false; + for (Enumeration<String> keys = dictionary.keys(); keys.hasMoreElements();) { + String key = keys.nextElement(); + if (key.toLowerCase() != key) { + hasUpperCase = true; + break; + } + } + if (!hasUpperCase) + return dictionary; + + Dictionary<String, Object> lcMap = new Hashtable<String, Object>(dictionary.size()); + for (Enumeration<String> keys = dictionary.keys(); keys.hasMoreElements();) { + String key = keys.nextElement(); + if (lcMap.put(key.toLowerCase(), dictionary.get(key)) != null) + throw new IllegalArgumentException("case variants of the same key name: '" + key + '\''); //$NON-NLS-1$ + } + return lcMap; + } + } + + static class MapMemberProvider extends MemberProvider { + private final Map<String, ? extends Object> map; + + public MapMemberProvider(Map<String, ? extends Object> map) { + this.map = map; + } + + @Override + public Object getMember(String memberName) { + return map.get(memberName); + } + } + + static class CIMapMemberProvider extends MapMemberProvider { + public CIMapMemberProvider(Map<String, ? extends Object> map) { + super(lowerCaseKeys(map)); + } + + @Override + public Object getMember(String memberName) { + return super.getMember(memberName == null ? null : memberName.toLowerCase()); + } + + private static Map<String, ? extends Object> lowerCaseKeys(Map<String, ? extends Object> map) { + boolean hasUpperCase = false; + Set<? extends Entry<String, ? extends Object>> entrySet = map.entrySet(); + for (Entry<String, ?> entry : entrySet) { + String key = entry.getKey(); + String lowKey = key.toLowerCase(); + if (key != lowKey) { + hasUpperCase = true; + break; + } + } + if (!hasUpperCase) + return map; + + Map<String, Object> lcMap = new HashMap<String, Object>(map.size()); + for (Entry<String, ?> entry : entrySet) { + if (lcMap.put(entry.getKey().toLowerCase(), entry.getValue()) != null) + throw new IllegalArgumentException("case variants of the same key name: '" + entry.getKey() + '\''); //$NON-NLS-1$ + } + return lcMap; + } + } + + static class ServiceRefMemberProvider extends MemberProvider { + private final ServiceReference serviceRef; + + public ServiceRefMemberProvider(ServiceReference serviceRef) { + this.serviceRef = serviceRef; + } + + @Override + public Object getMember(String memberName) { + return serviceRef.getProperty(memberName); + } + } + + private static final MemberProvider emptyProvider = create(CollectionUtils.emptyMap(), false); + + /** + * Create a new member provider on the given value. The value can be an instance of a {@link Map}, {@link Dictionary}, + * or {@link ServiceReference}. + * @param value The value that provides the members + * @param caseInsensitive <code>true</code> if the members should be retrievable in a case insensitive way. + * @return A member provided that is backed by <code>value</code>. + */ + @SuppressWarnings("unchecked") + public static MemberProvider create(Object value, boolean caseInsensitive) { + if (value instanceof Map<?, ?>) + return caseInsensitive ? new CIMapMemberProvider((Map<String, ?>) value) : new MapMemberProvider((Map<String, ?>) value); + if (value instanceof Dictionary<?, ?>) + return caseInsensitive ? new CIDictionaryMemberProvider((Dictionary<String, ?>) value) : new DictionaryMemberProvider((Dictionary<String, ?>) value); + if (value instanceof ServiceReference) + return new ServiceRefMemberProvider((ServiceReference) value); + throw new IllegalArgumentException(); + } + + public abstract Object getMember(String memberName); + + public static MemberProvider emptyProvider() { + return emptyProvider; + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/NAry.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/NAry.java new file mode 100644 index 000000000..805cff2ab --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/NAry.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2010 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor; + +/** + * The abstract baseclass for all N-ary expressions + */ +public abstract class NAry extends Expression { + public final Expression[] operands; + + protected NAry(Expression[] operands) { + this.operands = operands; + } + + public boolean accept(IExpressionVisitor visitor) { + if (super.accept(visitor)) + for (int idx = 0; idx < operands.length; ++idx) + if (!operands[idx].accept(visitor)) + return false; + return true; + } + + public int compareTo(Expression e) { + int cmp = super.compareTo(e); + if (cmp == 0) + cmp = compare(operands, ((NAry) e).operands); + return cmp; + } + + public boolean equals(Object o) { + return super.equals(o) && equals(operands, ((NAry) o).operands); + } + + public abstract String getOperator(); + + public int hashCode() { + return hashCode(operands); + } + + public void toString(StringBuffer bld, Variable rootVariable) { + appendOperand(bld, rootVariable, operands[0], getPriority()); + for (int idx = 1; idx < operands.length; ++idx) { + bld.append(' '); + bld.append(getOperator()); + bld.append(' '); + appendOperand(bld, rootVariable, operands[idx], getPriority()); + } + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Not.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Not.java new file mode 100644 index 000000000..5246859a3 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Not.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext; + +/** + * An expression that yields <code>true</code> when its operand does not. + */ +final class Not extends Unary { + Not(Expression operand) { + super(operand); + } + + public Object evaluate(IEvaluationContext context) { + return Boolean.valueOf(operand.evaluate(context) != Boolean.TRUE); + } + + public int getExpressionType() { + return TYPE_NOT; + } + + public String getOperator() { + return OPERATOR_NOT; + } + + public int getPriority() { + return PRIORITY_NOT; + } + + public int hashCode() { + return 3 * operand.hashCode(); + } + + public void toLDAPString(StringBuffer buf) { + buf.append("(!"); //$NON-NLS-1$ + operand.toLDAPString(buf); + buf.append(')'); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Or.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Or.java new file mode 100644 index 000000000..32805042e --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Or.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext; + +/** + * n-ary OR operator. The full evaluation is <code>false</code> if none of its operands + * evaluate to <code>true</code>. + */ +final class Or extends NAry { + public Or(Expression[] operands) { + super(assertLength(operands, 2, OPERATOR_OR)); + } + + public Object evaluate(IEvaluationContext context) { + for (int idx = 0; idx < operands.length; ++idx) { + if (operands[idx].evaluate(context) == Boolean.TRUE) + return Boolean.TRUE; + } + return Boolean.FALSE; + } + + public int getExpressionType() { + return TYPE_OR; + } + + public String getOperator() { + return OPERATOR_OR; + } + + public int getPriority() { + return PRIORITY_OR; + } + + public void toLDAPString(StringBuffer buf) { + buf.append("(|"); //$NON-NLS-1$ + for (int idx = 0; idx < operands.length; ++idx) + operands[idx].toLDAPString(buf); + buf.append(')'); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Parameter.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Parameter.java new file mode 100644 index 000000000..b4c56346e --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Parameter.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext; + +/** + * The abstract base class for the indexed and keyed parameters + */ +public class Parameter extends Expression { + final int position; + + Parameter(int position) { + this.position = position; + } + + public int compareTo(Expression e) { + int cmp = super.compareTo(e); + if (cmp == 0) + cmp = position - ((Parameter) e).position; + return cmp; + } + + public boolean equals(Object o) { + if (o == this) + return true; + if (o == null) + return false; + return getClass() == o.getClass() && position == ((Parameter) o).position; + } + + public Object evaluate(IEvaluationContext context) { + return context.getParameter(position); + } + + public int getExpressionType() { + return TYPE_PARAMETER; + } + + public String getOperator() { + return OPERATOR_PARAMETER; + } + + public int getPriority() { + return PRIORITY_VARIABLE; + } + + public int hashCode() { + return 31 * (1 + position); + } + + public void toString(StringBuffer bld, Variable rootVariable) { + bld.append('$'); + bld.append(position); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/RepeatableIterator.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/RepeatableIterator.java new file mode 100644 index 000000000..e44a4e507 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/RepeatableIterator.java @@ -0,0 +1,237 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import java.util.*; +import org.eclipse.equinox.p2.query.IQueryResult; + +public class RepeatableIterator<T> implements IRepeatableIterator<T> { + private final List<T> values; + private int position = -1; + + @SuppressWarnings("unchecked") + public static <T> IRepeatableIterator<T> create(Object unknown) { + if (unknown.getClass().isArray()) + return create((T[]) unknown); + if (unknown instanceof Iterator<?>) + return create((Iterator<T>) unknown); + if (unknown instanceof List<?>) + return create((List<T>) unknown); + if (unknown instanceof Collection<?>) + return create((Collection<T>) unknown); + if (unknown instanceof Map<?, ?>) + return create((Set<T>) ((Map<?, ?>) unknown).entrySet()); + if (unknown instanceof IQueryResult<?>) + return create((IQueryResult<T>) unknown); + throw new IllegalArgumentException("Cannot convert a " + unknown.getClass().getName() + " into an iterator"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public static <T> IRepeatableIterator<T> create(Iterator<T> iterator) { + return iterator instanceof IRepeatableIterator<?> ? ((IRepeatableIterator<T>) iterator).getCopy() : new ElementRetainingIterator<T>(iterator); + } + + public static <T> IRepeatableIterator<T> create(List<T> values) { + return new RepeatableIterator<T>(values); + } + + public static <T> IRepeatableIterator<T> create(Collection<T> values) { + return new CollectionIterator<T>(values); + } + + public static <T> IRepeatableIterator<T> create(IQueryResult<T> values) { + return new QueryResultIterator<T>(values); + } + + public static <T> IRepeatableIterator<T> create(T[] values) { + return new ArrayIterator<T>(values); + } + + RepeatableIterator(List<T> values) { + this.values = values; + } + + public IRepeatableIterator<T> getCopy() { + return new RepeatableIterator<T>(values); + } + + public boolean hasNext() { + return position + 1 < values.size(); + } + + public T next() { + if (++position == values.size()) { + --position; + throw new NoSuchElementException(); + } + return values.get(position); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public Object getIteratorProvider() { + return values; + } + + void setPosition(int position) { + this.position = position; + } + + List<T> getValues() { + return values; + } + + static class ArrayIterator<T> implements IRepeatableIterator<T> { + private final T[] array; + private int position = -1; + + public ArrayIterator(T[] array) { + this.array = array; + } + + public Object getIteratorProvider() { + return array; + } + + public boolean hasNext() { + return position + 1 < array.length; + } + + public T next() { + if (++position >= array.length) + throw new NoSuchElementException(); + return array[position]; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public IRepeatableIterator<T> getCopy() { + return new ArrayIterator<T>(array); + } + } + + static class CollectionIterator<T> implements IRepeatableIterator<T> { + private final Collection<T> collection; + + private final Iterator<T> iterator; + + CollectionIterator(Collection<T> collection) { + this.collection = collection; + this.iterator = collection.iterator(); + } + + public IRepeatableIterator<T> getCopy() { + return new CollectionIterator<T>(collection); + } + + public Object getIteratorProvider() { + return collection; + } + + public boolean hasNext() { + return iterator.hasNext(); + } + + public T next() { + return iterator.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + static class QueryResultIterator<T> implements IRepeatableIterator<T> { + private final IQueryResult<T> queryResult; + + private final Iterator<T> iterator; + + QueryResultIterator(IQueryResult<T> queryResult) { + this.queryResult = queryResult; + this.iterator = queryResult.iterator(); + } + + public IRepeatableIterator<T> getCopy() { + return new QueryResultIterator<T>(queryResult); + } + + public Object getIteratorProvider() { + return queryResult; + } + + public boolean hasNext() { + return iterator.hasNext(); + } + + public T next() { + return iterator.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + static class ElementRetainingIterator<T> extends RepeatableIterator<T> { + + private Iterator<T> innerIterator; + + ElementRetainingIterator(Iterator<T> iterator) { + super(new ArrayList<T>()); + innerIterator = iterator; + } + + public synchronized boolean hasNext() { + if (innerIterator != null) { + if (innerIterator.hasNext()) + return true; + innerIterator = null; + setPosition(getValues().size()); + } + return super.hasNext(); + } + + public synchronized T next() { + if (innerIterator != null) { + T val = innerIterator.next(); + getValues().add(val); + return val; + } + return super.next(); + } + + public synchronized IRepeatableIterator<T> getCopy() { + // If the current iterator still exists, we must exhaust it first + // + exhaustInnerIterator(); + return super.getCopy(); + } + + public synchronized Object getIteratorProvider() { + exhaustInnerIterator(); + return super.getIteratorProvider(); + } + + private void exhaustInnerIterator() { + if (innerIterator != null) { + List<T> values = getValues(); + int savePos = values.size() - 1; + while (innerIterator.hasNext()) + values.add(innerIterator.next()); + innerIterator = null; + setPosition(savePos); + } + } + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Unary.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Unary.java new file mode 100644 index 000000000..9c6b7d8a5 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Unary.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext; +import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor; + +/** + * The abstract base class for all unary expressions + */ +public abstract class Unary extends Expression { + public final Expression operand; + + protected Unary(Expression operand) { + this.operand = operand; + } + + public boolean accept(IExpressionVisitor visitor) { + return super.accept(visitor) && operand.accept(visitor); + } + + public int compareTo(Expression e) { + int cmp = super.compareTo(e); + if (cmp == 0) + cmp = operand.compareTo(((Unary) e).operand); + return cmp; + } + + public boolean equals(Object o) { + return super.equals(o) && operand.equals(((Unary) o).operand); + } + + public Object evaluate(IEvaluationContext context) { + return operand.evaluate(context); + } + + public int hashCode() { + return operand.hashCode() * 3 + operand.getExpressionType(); + } + + public Expression getOperand() { + return operand; + } + + public void toString(StringBuffer bld, Variable rootVariable) { + bld.append(getOperator()); + appendOperand(bld, rootVariable, operand, getPriority()); + } +}
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Variable.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Variable.java new file mode 100644 index 000000000..a7adfb738 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Variable.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression; + +import java.util.Iterator; +import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext; + +/** + * An expression representing a variable stack in the current thread. + */ +public class Variable extends Expression { + + private final String name; + + public Variable(String name) { + this.name = name; + } + + public int compareTo(Expression e) { + int cmp = super.compareTo(e); + if (cmp == 0) + cmp = name.compareTo(((Variable) e).name); + return cmp; + } + + public boolean equals(Object o) { + return super.equals(o) && name.equals(((Variable) o).name); + } + + public final Object evaluate(IEvaluationContext context) { + return context.getValue(this); + } + + public Iterator<?> evaluateAsIterator(IEvaluationContext context) { + Object value = context.getValue(this); + if (value instanceof IRepeatableIterator<?>) + return ((IRepeatableIterator<?>) value).getCopy(); + + Iterator<?> itor = RepeatableIterator.create(value); + setValue(context, itor); + return itor; + } + + public int getExpressionType() { + return TYPE_VARIABLE; + } + + public String getName() { + return name; + } + + public String getOperator() { + return "<variable>"; //$NON-NLS-1$ + } + + public int getPriority() { + return PRIORITY_VARIABLE; + } + + public int hashCode() { + return name.hashCode(); + } + + public final void setValue(IEvaluationContext context, Object value) { + context.setValue(this, value); + } + + public void toString(StringBuffer bld, Variable rootVariable) { + bld.append(name); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/ExpressionParser.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/ExpressionParser.java new file mode 100644 index 000000000..905d0927c --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/ExpressionParser.java @@ -0,0 +1,617 @@ +/******************************************************************************* + * Copyright (c) 2010 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression.parser; + +import org.eclipse.equinox.internal.p2.metadata.expression.LDAPApproximation; + +import java.util.*; +import org.eclipse.equinox.internal.p2.metadata.expression.IExpressionConstants; +import org.eclipse.equinox.p2.metadata.expression.*; + +public class ExpressionParser extends Stack<IExpression> implements IExpressionConstants, IExpressionParser { + private static final long serialVersionUID = 5481439062356612378L; + + protected static final int TOKEN_OR = 1; + protected static final int TOKEN_AND = 2; + + protected static final int TOKEN_EQUAL = 10; + protected static final int TOKEN_NOT_EQUAL = 11; + protected static final int TOKEN_LESS = 12; + protected static final int TOKEN_LESS_EQUAL = 13; + protected static final int TOKEN_GREATER = 14; + protected static final int TOKEN_GREATER_EQUAL = 15; + protected static final int TOKEN_MATCHES = 16; + + protected static final int TOKEN_NOT = 20; + protected static final int TOKEN_DOT = 21; + protected static final int TOKEN_COMMA = 22; + protected static final int TOKEN_PIPE = 23; + protected static final int TOKEN_DOLLAR = 24; + protected static final int TOKEN_IF = 25; + protected static final int TOKEN_ELSE = 26; + + protected static final int TOKEN_LP = 30; + protected static final int TOKEN_RP = 31; + protected static final int TOKEN_LB = 32; + protected static final int TOKEN_RB = 33; + protected static final int TOKEN_LC = 34; + protected static final int TOKEN_RC = 35; + + protected static final int TOKEN_IDENTIFIER = 40; + protected static final int TOKEN_LITERAL = 41; + + protected static final int TOKEN_NULL = 50; + protected static final int TOKEN_TRUE = 51; + protected static final int TOKEN_FALSE = 52; + + private static final int TOKEN_ALL = 60; + private static final int TOKEN_EXISTS = 61; + + protected static final int TOKEN_END = 0; + protected static final int TOKEN_ERROR = -1; + + protected static final Map<String, Integer> keywords; + static { + keywords = new HashMap<String, Integer>(); + keywords.put(KEYWORD_FALSE, new Integer(TOKEN_FALSE)); + keywords.put(KEYWORD_NULL, new Integer(TOKEN_NULL)); + keywords.put(KEYWORD_TRUE, new Integer(TOKEN_TRUE)); + keywords.put(KEYWORD_ALL, new Integer(TOKEN_ALL)); + keywords.put(KEYWORD_EXISTS, new Integer(TOKEN_EXISTS)); + } + + protected final IExpressionFactory factory; + + protected String expression; + protected int tokenPos; + protected int currentToken; + protected int lastTokenPos; + protected Object tokenValue; + protected String rootVariable; + + public ExpressionParser(IExpressionFactory factory) { + this.factory = factory; + } + + public synchronized IExpression parse(String exprString) { + expression = exprString; + tokenPos = 0; + currentToken = 0; + tokenValue = null; + IExpression thisVariable = factory.thisVariable(); + rootVariable = ExpressionUtil.getName(thisVariable); + push(thisVariable); + try { + nextToken(); + IExpression expr = currentToken == TOKEN_END ? factory.constant(Boolean.TRUE) : parseCondition(); + assertToken(TOKEN_END); + return expr; + } finally { + if (thisVariable != null) + popVariable(); // pop item + } + } + + protected Map<String, Integer> keywordToTokenMap() { + return keywords; + } + + protected IExpression parseCondition() { + // Just a hook in this parser. Conditions are not supported + return parseOr(); + } + + protected IExpression parseOr() { + IExpression expr = parseAnd(); + if (currentToken != TOKEN_OR) + return expr; + + ArrayList<IExpression> exprs = new ArrayList<IExpression>(); + exprs.add(expr); + do { + nextToken(); + exprs.add(parseAnd()); + } while (currentToken == TOKEN_OR); + return factory.or(exprs.toArray(new IExpression[exprs.size()])); + } + + protected IExpression parseAnd() { + IExpression expr = parseBinary(); + if (currentToken != TOKEN_AND) + return expr; + + ArrayList<IExpression> exprs = new ArrayList<IExpression>(); + exprs.add(expr); + do { + nextToken(); + exprs.add(parseBinary()); + } while (currentToken == TOKEN_AND); + return factory.and(exprs.toArray(new IExpression[exprs.size()])); + } + + protected IExpression parseBinary() { + IExpression expr = parseNot(); + for (;;) { + switch (currentToken) { + case TOKEN_OR : + case TOKEN_AND : + case TOKEN_RP : + case TOKEN_RB : + case TOKEN_RC : + case TOKEN_COMMA : + case TOKEN_IF : + case TOKEN_ELSE : + case TOKEN_END : + break; + case TOKEN_EQUAL : + case TOKEN_NOT_EQUAL : + case TOKEN_GREATER : + case TOKEN_GREATER_EQUAL : + case TOKEN_LESS : + case TOKEN_LESS_EQUAL : + case TOKEN_MATCHES : + int realToken = currentToken; + nextToken(); + IExpression rhs; + if (realToken == TOKEN_MATCHES && currentToken == TOKEN_LITERAL && tokenValue instanceof String) + rhs = factory.constant(new LDAPApproximation((String) tokenValue)); + else + rhs = parseNot(); + switch (realToken) { + case TOKEN_EQUAL : + expr = factory.equals(expr, rhs); + break; + case TOKEN_NOT_EQUAL : + expr = factory.not(factory.equals(expr, rhs)); + break; + case TOKEN_GREATER : + expr = factory.greater(expr, rhs); + break; + case TOKEN_GREATER_EQUAL : + expr = factory.greaterEqual(expr, rhs); + break; + case TOKEN_LESS : + expr = factory.less(expr, rhs); + break; + case TOKEN_LESS_EQUAL : + expr = factory.lessEqual(expr, rhs); + break; + default : + expr = factory.matches(expr, rhs); + } + continue; + default : + throw syntaxError(); + } + break; + } + return expr; + } + + protected IExpression parseNot() { + if (currentToken == TOKEN_NOT) { + nextToken(); + IExpression expr = parseNot(); + return factory.not(expr); + } + return parseCollectionExpression(); + } + + protected IExpression parseCollectionExpression() { + IExpression expr = parseCollectionLHS(); + if (expr == null) { + expr = parseMember(); + if (currentToken != TOKEN_DOT) + return expr; + nextToken(); + } + for (;;) { + int funcToken = currentToken; + nextToken(); + assertToken(TOKEN_LP); + nextToken(); + expr = parseCollectionRHS(expr, funcToken); + if (currentToken != TOKEN_DOT) + break; + nextToken(); + } + return expr; + } + + protected IExpression parseCollectionLHS() { + IExpression expr = null; + switch (currentToken) { + case TOKEN_EXISTS : + case TOKEN_ALL : + expr = getVariableOrRootMember(rootVariable); + break; + } + return expr; + } + + protected IExpression parseCollectionRHS(IExpression expr, int funcToken) { + switch (funcToken) { + case TOKEN_EXISTS : + expr = factory.exists(expr, parseLambdaDefinition()); + break; + case TOKEN_ALL : + expr = factory.all(expr, parseLambdaDefinition()); + break; + default : + throw syntaxError(); + } + return expr; + } + + protected IExpression parseLambdaDefinition() { + assertToken(TOKEN_IDENTIFIER); + IExpression each = factory.variable((String) tokenValue); + push(each); + try { + nextToken(); + assertToken(TOKEN_PIPE); + nextToken(); + IExpression body = parseCondition(); + assertToken(TOKEN_RP); + nextToken(); + return factory.lambda(each, body); + } finally { + pop(); + } + } + + protected IExpression parseMember() { + IExpression expr = parseUnary(); + String name; + while (currentToken == TOKEN_DOT || currentToken == TOKEN_LB) { + int savePos = tokenPos; + int saveToken = currentToken; + Object saveTokenValue = tokenValue; + nextToken(); + if (saveToken == TOKEN_DOT) { + switch (currentToken) { + case TOKEN_IDENTIFIER : + name = (String) tokenValue; + nextToken(); + expr = factory.member(expr, name); + break; + + default : + tokenPos = savePos; + currentToken = saveToken; + tokenValue = saveTokenValue; + return expr; + } + } else { + IExpression atExpr = parseMember(); + assertToken(TOKEN_RB); + nextToken(); + expr = factory.at(expr, atExpr); + } + } + return expr; + } + + protected IExpression parseUnary() { + IExpression expr; + switch (currentToken) { + case TOKEN_LP : + nextToken(); + expr = parseCondition(); + assertToken(TOKEN_RP); + nextToken(); + break; + case TOKEN_LITERAL : + expr = factory.constant(tokenValue); + nextToken(); + break; + case TOKEN_IDENTIFIER : + expr = getVariableOrRootMember((String) tokenValue); + nextToken(); + break; + case TOKEN_NULL : + expr = factory.constant(null); + nextToken(); + break; + case TOKEN_TRUE : + expr = factory.constant(Boolean.TRUE); + nextToken(); + break; + case TOKEN_FALSE : + expr = factory.constant(Boolean.FALSE); + nextToken(); + break; + case TOKEN_DOLLAR : + expr = parseParameter(); + break; + default : + throw syntaxError(); + } + return expr; + } + + private IExpression parseParameter() { + if (currentToken == TOKEN_DOLLAR) { + nextToken(); + if (currentToken == TOKEN_LITERAL && tokenValue instanceof Integer) { + IExpression param = factory.indexedParameter(((Integer) tokenValue).intValue()); + nextToken(); + return param; + } + } + throw syntaxError(); + } + + protected IExpression[] parseArray() { + IExpression expr = parseCondition(); + if (currentToken != TOKEN_COMMA) + return new IExpression[] {expr}; + + ArrayList<IExpression> operands = new ArrayList<IExpression>(); + operands.add(expr); + do { + nextToken(); + if (currentToken == TOKEN_LC) + // We don't allow lambdas in the array + break; + operands.add(parseCondition()); + } while (currentToken == TOKEN_COMMA); + return operands.toArray(new IExpression[operands.size()]); + } + + protected void assertToken(int token) { + if (currentToken != token) + throw syntaxError(); + } + + protected IExpression getVariableOrRootMember(String id) { + int idx = size(); + while (--idx >= 0) { + IExpression v = get(idx); + if (id.equals(v.toString())) + return v; + } + + if (rootVariable == null || rootVariable.equals(id)) + throw syntaxError("No such variable: " + id); //$NON-NLS-1$ + + return factory.member(getVariableOrRootMember(rootVariable), id); + } + + protected void nextToken() { + tokenValue = null; + int top = expression.length(); + char c = 0; + while (tokenPos < top) { + c = expression.charAt(tokenPos); + if (!Character.isWhitespace(c)) + break; + ++tokenPos; + } + if (tokenPos >= top) { + lastTokenPos = top; + currentToken = TOKEN_END; + return; + } + + lastTokenPos = tokenPos; + switch (c) { + case '|' : + if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '|') { + tokenValue = OPERATOR_OR; + currentToken = TOKEN_OR; + tokenPos += 2; + } else { + currentToken = TOKEN_PIPE; + ++tokenPos; + } + break; + + case '&' : + if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '&') { + tokenValue = OPERATOR_AND; + currentToken = TOKEN_AND; + tokenPos += 2; + } else + currentToken = TOKEN_ERROR; + break; + + case '=' : + if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') { + tokenValue = OPERATOR_EQUALS; + currentToken = TOKEN_EQUAL; + tokenPos += 2; + } else + currentToken = TOKEN_ERROR; + break; + + case '!' : + if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') { + tokenValue = OPERATOR_NOT_EQUALS; + currentToken = TOKEN_NOT_EQUAL; + tokenPos += 2; + } else { + currentToken = TOKEN_NOT; + ++tokenPos; + } + break; + + case '~' : + if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') { + tokenValue = OPERATOR_MATCHES; + currentToken = TOKEN_MATCHES; + tokenPos += 2; + } else + currentToken = TOKEN_ERROR; + break; + + case '>' : + if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') { + tokenValue = OPERATOR_GT_EQUAL; + currentToken = TOKEN_GREATER_EQUAL; + tokenPos += 2; + } else { + currentToken = TOKEN_GREATER; + ++tokenPos; + } + break; + + case '<' : + if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') { + tokenValue = OPERATOR_LT_EQUAL; + currentToken = TOKEN_LESS_EQUAL; + tokenPos += 2; + } else { + currentToken = TOKEN_LESS; + ++tokenPos; + } + break; + + case '?' : + currentToken = TOKEN_IF; + ++tokenPos; + break; + + case ':' : + currentToken = TOKEN_ELSE; + ++tokenPos; + break; + + case '.' : + currentToken = TOKEN_DOT; + ++tokenPos; + break; + + case '$' : + currentToken = TOKEN_DOLLAR; + ++tokenPos; + break; + + case '{' : + currentToken = TOKEN_LC; + ++tokenPos; + break; + + case '}' : + currentToken = TOKEN_RC; + ++tokenPos; + break; + + case '(' : + currentToken = TOKEN_LP; + ++tokenPos; + break; + + case ')' : + currentToken = TOKEN_RP; + ++tokenPos; + break; + + case '[' : + currentToken = TOKEN_LB; + ++tokenPos; + break; + + case ']' : + currentToken = TOKEN_RB; + ++tokenPos; + break; + + case ',' : + currentToken = TOKEN_COMMA; + ++tokenPos; + break; + + case '"' : + case '\'' : { + int start = ++tokenPos; + while (tokenPos < top && expression.charAt(tokenPos) != c) + ++tokenPos; + if (tokenPos == top) { + tokenPos = start - 1; + currentToken = TOKEN_ERROR; + } else { + tokenValue = expression.substring(start, tokenPos++); + currentToken = TOKEN_LITERAL; + } + break; + } + + case '/' : { + int start = ++tokenPos; + StringBuffer buf = new StringBuffer(); + while (tokenPos < top) { + c = expression.charAt(tokenPos); + if (c == '\\' && tokenPos + 1 < top) { + c = expression.charAt(++tokenPos); + if (c != '/') + buf.append('\\'); + } else if (c == '/') + break; + buf.append(c); + ++tokenPos; + } + if (tokenPos == top) { + tokenPos = start - 1; + currentToken = TOKEN_ERROR; + } else { + tokenValue = SimplePattern.compile(expression.substring(start, tokenPos++)); + currentToken = TOKEN_LITERAL; + } + break; + } + + default : + if (Character.isDigit(c)) { + int start = tokenPos++; + while (tokenPos < top && Character.isDigit(expression.charAt(tokenPos))) + ++tokenPos; + tokenValue = Integer.valueOf(expression.substring(start, tokenPos)); + currentToken = TOKEN_LITERAL; + break; + } + if (Character.isJavaIdentifierStart(c)) { + int start = tokenPos++; + while (tokenPos < top && Character.isJavaIdentifierPart(expression.charAt(tokenPos))) + ++tokenPos; + String word = expression.substring(start, tokenPos); + Integer token = keywordToTokenMap().get(word); + if (token == null) + currentToken = TOKEN_IDENTIFIER; + else + currentToken = token.intValue(); + tokenValue = word; + break; + } + throw syntaxError(); + } + } + + protected void popVariable() { + if (isEmpty()) + throw syntaxError(); + pop(); + } + + protected ExpressionParseException syntaxError() { + Object tv = tokenValue; + if (tv == null) { + if (lastTokenPos >= expression.length()) + return syntaxError("Unexpeced end of expression"); //$NON-NLS-1$ + tv = expression.substring(lastTokenPos, lastTokenPos + 1); + } + return syntaxError("Unexpected token \"" + tv + '"'); //$NON-NLS-1$ + } + + protected ExpressionParseException syntaxError(String message) { + return new ExpressionParseException(expression, message, tokenPos); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/LDAPFilterParser.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/LDAPFilterParser.java new file mode 100644 index 000000000..b5fd6714c --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/parser/LDAPFilterParser.java @@ -0,0 +1,268 @@ +/******************************************************************************* + * Copyright (c) 2010 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata.expression.parser; + +import java.util.ArrayList; +import org.eclipse.equinox.internal.p2.metadata.Messages; +import org.eclipse.equinox.internal.p2.metadata.expression.IExpressionConstants; +import org.eclipse.equinox.internal.p2.metadata.expression.LDAPApproximation; +import org.eclipse.equinox.p2.metadata.expression.*; +import org.eclipse.osgi.util.NLS; + +/** + * Parser class for OSGi filter strings. This class parses the complete filter string and builds a tree of Filter + * objects rooted at the parent. + */ +public class LDAPFilterParser { + private final IExpressionFactory factory; + + private final IExpression self; + + private final StringBuffer sb = new StringBuffer(); + + private String filterString; + + private int position; + + public LDAPFilterParser(IExpressionFactory factory) { + this.factory = factory; + self = factory.variable(IExpressionConstants.VARIABLE_THIS); + position = 0; + } + + public synchronized IFilterExpression parse(String filter) { + filterString = filter; + position = 0; + try { + IExpression expr = parseFilter(); + if (position != filterString.length()) + throw syntaxException(Messages.filter_trailing_characters); + return factory.filterExpression(expr); + } catch (StringIndexOutOfBoundsException e) { + throw syntaxException(Messages.filter_premature_end); + } + } + + private IExpression parseAnd() { + skipWhiteSpace(); + char c = filterString.charAt(position); + if (c != '(') + throw syntaxException(Messages.filter_missing_leftparen); + + ArrayList<IExpression> operands = new ArrayList<IExpression>(); + while (c == '(') { + IExpression child = parseFilter(); + if (!operands.contains(child)) + operands.add(child); + c = filterString.charAt(position); + } + // int sz = operands.size(); + // return sz == 1 ? operands.get(0) : factory.and(operands.toArray(new IExpression[sz])); + return factory.normalize(operands, IExpression.TYPE_AND); + } + + private IExpression parseAttr() { + skipWhiteSpace(); + + int begin = position; + int end = position; + + char c = filterString.charAt(begin); + while (!(c == '~' || c == '<' || c == '>' || c == '=' || c == '(' || c == ')')) { + position++; + if (!Character.isWhitespace(c)) + end = position; + c = filterString.charAt(position); + } + if (end == begin) + throw syntaxException(Messages.filter_missing_attr); + return factory.member(self, filterString.substring(begin, end)); + } + + private IExpression parseFilter() { + IExpression filter; + skipWhiteSpace(); + + if (filterString.charAt(position) != '(') + throw syntaxException(Messages.filter_missing_leftparen); + + position++; + filter = parseFiltercomp(); + + skipWhiteSpace(); + + if (filterString.charAt(position) != ')') + throw syntaxException(Messages.filter_missing_rightparen); + + position++; + skipWhiteSpace(); + + return filter; + } + + private IExpression parseFiltercomp() { + skipWhiteSpace(); + + char c = filterString.charAt(position); + + switch (c) { + case '&' : { + position++; + return parseAnd(); + } + case '|' : { + position++; + return parseOr(); + } + case '!' : { + position++; + return parseNot(); + } + } + return parseItem(); + } + + private IExpression parseItem() { + IExpression attr = parseAttr(); + + skipWhiteSpace(); + String value; + + boolean[] hasWild = {false}; + char c = filterString.charAt(position); + switch (c) { + case '~' : + case '>' : + case '<' : + if (filterString.charAt(position + 1) != '=') + throw syntaxException(Messages.filter_invalid_operator); + position += 2; + int savePos = position; + value = parseValue(hasWild); + if (hasWild[0]) { + // Unescaped wildcard found. This is not legal for the given operator + position = savePos; + throw syntaxException(Messages.filter_invalid_value); + } + switch (c) { + case '>' : + return factory.greaterEqual(attr, factory.constant(value)); + case '<' : + return factory.lessEqual(attr, factory.constant(value)); + } + return factory.matches(attr, factory.constant(new LDAPApproximation(value))); + case '=' : + position++; + value = parseValue(hasWild); + return hasWild[0] ? factory.matches(attr, factory.constant(SimplePattern.compile(value))) : factory.equals(attr, factory.constant(value)); + } + throw syntaxException(Messages.filter_invalid_operator); + } + + private IExpression parseNot() { + skipWhiteSpace(); + + if (filterString.charAt(position) != '(') + throw syntaxException(Messages.filter_missing_leftparen); + return factory.not(parseFilter()); + } + + private IExpression parseOr() { + skipWhiteSpace(); + char c = filterString.charAt(position); + if (c != '(') + throw syntaxException(Messages.filter_missing_leftparen); + + ArrayList<IExpression> operands = new ArrayList<IExpression>(); + while (c == '(') { + IExpression child = parseFilter(); + operands.add(child); + c = filterString.charAt(position); + } + // int sz = operands.size(); + // return sz == 1 ? operands.get(0) : factory.or(operands.toArray(new IExpression[sz])); + return factory.normalize(operands, IExpression.TYPE_OR); + } + + private static int hexValue(char c) { + int v; + if (c <= '9') + v = c - '0'; + else if (c <= 'F') + v = (c - 'A') + 10; + else + v = (c - 'a') + 10; + return v; + } + + private String parseValue(boolean[] hasWildBin) { + sb.setLength(0); + int savePos = position; + boolean hasEscapedWild = false; + parseloop: while (true) { + char c = filterString.charAt(position); + switch (c) { + case '*' : + if (hasEscapedWild && !hasWildBin[0]) { + // We must redo the parse. + position = savePos; + hasWildBin[0] = true; + return parseValue(hasWildBin); + } + hasWildBin[0] = true; + sb.append(c); + position++; + break; + + case ')' : + break parseloop; + + case '(' : + throw syntaxException(Messages.filter_invalid_value); + + case '\\' : + c = filterString.charAt(++position); + if (c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f' && position + 1 < filterString.length()) { + char nc = filterString.charAt(position + 1); + if (nc >= '0' && nc <= '9' || nc >= 'A' && nc <= 'F' || nc >= 'a' && nc <= 'f') { + // Assume proper \xx escape where xx are hex digits + ++position; + c = (char) (((hexValue(c) << 4) & 0xf0) | (hexValue(nc) & 0x0f)); + if (c == '*' && hasWildBin != null) { + hasEscapedWild = true; + if (hasWildBin[0]) + sb.append('\\'); + } + } + } + /* fall through into default */ + + default : + sb.append(c); + position++; + break; + } + } + if (sb.length() == 0) + throw syntaxException(Messages.filter_missing_value); + return sb.toString(); + } + + private void skipWhiteSpace() { + for (int top = filterString.length(); position < top; ++position) + if (!Character.isWhitespace(filterString.charAt(position))) + break; + } + + protected ExpressionParseException syntaxException(String message) { + return new ExpressionParseException(NLS.bind(message, filterString, Integer.toString(position))); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/messages.properties b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/messages.properties index 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; + } +} |