diff options
author | Pascal Rapicault | 2010-01-13 17:29:41 +0000 |
---|---|---|
committer | Pascal Rapicault | 2010-01-13 17:29:41 +0000 |
commit | 18189f0d42f7375660762dc6c885cf31683ae562 (patch) | |
tree | 17775d847bed9a33f3c68b74db2df75a2139c0bc /bundles/org.eclipse.equinox.p2.metadata/src/org | |
parent | c363f2984a09b73c422e38f4556fd3b23eafe958 (diff) | |
download | rt.equinox.p2-18189f0d42f7375660762dc6c885cf31683ae562.tar.gz rt.equinox.p2-18189f0d42f7375660762dc6c885cf31683ae562.tar.xz rt.equinox.p2-18189f0d42f7375660762dc6c885cf31683ae562.zip |
Merging api branch back to HEADv20100113
Diffstat (limited to 'bundles/org.eclipse.equinox.p2.metadata/src/org')
119 files changed, 8928 insertions, 2948 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/provisional/p2/metadata/VersionFormat.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionFormatParser.java index 5de45b418..83170c9a7 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/VersionFormat.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionFormatParser.java @@ -8,25 +8,46 @@ * Contributors: * Cloudsmith Inc. - initial API and implementation *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; +package org.eclipse.equinox.internal.p2.metadata; -import org.eclipse.equinox.internal.p2.metadata.Messages; +import org.eclipse.equinox.p2.metadata.VersionFormatException; import java.io.Serializable; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import org.eclipse.equinox.internal.p2.metadata.VersionFormat.TreeInfo; 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. + * 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)} + * */ -public class VersionFormat implements Serializable { - private static final long serialVersionUID = 6888925893926932754L; +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.) @@ -58,7 +79,7 @@ public class VersionFormat implements Serializable { return sb.toString(); } - Comparable getDefaultValue() { + Comparable<?> getDefaultValue() { return null; } @@ -66,7 +87,7 @@ public class VersionFormat implements Serializable { return this; } - Comparable getPadValue() { + Comparable<?> getPadValue() { return null; } @@ -74,13 +95,13 @@ public class VersionFormat implements Serializable { return qualifier; } - boolean parse(List segments, String version, int maxPos, TreeInfo info) { + 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 segments, String version, int maxPos, TreeInfo info); + abstract boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info); - void setDefaults(List segments) { + void setDefaults(List<Comparable<?>> segments) { // No-op at this level } @@ -131,7 +152,7 @@ public class VersionFormat implements Serializable { return min; } - boolean parse(Fragment[] fragments, int fragIdx, List segments, String version, int maxPos, TreeInfo info) { + boolean parse(Fragment[] fragments, int fragIdx, List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { Fragment fragment = fragments[fragIdx++]; int idx = 0; @@ -157,9 +178,22 @@ public class VersionFormat implements Serializable { for (;;) { // Pad with default values unless the max is unbounded // - if (max != Integer.MAX_VALUE) { - for (; idx < max; ++idx) - fragment.setDefaults(segments); + 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) @@ -242,7 +276,7 @@ public class VersionFormat implements Serializable { super(instr, qualifier); } - boolean parseOne(List segments, String version, int maxPos, TreeInfo info) { + boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { int pos = info.getPosition(); maxPos = checkRange(pos, maxPos); if (maxPos < 0) @@ -266,7 +300,7 @@ public class VersionFormat implements Serializable { return false; if (!isIgnored()) - segments.add(Version.valueOf(value)); + segments.add(VersionParser.valueOf(value)); info.setPosition(pos); return true; } @@ -327,7 +361,7 @@ public class VersionFormat implements Serializable { return true; } - boolean parseOne(List segments, String version, int maxPos, TreeInfo info) { + 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. @@ -346,15 +380,86 @@ public class VersionFormat implements Serializable { } } + 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 Comparable<?> defaultValue; private final boolean ignored; - private final Comparable padValue; + private final Comparable<?> padValue; ElementFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) { super(qualifier); @@ -369,11 +474,11 @@ public class VersionFormat implements Serializable { } } - Comparable getDefaultValue() { + Comparable<?> getDefaultValue() { return defaultValue; } - Comparable getPadValue() { + Comparable<?> getPadValue() { return padValue; } @@ -381,8 +486,8 @@ public class VersionFormat implements Serializable { return ignored; } - void setDefaults(List segments) { - Object defaultVal = getDefaultValue(); + void setDefaults(List<Comparable<?>> segments) { + Comparable<?> defaultVal = getDefaultValue(); if (defaultVal != null) segments.add(defaultVal); } @@ -395,13 +500,13 @@ public class VersionFormat implements Serializable { } if (defaultValue != null) { sb.append('='); - VersionVector.rawToString(sb, false, defaultValue); + VersionFormat.rawToString(sb, false, defaultValue); sb.append(';'); } if (padValue != null) { sb.append('='); sb.append('p'); - VersionVector.rawToString(sb, false, padValue); + VersionFormat.rawToString(sb, false, padValue); sb.append(';'); } super.toString(sb); @@ -427,26 +532,26 @@ public class VersionFormat implements Serializable { return fragments[0].getFirstLeaf(); } - boolean parseOne(List segments, String version, int maxPos, TreeInfo info) { + boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { if (array) { - ArrayList subSegs = new ArrayList(); + 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(); + Comparable<?> padValue = info.getPadValue(); if (padValue != null) info.setPadValue(null); // Prevent outer group from getting this. else padValue = getPadValue(); - VersionParser.removeRedundantTrail(segments, padValue); - segments.add(new VersionVector((Comparable[]) subSegs.toArray(new Comparable[subSegs.size()]), padValue)); + 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(); + Comparable<?> padValue = getPadValue(); if (padValue != null) info.setPadValue(padValue); return true; @@ -454,8 +559,8 @@ public class VersionFormat implements Serializable { return false; } - void setDefaults(List segments) { - Comparable dflt = getDefaultValue(); + void setDefaults(List<Comparable<?>> segments) { + Comparable<?> dflt = getDefaultValue(); if (dflt != null) { // A group default overrides any defaults within the // group fragments @@ -499,7 +604,7 @@ public class VersionFormat implements Serializable { this.string = string; } - boolean parseOne(List segments, String version, int maxPos, TreeInfo info) { + boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { int pos = info.getPosition(); int litLen = string.length(); if (pos + litLen > maxPos) @@ -517,7 +622,7 @@ public class VersionFormat implements Serializable { String str = string; if (str.length() != 1) { sb.append('\''); - toStringEscaped(sb, str, "\'"); //$NON-NLS-1$ + VersionFormatParser.toStringEscaped(sb, str, "\'"); //$NON-NLS-1$ sb.append('\''); } else { char c = str.charAt(0); @@ -556,7 +661,7 @@ public class VersionFormat implements Serializable { this.signed = signed; } - boolean parseOne(List segments, String version, int maxPos, TreeInfo info) { + boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { int pos = info.getPosition(); maxPos = checkRange(pos, maxPos); if (maxPos < 0) @@ -611,7 +716,7 @@ public class VersionFormat implements Serializable { return false; if (!isIgnored()) - segments.add(Version.valueOf(value)); + segments.add(VersionParser.valueOf(value)); info.setPosition(pos); return true; } @@ -629,13 +734,13 @@ public class VersionFormat implements Serializable { super(null, qualifier); } - boolean parseOne(List segments, String version, int maxPos, TreeInfo info) { + 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); + Comparable<?> v = VersionParser.parseRawElement(version, position, maxPos); if (v == null) return false; @@ -658,7 +763,7 @@ public class VersionFormat implements Serializable { super(instr, qualifier); } - boolean parseOne(List segments, String version, int maxPos, TreeInfo info) { + boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { int pos = info.getPosition(); if (pos >= maxPos) return false; @@ -809,9 +914,9 @@ public class VersionFormat implements Serializable { super(processing, qualifier); } - boolean parseOne(List segments, String version, int maxPos, TreeInfo info) { + boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { int[] position = new int[] {info.getPosition()}; - Comparable v = VersionParser.parseRawElement(version, position, maxPos); + Comparable<?> v = VersionParser.parseRawElement(version, position, maxPos); if (v == null) return false; @@ -830,13 +935,52 @@ public class VersionFormat implements Serializable { 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 segments, String version, int maxPos, TreeInfo info) { + boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { int pos = info.getPosition(); maxPos = checkRange(pos, maxPos); if (maxPos < 0) @@ -888,360 +1032,517 @@ public class VersionFormat implements Serializable { } } - private static class TreeInfo extends ArrayList { - private static final long serialVersionUID = 4770093863009659750L; + private int current; - private static class StateInfo { - Fragment fragment; - int segmentCount; - int position; + private List<Fragment> currentList; - StateInfo(int position, int segmentCount, Fragment fragment) { - this.fragment = fragment; - this.position = position; - this.segmentCount = segmentCount; - } - } + private int eos; - private Comparable padValue; - private int top; + private String format; - TreeInfo(Fragment frag, int pos) { - add(new StateInfo(pos, 0, frag)); - top = 0; - } + private int start; - Comparable getPadValue() { - return padValue; - } + Fragment compile(String fmt, int pos, int maxPos) throws VersionFormatException { + format = fmt; + if (start >= maxPos) + throw new VersionFormatException(Messages.format_is_empty); - int getPosition() { - return ((StateInfo) get(top)).position; - } + start = pos; + current = pos; + eos = maxPos; + currentList = new ArrayList<Fragment>(); + while (current < eos) + parseFragment(); - void popState(List segments, Fragment frag) { - int idx = top; - while (idx > 0) { - StateInfo si = (StateInfo) get(idx); - if (si.fragment == frag) { - int nsegs = segments.size(); - int segMax = si.segmentCount; - while (nsegs > segMax) - segments.remove(--nsegs); - top = idx - 1; + 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; } - } - } - - void pushState(int segCount, Fragment fragment) { - int pos = ((StateInfo) get(top)).position; - if (++top == size()) - add(new StateInfo(pos, segCount, fragment)); - else { - StateInfo si = (StateInfo) get(top); - si.fragment = fragment; - si.position = pos; - si.segmentCount = segCount; - } - } - - void setPadValue(Comparable pad) { - padValue = pad; - } - - void setPosition(int pos) { - ((StateInfo) get(top)).position = pos; + // Fall through to default + default : + topFrag = createGroupFragment(null, EXACT_ONE_QUALIFIER, currentList.toArray(new Fragment[currentList.size()]), false); } + currentList = null; + return topFrag; } - /** - * 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; + 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}))); - private static final Map formatCache = Collections.synchronizedMap(new HashMap()); + char c = format.charAt(current); + if (c != expected) + throw formatException(c, new String(new char[] {expected})); + ++current; + } - private static final String OSGI_FORMAT_STRING = "n[.n=0;[.n=0;[.S=[A-Za-z0-9_-];]]]"; //$NON-NLS-1$ + private VersionFormatException formatException(char found, String expected) { + return formatException(new String(new char[] {found}), expected); + } - private static final String RAW_FORMAT_STRING = "r(.r)*p?"; //$NON-NLS-1$ + 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})); + } - 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 (FormatException e) { - // If this happens, something is wrong with the actual - // implementation of the FormatCompiler. - // - throw new ExceptionInInitializerError(e); - } + 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})); } - /** - * 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 FormatException If the format could not be compiled - */ - public static VersionFormat compile(String format) throws FormatException { - return compile(format, 0, format.length()); + private VersionFormatException illegalControlCharacter(char c) { + return formatException(NLS.bind(Messages.illegal_character_encountered_ascii_0, VersionParser.valueOf(c))); } - /** - * 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 FormatException If the format could not be compiled - */ - public static VersionFormat compile(String format, int start, int end) throws FormatException { - String fmtString = format.substring(start, end).intern(); - synchronized (fmtString) { - VersionFormat fmt = (VersionFormat) formatCache.get(fmtString); - if (fmt == null) { - VersionFormatParser parser = new VersionFormatParser(); - fmt = new VersionFormat(parser.compile(format, start, end)); - formatCache.put(fmtString, fmt); + 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); } - return fmt; + sb.append(c); } + return sb.toString(); } - /** - * 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. - */ - static Version parseRaw(String version, VersionFormat originalFormat, String original) { - Comparable[] padReturn = new Comparable[1]; - Comparable[] vector = RAW_FORMAT.parse(version, 0, version.length(), padReturn); - return new Version(vector, padReturn[0], originalFormat, original); + 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())); } - 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('\\'); + private void parseBracketGroup() throws VersionFormatException { + List<Fragment> saveList = currentList; + currentList = new ArrayList<Fragment>(); + while (current < eos && format.charAt(current) != ']') + parseFragment(); - 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; + if (current == eos) + throw formatException(NLS.bind(Messages.premature_end_of_format_expected_0, "]")); //$NON-NLS-1$ - sb.append('-'); - if (c == '\\' || c == ']' || (c == '-' && idx + 1 < top)) - sb.append('\\'); - sb.append(c); - idx = ndx - 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); } } - sb.append(']'); - sb.append(';'); + assertChar(']'); + int top = sb.length(); + char[] chars = new char[top]; + sb.getChars(0, top, chars, 0); + ep.characters = chars; } - static Fragment createAutoFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) { - return new AutoFragment(instr, qualifier); + 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())); } - static Fragment createDelimiterFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) { - return new DelimiterFragment(instr, qualifier); + 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); + } } - static Fragment createGroupFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, Fragment[] fragments, boolean array) { - return new GroupFragment(instr, qualifier, fragments, array); - } + 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); + } + } - static Fragment createLiteralFragment(Qualifier qualifier, String literal) { - return new LiteralFragment(qualifier, literal); + 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; } - static Fragment createNumberFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean signed) { - return new NumberFragment(instr, qualifier, signed); - } + private int parseIntegerLiteral() throws VersionFormatException { + if (current == eos) + throw formatException(NLS.bind(Messages.premature_end_of_format_expected_0, "<integer>")); //$NON-NLS-1$ - static Fragment createPadFragment(Qualifier qualifier) { - return new PadFragment(qualifier); - } + char c = format.charAt(current); + if (!VersionParser.isDigit(c)) + throw formatException(c, "<integer>"); //$NON-NLS-1$ - static Fragment createQuotedFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) { - return new QuotedFragment(instr, qualifier); + int value = c - '0'; + while (++current < eos) { + c = format.charAt(current); + if (!VersionParser.isDigit(c)) + break; + value *= 10; + value += (c - '0'); + } + return value; } - static Fragment createRawFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) { - return new RawFragment(instr, qualifier); + 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)); } - static Fragment createStringFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean unbound) { - return new StringFragment(instr, qualifier, unbound); + 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}; } - 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); + 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 final Fragment topFragment; + private void parsePad() throws VersionFormatException { + currentList.add(createPadFragment(parseQualifier())); + } - private String fmtString; + private VersionFormatParser.Instructions parseProcessing() throws VersionFormatException { + if (current >= eos) + return null; - VersionFormat(Fragment topFragment) { - this.topFragment = topFragment; - } + char c = format.charAt(current); + if (c != '=') + return null; - public boolean equals(Object o) { - return this == o || o instanceof VersionFormat && toString().equals(o.toString()); + VersionFormatParser.Instructions ep = new VersionFormatParser.Instructions(); + do { + current++; + parseProcessingInstruction(ep); + } while (current < eos && format.charAt(current) == '='); + return ep; } - public int hashCode() { - return 11 * toString().hashCode(); - } + private void parseProcessingInstruction(VersionFormatParser.Instructions processing) throws VersionFormatException { + if (current == eos) + throw formatException(Messages.premature_end_of_format); - /** - * Parse the given version string. - * @param version The version string to parse. - * @return A created version. - * @throws IllegalArgumentException If the version string could not be parsed. - */ - public Version parse(String version) { - return parse(version, 0, version.length()); + 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(';'); } - /** - * Parse the given version string. - * @param version The version string to parse. - * @param start Start position in the version string - * @return A created version. - * @throws IllegalArgumentException If the version string could not be parsed. - */ - public Version parse(String version, int start, int maxPos) { - Comparable[] padReturn = new Comparable[1]; - Comparable[] vector = parse(version, start, maxPos, padReturn); - return new Version(vector, padReturn[0], this, version.substring(start, maxPos)); - } + private Qualifier parseQualifier() throws VersionFormatException { + if (current >= eos) + return EXACT_ONE_QUALIFIER; - /** - * Returns the string representation of this compiled format - */ - public synchronized String toString() { - if (fmtString == null) { - StringBuffer sb = new StringBuffer(); - toString(sb); + char c = format.charAt(current); + if (c == '?') { + ++current; + return ZERO_OR_ONE_QUALIFIER; } - return fmtString; - } - /** - * Appends the string representation of this compiled format to - * the given StringBuffer. - * @param sb The buffer that will receive the string representation - */ - 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); + if (c == '*') { + ++current; + return ZERO_OR_MANY_QUALIFIER; } - } - TreeInfo createInfo(int start) { - return new TreeInfo(topFragment, start); - } + if (c == '+') { + ++current; + return ONE_OR_MANY_QUALIFIER; + } + + if (c != '{') + return EXACT_ONE_QUALIFIER; - Comparable[] parse(String version, int start, int maxPos, Comparable[] padReturn) { - ArrayList entries = new ArrayList(); - 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); - 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))); - Comparable pad = info.getPadValue(); - VersionParser.removeRedundantTrail(entries, pad); - padReturn[0] = pad; - return (Comparable[]) entries.toArray(new Comparable[entries.size()]); + 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); } - // Preserve cache during deserialization - private Object readResolve() { - synchronized (formatCache) { - String string = toString(); - VersionFormat fmt = (VersionFormat) formatCache.put(string, this); - if (fmt == null) - fmt = this; - else - // Put old format back - formatCache.put(string, fmt); - return fmt; + 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())); } -} -class RawFormat extends VersionFormat { - private static final long serialVersionUID = 8851695938450999819L; + 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())); + } - RawFormat(Fragment topFragment) { - super(topFragment); + 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; } - /** - * 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 new Version(vector, padReturn[0], null, null); + 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)); } - // Preserve singleton when deserialized - private Object readResolve() { - return RAW_FORMAT; + 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/provisional/p2/metadata/VersionParser.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionParser.java index 138d895b8..0ef47d9bc 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/VersionParser.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionParser.java @@ -8,9 +8,10 @@ * Contributors: * Cloudsmith Inc. - initial API and implementation *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; +package org.eclipse.equinox.internal.p2.metadata; -import org.eclipse.equinox.internal.p2.metadata.Messages; +import org.eclipse.equinox.p2.metadata.Version; +import org.eclipse.equinox.p2.metadata.VersionFormatException; import java.util.ArrayList; import java.util.List; @@ -18,21 +19,49 @@ import org.eclipse.osgi.util.NLS; /** * The Omni Version parser. Not intended for public API. Instead use - * {@link Version#Version(String)} or {@link Version#parseVersion(String)}. + * {@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. */ -abstract class VersionParser { - static void removeRedundantTrail(List segments, Comparable padValue) { - Comparable redundantTrail = padValue == null ? VersionVector.MIN_VALUE : padValue; +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); - } - static final String RAW_PREFIX = "raw:"; //$NON-NLS-1$ + return padValue; + } private VersionParser() { // Prevent class from being instantiated @@ -45,34 +74,31 @@ abstract class VersionParser { * @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 - * @param receiver The version to be filled in - * @returns <code>true</code> if a version indeed was parsed or <code>false</code> if the 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 */ - static boolean parseInto(String version, int start, int maxPos, Version receiver) throws IllegalArgumentException { + 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 false; + return null; - Comparable[] padReturn = new Comparable[1]; - Comparable[] vector = null; - Comparable pad = null; + Comparable<?>[] padReturn = new Comparable[1]; + Comparable<?>[] vector = null; + Comparable<?> pad = null; VersionFormat fmt = null; char c = version.charAt(pos); if (isDigit(c)) { - fmt = VersionFormat.OSGI_FORMAT; - vector = fmt.parse(version, pos, maxPos, padReturn); - receiver.init(vector, padReturn[0], fmt, version); - return true; + vector = VersionFormat.OSGI_FORMAT.parse(version, pos, maxPos, padReturn); + return OSGiVersion.fromVector(vector, padReturn[0]); } if (!isLetter(c)) throw new IllegalArgumentException(); - if (version.startsWith(RAW_PREFIX, pos)) { + if (version.startsWith(Version.RAW_PREFIX, pos)) { VersionFormat rawFmt = VersionFormat.RAW_FORMAT; pos += 4; @@ -107,12 +133,10 @@ abstract class VersionParser { vector = rawFmt.parse(version, pos, end, padReturn); pad = padReturn[0]; pos = end; - if (pos == maxPos) { + if (pos == maxPos) // This was a pure raw version // - receiver.init(vector, pad, null, null); - return true; - } + 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))); @@ -132,7 +156,7 @@ abstract class VersionParser { int end = findEndOfFormat(version, pos, maxPos); fmt = VersionFormat.compile(version, pos, end); pos = end + 1; - } catch (FormatException e) { + } catch (VersionFormatException e) { throw new IllegalArgumentException(e.getMessage()); } if (pos == maxPos) { @@ -140,8 +164,7 @@ abstract class VersionParser { // if (vector == null) throw new IllegalArgumentException(NLS.bind(Messages.only_format_specified_0, version.substring(start, maxPos))); - receiver.init(vector, pad, fmt, null); - return true; + return fmt == VersionFormat.OSGI_FORMAT ? OSGiVersion.fromVector(vector, pad) : OmniVersion.fromVector(vector, pad, fmt, null); } } @@ -161,15 +184,14 @@ abstract class VersionParser { vector = fmt.parse(version, pos, maxPos, padReturn); pad = padReturn[0]; } - receiver.init(vector, pad, fmt, version.substring(pos)); - return true; + 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'; } - static boolean isLetter(char c) { + public static boolean isLetter(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } @@ -177,7 +199,7 @@ abstract class VersionParser { return isDigit(c) || isLetter(c); } - static int findEndOfFormat(String string, int pos, int maxPos) { + public static int findEndOfFormat(String string, int pos, int maxPos) { int end = -1; int depth = 1; for (int idx = pos; idx < maxPos; ++idx) { @@ -216,14 +238,14 @@ abstract class VersionParser { return end; } - static Comparable parseRawElement(String value, int[] position, int maxPos) { + 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; + Comparable<?> v; switch (c) { case '\'' : case '"' : { @@ -247,7 +269,7 @@ abstract class VersionParser { if (c != '\'' && c != '"') break; } - v = sb.toString(); + v = sb.length() == 0 ? VersionVector.MINS_VALUE : sb.toString(); break; } case '<' : { @@ -289,7 +311,7 @@ abstract class VersionParser { int val = Integer.parseInt(value.substring(start, current)); if (negate) val = -val; - v = Version.valueOf(val); + v = valueOf(val); break; } return null; @@ -299,7 +321,7 @@ abstract class VersionParser { return v; } - private static Comparable parseRawVector(String value, int[] position, int maxPos) { + private static Comparable<?> parseRawVector(String value, int[] position, int maxPos) { int pos = position[0]; if (pos >= maxPos) return null; @@ -308,7 +330,7 @@ abstract class VersionParser { if (c == '>') return null; - ArrayList rawList = new ArrayList(); + ArrayList<Comparable<?>> rawList = new ArrayList<Comparable<?>>(); boolean padMarkerSeen = (c == 'p'); if (padMarkerSeen) { if (++pos >= maxPos) @@ -316,9 +338,9 @@ abstract class VersionParser { position[0] = pos; } - Comparable pad = null; + Comparable<?> pad = null; for (;;) { - Comparable elem = parseRawElement(value, position, maxPos); + Comparable<?> elem = parseRawElement(value, position, maxPos); if (elem == null) return null; @@ -347,18 +369,18 @@ abstract class VersionParser { if (c != '.') return null; } - removeRedundantTrail(rawList, pad); - return new VersionVector((Comparable[]) rawList.toArray(new Comparable[rawList.size()]), pad); + pad = removeRedundantTrail(rawList, pad); + return new VersionVector(rawList.toArray(new Comparable[rawList.size()]), pad); } - static int skipWhite(String string, int pos) { + public static int skipWhite(String string, int pos) { int top = string.length(); while (pos < top && string.charAt(pos) <= ' ') ++pos; return pos; } - static int skipTrailingWhite(String string, int start, int end) { + 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/provisional/p2/metadata/VersionVector.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionVector.java index 3d70be612..c723c8e6b 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/VersionVector.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionVector.java @@ -8,7 +8,7 @@ * Contributors: * Cloudsmith Inc. - initial API and implementation *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; +package org.eclipse.equinox.internal.p2.metadata; import java.io.Serializable; @@ -18,9 +18,13 @@ import java.io.Serializable; * * @Immutable */ -public class VersionVector implements Comparable, Serializable { +public class VersionVector implements Comparable<VersionVector>, Serializable { - private static final class MaxStringValue implements Comparable, Serializable { + interface MinMaxComparable extends Comparable<Object>, Serializable { + // + } + + private static final class MaxStringValue implements MinMaxComparable { private static final long serialVersionUID = -4936252230441132767L; MaxStringValue() { @@ -41,7 +45,7 @@ public class VersionVector implements Comparable, Serializable { } } - private static final class MaxValue implements Comparable, Serializable { + private static final class MaxValue implements MinMaxComparable { private static final long serialVersionUID = -5889641741635253589L; MaxValue() { @@ -52,17 +56,17 @@ public class VersionVector implements Comparable, Serializable { return o == this ? 0 : 1; } - public String toString() { - return "M"; //$NON-NLS-1$ - } - // For singleton deserialization private Object readResolve() { return MAX_VALUE; } + + public String toString() { + return "M"; //$NON-NLS-1$ + } } - private static class MinValue implements Comparable, Serializable { + private static class MinValue implements MinMaxComparable { private static final long serialVersionUID = -1066323980049812226L; MinValue() { @@ -73,87 +77,129 @@ public class VersionVector implements Comparable, Serializable { return o == this ? 0 : -1; } - public String toString() { - return "-M"; //$NON-NLS-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 MAX_VALUE = new MaxValue(); + 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 MAXS_VALUE = new MaxStringValue(); + public static final Comparable<Object> MAXS_VALUE = new MaxStringValue(); /** * A value that is less then any other value */ - public static final Comparable MIN_VALUE = new MinValue(); + 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 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); + 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)); } - /** - * 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. - */ - 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; + 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 (rangeSafe && (c == '\\' || c == '[' || c == '(' || c == ']' || c == ')' || c == ',' || c <= ' ')) - sb.append('\\'); - sb.append(c); } - sb.append(quote); + 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; } - private static int compareSegments(Comparable a, Comparable b) { + static int compareSegments(Comparable<?> a, Comparable<?> b) { if (a == b) return 0; @@ -164,20 +210,20 @@ public class VersionVector implements Comparable, Serializable { } if (a instanceof String && b instanceof String) - return a.compareTo(b); + return ((String) a).compareTo((String) b); if (a == MAX_VALUE || a == MIN_VALUE || a == MAXS_VALUE) - return a.compareTo(b); + return ((MinMaxComparable) a).compareTo(b); if (b == MAX_VALUE || b == MIN_VALUE || b == MAXS_VALUE) - return -b.compareTo(a); + 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) ? a.compareTo(b) : 1; + return (b instanceof VersionVector) ? ((VersionVector) a).compareTo((VersionVector) b) : 1; if (b instanceof VersionVector) return -1; @@ -185,46 +231,20 @@ public class VersionVector implements Comparable, Serializable { throw new IllegalArgumentException(); } - private Comparable padValue; - - private Comparable[] vector; + private final Comparable<?> padValue; - VersionVector() { - // Constructor used in conjunction with init (when version is parsed from string) - } + private final Comparable<?>[] vector; - VersionVector(Comparable[] vector, Comparable pad) { + public VersionVector(Comparable<?>[] vector, Comparable<?> pad) { this.vector = vector; this.padValue = (pad == MIN_VALUE) ? null : pad; } - public int compareTo(Object o) { - if (o == this) + public int compareTo(VersionVector ov) { + if (ov == this) return 0; - VersionVector ov = (VersionVector) o; - Comparable[] t_vector = vector; - Comparable[] o_vector = ov.vector; - int top = t_vector.length; - if (top > o_vector.length) - top = o_vector.length; - - for (int idx = 0; idx < top; ++idx) { - int cmp = compareSegments(t_vector[idx], o_vector[idx]); - if (cmp != 0) - return cmp; - } - - // All elements compared equal up to this point. Check - // pad values - if (top < t_vector.length) - return (ov.padValue == null) ? 1 : compareReminder(top, ov.padValue); - - if (top < o_vector.length) - return (padValue == null) ? -1 : -ov.compareReminder(top, padValue); - - // Lengths are equal. Compare pad values - return padValue == null ? (ov.padValue == null ? 0 : -1) : (ov.padValue == null ? 1 : compareSegments(padValue, ov.padValue)); + return compare(vector, padValue, ov.vector, ov.padValue); } public boolean equals(Object o) { @@ -235,31 +255,7 @@ public class VersionVector implements Comparable, Serializable { return false; VersionVector ov = (VersionVector) o; - - // We compare pad first since it is impossible for versions with - // different pad to be equal (versions are padded to infinity) - if (padValue == null) { - if (ov.padValue != null) - return false; - } else { - if (ov.padValue == null || !padValue.equals(ov.padValue)) - return false; - } - - Comparable[] t_vector = vector; - Comparable[] o_vector = ov.vector; - int idx = t_vector.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 != o_vector.length) - return false; - - while (--idx >= 0) - if (!t_vector[idx].equals(o_vector[idx])) - return false; - - return true; + return equals(vector, padValue, ov.vector, ov.padValue); } /** @@ -267,7 +263,7 @@ public class VersionVector implements Comparable, Serializable { * 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() { + public Comparable<?> getPad() { return padValue; } @@ -276,7 +272,7 @@ public class VersionVector implements Comparable, Serializable { * @param index The zero based index of the desired element * @return An element from the raw vector */ - public Comparable getSegment(int index) { + public Comparable<?> getSegment(int index) { return vector[index]; } @@ -288,16 +284,17 @@ public class VersionVector implements Comparable, Serializable { 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() { - 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; + return hashCode(vector, padValue); } public String toString() { @@ -312,7 +309,7 @@ public class VersionVector implements Comparable, Serializable { * @param sb The buffer to append to */ public void toString(StringBuffer sb) { - toString(sb, false); + toString(sb, vector, padValue, false); } /** @@ -322,46 +319,7 @@ public class VersionVector implements Comparable, Serializable { * @param rangeSafe If <code>true</code>, the range delimiters will be escaped * with backslash. */ - public void toString(StringBuffer sb, 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 - rawToString(sb, rangeSafe, padValue == null ? MIN_VALUE : padValue); - else { - for (int idx = 0; idx < top; ++idx) { - if (idx > 0) - sb.append('.'); - rawToString(sb, rangeSafe, vector[idx]); - } - } - if (padValue != null) { - sb.append('p'); - rawToString(sb, rangeSafe, padValue); - } - } - - /** - * 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; - } - - void init(Comparable[] vec, Comparable pad) { - vector = vec; - padValue = (pad == MIN_VALUE) ? null : pad; - } - - private int compareReminder(int idx, 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 : padValue.compareTo(othersPad); - return cmp; + 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/provisional/p2/metadata/VersionedId.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionedId.java index 3b97f48a1..1bf5ebd44 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/VersionedId.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/VersionedId.java @@ -11,9 +11,12 @@ * Thomas Hallgren - Fix for bug 268659 * IBM - ongoing development *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; +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. 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/provisional/p2/metadata/query/IUPropertyQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/IUPropertyQuery.java index 9b43371dc..95f3f3375 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/IUPropertyQuery.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/query/IUPropertyQuery.java @@ -8,16 +8,17 @@ * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata.query; +package org.eclipse.equinox.internal.p2.metadata.query; -import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit; +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 { +public class IUPropertyQuery extends MatchQuery<IInstallableUnit> { private String propertyName; private String propertyValue; @@ -32,10 +33,7 @@ public class IUPropertyQuery extends MatchQuery { /* (non-Javadoc) * @see org.eclipse.equinox.p2.query2.Query#isMatch(java.lang.Object) */ - public boolean isMatch(Object object) { - if (!(object instanceof IInstallableUnit)) - return false; - IInstallableUnit candidate = (IInstallableUnit) object; + public boolean isMatch(IInstallableUnit candidate) { if (propertyName == null) return true; String value = getProperty(candidate, propertyName); 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; + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/QueryHelpers.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/query/QueryHelpers.java index 0d0a54d82..794ceff2b 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/QueryHelpers.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/query/QueryHelpers.java @@ -7,20 +7,25 @@ * Contributors: * EclipseSource - initial API and implementation ******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata.query; +package org.eclipse.equinox.internal.p2.query; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import org.eclipse.equinox.p2.query.IQuery; /** * Static helper methods for the Query API. + * + * @noextend This class is not intended to be subclassed by clients. */ public class QueryHelpers { /** * Gets the ID for a Query. + * + * @noreference This method is not intended to be referenced by clients. */ - public static String getId(Query query) { + public static String getId(IQuery<?> query) { return query.getClass().getName(); } @@ -28,9 +33,11 @@ public class QueryHelpers { * Gets a particular property of a query. * @param query The query to retrieve the property from * @param property The property to retrieve + * + * @noreference This method is not intended to be referenced by clients. */ - public static Object getProperty(Query query, String property) { - Class clazz = query.getClass(); + public static Object getProperty(IQuery<?> query, String property) { + Class<?> clazz = query.getClass(); Object result = null; try { Method method = clazz.getMethod("get" + property, new Class[0]); //$NON-NLS-1$ diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IInstallableUnit.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IInstallableUnit.java deleted file mode 100644 index b06c7337a..000000000 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IInstallableUnit.java +++ /dev/null @@ -1,278 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2007, 2008 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 - * Genuitec, LLC - added license support - * EclipseSource - ongoing development - ******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; - -import java.util.Map; - -/** - * @noimplement This interface is not intended to be implemented by clients. - * @noextend This interface is not intended to be extended by clients. - */ -public interface IInstallableUnit extends IVersionedId, Comparable { - - /** - * A capability namespace representing a particular profile flavor. - */ - public static final String NAMESPACE_FLAVOR = "org.eclipse.equinox.p2.flavor"; //$NON-NLS-1$ - - /** - * A capability namespace representing a particular InstallableUnit by id. - * Each InstallableUnit automatically provides a capability in this namespace representing - * itself, and other InstallableUnits can require such a capability to state that they - * require a particular InstallableUnit to be present. - * - * @see IInstallableUnit#getId() - */ - public static final String NAMESPACE_IU_ID = "org.eclipse.equinox.p2.iu"; //$NON-NLS-1$ - - /** - * A property key (value <code>"org.eclipse.equinox.p2.partial.iu"</code>) for a - * boolean property indicating the IU is generated from incomplete information and - * should be replaced by the complete IU if available. - * - * @see #getProperty(String) - */ - public static final String PROP_PARTIAL_IU = "org.eclipse.equinox.p2.partial.iu"; //$NON-NLS-1$ - - /** - * A property key (value <code>"org.eclipse.equinox.p2.type.profile"</code>) for a - * boolean property indicating that an installable unit is a profile. - * - * @see #getProperty(String) - */ - public static final String PROP_TYPE_PROFILE = "org.eclipse.equinox.p2.type.profile"; //$NON-NLS-1$ - - /** - * A property key (value <code>"org.eclipse.equinox.p2.type.category"</code>) for a - * boolean property indicating that an installable unit is a category. - * - * @see #getProperty(String) - */ - public static final String PROP_TYPE_CATEGORY = "org.eclipse.equinox.p2.type.category"; //$NON-NLS-1$ - - /** - * A property key (value <code>"org.eclipse.equinox.p2.type.fragment"</code>) for a - * boolean property indicating that an installable unit is a fragment. - * - * @see #getProperty(String) - */ - public static final String PROP_TYPE_FRAGMENT = "org.eclipse.equinox.p2.type.fragment"; //$NON-NLS-1$ - - /** - * A property key (value <code>"org.eclipse.equinox.p2.type.group"</code>) for a - * boolean property indicating that an installable unit is a group. - * - * @see #getProperty(String) - */ - public static final String PROP_TYPE_GROUP = "org.eclipse.equinox.p2.type.group"; //$NON-NLS-1$ - - /** - * A property key (value <code>"org.eclipse.equinox.p2.type.patch"</code>) for a - * boolean property indicating that an installable unit is a group. - * - * @see #getProperty(String) - */ - public static final String PROP_TYPE_PATCH = "org.eclipse.equinox.p2.type.patch"; //$NON-NLS-1$ - - /** - * A property key (value <code>"org.eclipse.equinox.p2.type.lock"</code>) for an - * integer property indicating how an installable unit is locked in its profile. - * The integer is a bit-mask indicating the different locks defined on the installable - * unit. The property should be obtained from a profile using - * IProfile#getInstallableUnitProperty(IInstallableUnit, String). - * - * @see #LOCK_UNINSTALL - * @see #LOCK_UPDATE - * @see #LOCK_NONE - */ - public static final String PROP_PROFILE_LOCKED_IU = "org.eclipse.equinox.p2.type.lock"; //$NON-NLS-1$ - - //TODO Move to UI - public static final String PROP_PROFILE_ROOT_IU = "org.eclipse.equinox.p2.type.root"; //$NON-NLS-1$ - - /** - * A property key (value <code>"org.eclipse.equinox.p2.contact"</code>) for a - * String property containing a contact address where problems can be reported, - * such as an email address. - * - * @see #getProperty(String) - */ - public static final String PROP_CONTACT = "org.eclipse.equinox.p2.contact"; //$NON-NLS-1$ - /** - * A property key (value <code>"org.eclipse.equinox.p2.description"</code>) for a - * String property containing a human-readable description of the installable unit. - * - * @see #getProperty(String) - */ - public static final String PROP_DESCRIPTION = "org.eclipse.equinox.p2.description"; //$NON-NLS-1$ - - /** - * A property key (value <code>"org.eclipse.equinox.p2.description.url"</code>) for a - * String property containing a URL to the description of the installable unit. - * - * @see #getProperty(String) - */ - public static final String PROP_DESCRIPTION_URL = "org.eclipse.equinox.p2.description.url"; //$NON-NLS-1$ - /** - * A property key (value <code>"org.eclipse.equinox.p2.doc.url"</code>) for a - * String property containing a URL for documentation about the installable unit. - * - * @see #getProperty(String) - */ - public static final String PROP_DOC_URL = "org.eclipse.equinox.p2.doc.url"; //$NON-NLS-1$ - - /** - * A property key (value <code>"org.eclipse.equinox.p2.bundle.localization"</code>) for a String - * property containing the bundle localization property file name - */ - public static final String PROP_BUNDLE_LOCALIZATION = "org.eclipse.equinox.p2.bundle.localization"; //$NON-NLS-1$ - - /** - * A property key (value <code>"org.eclipse.equinox.p2.name"</code>) for a - * String property containing a human-readable name for the installable unit. - * - * @see #getProperty(String) - */ - public static final String PROP_NAME = "org.eclipse.equinox.p2.name"; //$NON-NLS-1$ - /** - * A property key (value <code>"org.eclipse.equinox.p2.provider"</code>) for a - * String property containing information about the vendor or provider of the - * installable unit. - * - * @see #getProperty(String) - */ - public static final String PROP_PROVIDER = "org.eclipse.equinox.p2.provider"; //$NON-NLS-1$ - - /** - * Constant used to indicate that an installable unit is not locked in anyway. - * @see #PROP_PROFILE_LOCKED_IU - */ - public static int LOCK_NONE = 0; - /** - * Constant used to indicate that an installable unit is locked so that it may - * not be uninstalled. - * @see #PROP_PROFILE_LOCKED_IU - */ - public static int LOCK_UNINSTALL = 1 << 0; - /** - * Constant used to indicate that an installable unit is locked so that it may - * not be updated. updates. - * @see #PROP_PROFILE_LOCKED_IU - */ - public static int LOCK_UPDATE = 1 << 1; - - public IArtifactKey[] getArtifacts(); - - /** - * Returns the filter on this installable unit. The filter is matched against - * the selection context of the profile the unit is installed into. An IU will not - * be installed if it has a filter condition that is not satisfied by the context. - * - * See Profile#getSelectionContext. - */ - public String getFilter(); - - /** - * Returns the fragments that have been bound to this installable unit, or - * <code>null</code> if this unit is not resolved. - * - * @see #isResolved() - * @return The fragments bound to this installable unit, or <code>null</code> - */ - public IInstallableUnitFragment[] getFragments(); - - /** - * Get an <i>unmodifiable copy</i> of the properties - * associated with the installable unit. - * - * @return an <i>unmodifiable copy</i> of the IU properties. - */ - public Map getProperties(); - - public String getProperty(String key); - - public IProvidedCapability[] getProvidedCapabilities(); - - public IRequiredCapability[] getRequiredCapabilities(); - - public IRequiredCapability[] getMetaRequiredCapabilities(); - - public ITouchpointData[] getTouchpointData(); - - public ITouchpointType getTouchpointType(); - - public boolean isFragment(); - - /** - * Returns whether this installable unit has been resolved. A resolved - * installable unit represents the union of an installable unit and some - * fragments. - * - * @see #getFragments() - * @see #unresolved() - * @return <code>true</code> if this installable unit is resolved, and - * <code>false</code> otherwise. - */ - public boolean isResolved(); - - public boolean isSingleton(); - - /** - * Returns whether this unit has a provided capability that satisfies the given - * required capability. - * @return <code>true</code> if this unit satisfies the given required - * capability, and <code>false</code> otherwise. - */ - public boolean satisfies(IRequiredCapability candidate); - - /** - * Returns the unresolved equivalent of this installable unit. If this unit is - * already unresolved, this method returns the receiver. Otherwise, this - * method returns an installable unit with the same id and version, but without - * any fragments attached. - * - * @see #getFragments() - * @see #isResolved() - * @return The unresolved equivalent of this unit - */ - public IInstallableUnit unresolved(); - - /** - * Returns information about what this installable unit is an update of. - * @return The lineage information about the installable unit - */ - public IUpdateDescriptor getUpdateDescriptor(); - - /** - * Returns the license that applies to this installable unit. - * @return the license that applies to this installable unit or <code>null</code> - */ - public ILicense getLicense(); - - /** - * Returns the copyright that applies to this installable unit. - * @return the copyright that applies to this installable unit or <code>null</code> - */ - public ICopyright getCopyright(); - - /** - * Returns whether this InstallableUnit is equal to the given object. - * - * This method returns <i>true</i> if: - * <ul> - * <li> Both this object and the given object are of type IInstallableUnit - * <li> The result of <b>getId()</b> on both objects are equal - * <li> The result of <b>getVersion()</b> on both objects are equal - * </ul> - */ - public boolean equals(Object obj); -}
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IRequiredCapability.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IRequiredCapability.java deleted file mode 100644 index 9e0b652e6..000000000 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IRequiredCapability.java +++ /dev/null @@ -1,90 +0,0 @@ -/******************************************************************************* -* 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.provisional.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 { - - 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(); - - /** - * 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(); - - public boolean isMultiple(); - - public boolean isOptional(); - - /** - * 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); - - /** - * 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); - - public boolean isGreedy(); - - /** - * Returns whether this required capability is equal to the given object. - * - * This method returns <i>true</i> if: - * <ul> - * <li> Both this object and the given object are of type IRequiredCapability - * <li> The result of <b>getFilter()</b> on both objects are equal - * <li> The result of <b>isMultiple()</b> on both objects are equal - * <li> The result of <b>getName()</b> on both objects are equal - * <li> The result of <b>geNamespace()</b> on both objects are equal - * <li> The result of <b>isOptional()</b> on both objects are equal - * <li> The result of <b>getRange()</b> on both objects are equal - * </ul> - */ - public boolean equals(Object other); - - public boolean isNegation(); - - public boolean satisfiedBy(IProvidedCapability cap); - -}
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/MetadataFactory.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/MetadataFactory.java index b1021d5b4..4e023f487 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/MetadataFactory.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/MetadataFactory.java @@ -16,7 +16,10 @@ import java.net.URI; import java.util.*; import java.util.Map.Entry; import org.eclipse.core.runtime.Assert; +import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils; import org.eclipse.equinox.internal.p2.metadata.*; +import org.eclipse.equinox.p2.metadata.*; +import org.osgi.framework.Filter; /** * A factory class for instantiating various p2 metadata objects. @@ -29,34 +32,53 @@ public class MetadataFactory { * the resulting immutable unit. */ public static class InstallableUnitDescription { + public static final String PROP_TYPE_GROUP = "org.eclipse.equinox.p2.type.group"; //$NON-NLS-1$ + protected InstallableUnit unit; + /** + * A property key (value <code>"org.eclipse.equinox.p2.type.patch"</code>) for a + * boolean property indicating that an installable unit is a group. + * + */ + public static final String PROP_TYPE_PATCH = "org.eclipse.equinox.p2.type.patch"; //$NON-NLS-1$ + + /** + * A property key (value <code>"org.eclipse.equinox.p2.type.fragment"</code>) for a + * boolean property indicating that an installable unit is a fragment. + * + */ + public static final String PROP_TYPE_FRAGMENT = "org.eclipse.equinox.p2.type.fragment"; //$NON-NLS-1$ + + /** + * A property key (value <code>"org.eclipse.equinox.p2.type.category"</code>) for a + * boolean property indicating that an installable unit is a category. + * + */ + public static final String PROP_TYPE_CATEGORY = "org.eclipse.equinox.p2.type.category"; //$NON-NLS-1$ + public InstallableUnitDescription() { super(); } - public void addProvidedCapabilities(Collection additional) { + public void addProvidedCapabilities(Collection<IProvidedCapability> additional) { if (additional == null || additional.size() == 0) return; - IProvidedCapability[] current = unit().getProvidedCapabilities(); - IProvidedCapability[] result = new IProvidedCapability[additional.size() + current.length]; - System.arraycopy(current, 0, result, 0, current.length); - int j = current.length; - for (Iterator i = additional.iterator(); i.hasNext();) - result[j++] = (IProvidedCapability) i.next(); - unit().setCapabilities(result); + Collection<IProvidedCapability> current = unit().getProvidedCapabilities(); + ArrayList<IProvidedCapability> all = new ArrayList<IProvidedCapability>(additional.size() + current.size()); + all.addAll(current); + all.addAll(additional); + unit().setCapabilities(all.toArray(new IProvidedCapability[all.size()])); } - public void addRequiredCapabilities(Collection additional) { + public void addRequiredCapabilities(Collection<IRequirement> additional) { if (additional == null || additional.size() == 0) return; - IRequiredCapability[] current = unit().getRequiredCapabilities(); - IRequiredCapability[] result = new IRequiredCapability[additional.size() + current.length]; - System.arraycopy(current, 0, result, 0, current.length); - int j = current.length; - for (Iterator i = additional.iterator(); i.hasNext();) - result[j++] = (IRequiredCapability) i.next(); - unit().setRequiredCapabilities(result); + List<IRequirement> current = unit().getRequiredCapabilities(); + ArrayList<IRequirement> all = new ArrayList<IRequirement>(additional.size() + current.size()); + all.addAll(current); + all.addAll(additional); + unit().setRequiredCapabilities(all.toArray(new IRequirement[all.size()])); } public void addTouchpointData(ITouchpointData data) { @@ -68,15 +90,15 @@ public class MetadataFactory { return unit().getId(); } - public IProvidedCapability[] getProvidedCapabilities() { + public Collection<IProvidedCapability> getProvidedCapabilities() { return unit().getProvidedCapabilities(); } - public IRequiredCapability[] getRequiredCapabilities() { + public List<IRequirement> getRequiredCapabilities() { return unit().getRequiredCapabilities(); } - public IRequiredCapability[] getMetaRequiredCapabilities() { + public Collection<IRequirement> getMetaRequiredCapabilities() { return unit().getMetaRequiredCapabilities(); } @@ -86,7 +108,7 @@ public class MetadataFactory { * * @return The current touchpoint data on this description */ - public ITouchpointData[] getTouchpointData() { + public List<ITouchpointData> getTouchpointData() { return unit().getTouchpointData(); } @@ -107,6 +129,10 @@ public class MetadataFactory { unit().setCopyright(copyright); } + public void setFilter(Filter filter) { + unit().setFilter(filter); + } + public void setFilter(String filter) { unit().setFilter(filter); } @@ -115,19 +141,19 @@ public class MetadataFactory { unit().setId(id); } - public void setLicense(ILicense license) { - unit().setLicense(license); + public void setLicenses(ILicense[] licenses) { + unit().setLicenses(licenses); } public void setProperty(String key, String value) { unit().setProperty(key, value); } - public void setRequiredCapabilities(IRequiredCapability[] requirements) { + public void setRequiredCapabilities(IRequirement[] requirements) { unit().setRequiredCapabilities(requirements); } - public void setMetaRequiredCapabilities(IRequiredCapability[] metaRequirements) { + public void setMetaRequiredCapabilities(IRequirement[] metaRequirements) { unit().setMetaRequiredCapabilities(metaRequirements); } @@ -162,17 +188,13 @@ public class MetadataFactory { } } - /** - * Description of an installable unit patch. The description will automatically have - * the {@link IInstallableUnit#PROP_TYPE_FRAGMENT} set to <code>true</code>. - */ public static class InstallableUnitFragmentDescription extends InstallableUnitDescription { public InstallableUnitFragmentDescription() { super(); - setProperty(IInstallableUnit.PROP_TYPE_FRAGMENT, Boolean.TRUE.toString()); + setProperty(InstallableUnitDescription.PROP_TYPE_FRAGMENT, Boolean.TRUE.toString()); } - public void setHost(IRequiredCapability[] hostRequirements) { + public void setHost(IRequirement[] hostRequirements) { ((InstallableUnitFragment) unit()).setHost(hostRequirements); } @@ -183,24 +205,20 @@ public class MetadataFactory { } } - /** - * Description of an installable unit patch. The description will automatically have - * the {@link IInstallableUnit#PROP_TYPE_PATCH} set to <code>true</code>. - */ public static class InstallableUnitPatchDescription extends InstallableUnitDescription { public InstallableUnitPatchDescription() { super(); - setProperty(IInstallableUnit.PROP_TYPE_PATCH, Boolean.TRUE.toString()); + setProperty(InstallableUnitDescription.PROP_TYPE_PATCH, Boolean.TRUE.toString()); } - public void setApplicabilityScope(IRequiredCapability[][] applyTo) { + public void setApplicabilityScope(IRequirement[][] applyTo) { if (applyTo == null) throw new IllegalArgumentException("A patch scope can not be null"); //$NON-NLS-1$ ((InstallableUnitPatch) unit()).setApplicabilityScope(applyTo); } - public void setLifeCycle(IRequiredCapability lifeCycle) { + public void setLifeCycle(IRequirement lifeCycle) { ((InstallableUnitPatch) unit()).setLifeCycle(lifeCycle); } @@ -211,7 +229,7 @@ public class MetadataFactory { InstallableUnit unit() { if (unit == null) { unit = new InstallableUnitPatch(); - ((InstallableUnitPatch) unit()).setApplicabilityScope(new IRequiredCapability[0][0]); + ((InstallableUnitPatch) unit()).setApplicabilityScope(new IRequirement[0][0]); } return unit; } @@ -220,7 +238,7 @@ public class MetadataFactory { /** * Singleton touchpoint data for a touchpoint with no instructions. */ - private static final ITouchpointData EMPTY_TOUCHPOINT_DATA = new TouchpointData(Collections.EMPTY_MAP); + private static final ITouchpointData EMPTY_TOUCHPOINT_DATA = new TouchpointData(CollectionUtils.<String, ITouchpointInstruction> emptyMap()); private static ITouchpointType[] typeCache = new ITouchpointType[5]; @@ -277,7 +295,7 @@ public class MetadataFactory { } /** - * Returns a {@link IRequiredCapability} with the given values. + * Returns a {@link IRequirement} with the given values. * * @param namespace The capability namespace * @param name The required capability name @@ -289,8 +307,12 @@ public class MetadataFactory { * and <code>false</code> otherwise. * @param multiple <code>true</code> if this capability can be satisfied by multiple provided capabilities, or it requires exactly one match */ - public static IRequiredCapability createRequiredCapability(String namespace, String name, VersionRange range, String filter, boolean optional, boolean multiple) { - return new RequiredCapability(namespace, name, range, filter, optional, multiple); + public static IRequiredCapability createRequiredCapability(String namespace, String name, VersionRange range, Filter filter, boolean optional, boolean multiple) { + return new RequiredCapability(namespace, name, range, filter, optional ? 0 : 1, multiple ? Integer.MAX_VALUE : 1, true); + } + + public static IRequirement createRequiredCapability(String namespace, String name, VersionRange range, Filter filter, int minCard, int maxCard, boolean greedy) { + return new RequiredCapability(namespace, name, range, filter, minCard, maxCard, greedy); } public static IRequiredCapability createRequiredCapability(String namespace, String name, VersionRange range, String filter, boolean optional, boolean multiple, boolean greedy) { @@ -303,8 +325,10 @@ public class MetadataFactory { * @param newValue The result of the requirement change - the requirement to replace the source requirement with * @return a requirement change */ - public static IRequirementChange createRequirementChange(IRequiredCapability applyOn, IRequiredCapability newValue) { - return new RequirementChange(applyOn, newValue); + public static IRequirementChange createRequirementChange(IRequirement applyOn, IRequirement newValue) { + if ((applyOn == null || applyOn instanceof IRequiredCapability) && (newValue == null || newValue instanceof IRequiredCapability)) + return new RequirementChange((IRequiredCapability) applyOn, (IRequiredCapability) newValue); + throw new IllegalArgumentException(); } /** @@ -327,7 +351,7 @@ public class MetadataFactory { * @throws IllegalArgumentException when the <code>body</code> is <code>null</code> */ public static ILicense createLicense(URI location, String body) { - return new License(location, body); + return new License(location, body, null); } /** @@ -354,24 +378,66 @@ public class MetadataFactory { * @param instructions The instructions for the touchpoint data. * @return The created touchpoint data */ - public static ITouchpointData createTouchpointData(Map instructions) { + public static ITouchpointData createTouchpointData(Map<String, ? extends Object> instructions) { Assert.isNotNull(instructions); //copy the map to protect against subsequent change by caller if (instructions.isEmpty()) return EMPTY_TOUCHPOINT_DATA; - Map result = new LinkedHashMap(instructions.size()); - for (Iterator iterator = instructions.entrySet().iterator(); iterator.hasNext();) { - Entry entry = (Entry) iterator.next(); + Map<String, ITouchpointInstruction> result = new LinkedHashMap<String, ITouchpointInstruction>(instructions.size()); + + for (Entry<String, ? extends Object> entry : instructions.entrySet()) { Object value = entry.getValue(); + ITouchpointInstruction instruction; if (value == null || value instanceof String) - value = createTouchpointInstruction((String) value, null); - - result.put(entry.getKey(), value); + instruction = createTouchpointInstruction((String) value, null); + else + instruction = (ITouchpointInstruction) value; + result.put(entry.getKey(), instruction); } return new TouchpointData(result); } + /** + * Merge the given touchpoint instructions with a pre-existing touchpoint data + * @param initial - the initial ITouchpointData + * @param incomingInstructions - Map of ITouchpointInstructions to merge into the initial touchpoint data + * @return the merged ITouchpointData + */ + public static ITouchpointData mergeTouchpointData(ITouchpointData initial, Map<String, ITouchpointInstruction> incomingInstructions) { + if (incomingInstructions == null || incomingInstructions.size() == 0) + return initial; + + Map<String, ITouchpointInstruction> resultInstructions = new HashMap<String, ITouchpointInstruction>(initial.getInstructions()); + for (String key : incomingInstructions.keySet()) { + ITouchpointInstruction instruction = incomingInstructions.get(key); + ITouchpointInstruction existingInstruction = resultInstructions.get(key); + + if (existingInstruction != null) { + String body = existingInstruction.getBody(); + if (body == null || body.length() == 0) + body = instruction.getBody(); + else if (instruction.getBody() != null) { + if (!body.endsWith(";")) //$NON-NLS-1$ + body += ';'; + body += instruction.getBody(); + } + + String importAttribute = existingInstruction.getImportAttribute(); + if (importAttribute == null || importAttribute.length() == 0) + importAttribute = instruction.getImportAttribute(); + else if (instruction.getImportAttribute() != null) { + if (!importAttribute.endsWith(",")) //$NON-NLS-1$ + importAttribute += ','; + importAttribute += instruction.getBody(); + } + instruction = createTouchpointInstruction(body, importAttribute); + } + resultInstructions.put(key, instruction); + } + return createTouchpointData(resultInstructions); + } + public static ITouchpointInstruction createTouchpointInstruction(String body, String importAttribute) { return new TouchpointInstruction(body, importAttribute); } diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/Version.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/Version.java deleted file mode 100644 index 9f47ece06..000000000 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/Version.java +++ /dev/null @@ -1,504 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2009 Cloudsmith 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.provisional.p2.metadata; - -import org.eclipse.equinox.internal.p2.metadata.Messages; - -import org.eclipse.osgi.util.NLS; - -/** - * <p>The 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> - * - * The class is signature compatible with {@link org.osgi.framework.Version} but attempts - * to use it as such might render a {@link UnsupportedOperationException} in case the - * vector holds incompatible values. The method {@link #isOSGiCompatible()} can be used - * to test. - * - * @Immutable - * @noextend This class is not intended to be subclassed by clients. - */ -public class Version extends VersionVector { - private static final Integer cache[] = new Integer[100]; - - private static final char[] allowedOSGiChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-".toCharArray(); //$NON-NLS-1$ - - public static final Integer ZERO_INT = new Integer(0); - - public static final Integer MAX_INT_OBJ = new Integer(Integer.MAX_VALUE); - - static { - cache[0] = ZERO_INT; - for (int i = 1; i < cache.length; i++) - cache[i] = new Integer(i); - } - - static Integer valueOf(int i) { - try { - return cache[i]; - } catch (ArrayIndexOutOfBoundsException e) { - return (i == Integer.MAX_VALUE) ? MAX_INT_OBJ : new Integer(i); - } - } - - /** - * The empty OSGi version "0.0.0". Equivalent to calling - * <code>new Version(0,0,0)</code>. - */ - public static final Version emptyVersion = new Version(0, 0, 0); - - /** - * The version that is semantically greater then all other versions. - */ - public static final Version MAX_VERSION = new Version("raw:MpM"); //$NON-NLS-1$ - - /** - * The version that is semantically less then all other versions. - */ - public static final Version MIN_VERSION = new Version("raw:-M"); //$NON-NLS-1$ - - private static final long serialVersionUID = 8202715438560849928L; - - /** - * Creates an OSGi version identifier from the specified numerical components. - * - * <p> - * The qualifier is set to the empty string. - * - * @param major Major component of the version identifier. - * @param minor Minor component of the version identifier. - * @param micro Micro component of the version identifier. - * @throws IllegalArgumentException If the numerical components are - * negative. - */ - public static Version createOSGi(int major, int minor, int micro) { - return createOSGi(major, minor, micro, null); - } - - /** - * Creates an OSGi version identifier from the specified components. - * - * @param major Major component of the version identifier. - * @param minor Minor component of the version identifier. - * @param micro Micro component of the version identifier. - * @param qualifier Qualifier component of the version identifier. If - * <code>null</code> is specified, then the qualifier will be set to - * the empty string. - * @throws IllegalArgumentException If the numerical components are negative - * or the qualifier string is invalid. - */ - public static Version createOSGi(int major, int minor, int micro, String qualifier) { - // TODO: Eliminate duplicates - return new Version(major, minor, micro, qualifier); - } - - /** - * Create an omni version from an OSGi <code>version</code>. - * @param version The OSGi version. Can be <code>null</code>. - * @return The created omni version - */ - public static Version fromOSGiVersion(org.osgi.framework.Version version) { - if (version == null) - return null; - if (version.equals(org.osgi.framework.Version.emptyVersion)) - return emptyVersion; - return new Version(version.getMajor(), version.getMinor(), version.getMicro(), version.getQualifier()); - } - - /** - * Parses a version identifier from the specified string. - * - * @param version String representation of the version identifier. Leading - * and trailing whitespace will be ignored. - * @return A <code>Version</code> object representing the version identifier - * or <code>null</code> if <code>version</code> is <code>null</code> or - * an empty string. - * @throws IllegalArgumentException If <code>version</code> is improperly - * formatted. - */ - public static Version create(String version) { - // TODO: Eliminate duplicates - if (version != null) { - Version v = new Version(); - if (VersionParser.parseInto(version, 0, version.length(), v)) - return v; - } - return null; - } - - /** - * Parses a version identifier from the specified string. This method is for backward - * compatibility with OSGi and will return the OSGi {@link #emptyVersion} when - * the provided string is empty or <code>null</code>. - * - * @param version String representation of the version identifier. Leading - * and trailing whitespace will be ignored. - * @return A <code>Version</code> object representing the version - * identifier. If <code>version</code> is <code>null</code> or - * the empty string then the OSGi <code>emptyVersion</code> will be - * returned. - * @throws IllegalArgumentException If <code>version</code> is improperly - * formatted. - * @see #create(String) - */ - public static Version parseVersion(String version) { - if (version == null || version.length() == 0 || "0.0.0".equals(version)) //$NON-NLS-1$ - return emptyVersion; - Version v = create(version); - return v == null ? emptyVersion : v; - } - - /** - * Convert <code>version</code> into its OSGi equivalent if possible. - * - * @param version The version to convert. Can be <code>null</code> - * @return The converted version or <code>null</code> if the argument was <code>null</code> - * @throws UnsupportedOperationException if the version could not be converted into an OSGi version - */ - public static org.osgi.framework.Version toOSGiVersion(Version version) { - if (version == null) - return null; - if (version.equals(emptyVersion)) - return org.osgi.framework.Version.emptyVersion; - return new org.osgi.framework.Version(version.getMajor(), version.getMinor(), version.getMicro(), version.getQualifier()); - } - - /** - * For exception messages only - * @param i the index of the entry - * @return the name of the entry - */ - private static String getOSGiEntryName(int i) { - String name = null; - switch (i) { - case 0 : - name = "major"; //$NON-NLS-1$ - break; - case 1 : - name = "minor"; //$NON-NLS-1$ - break; - case 2 : - name = "micro"; //$NON-NLS-1$ - break; - case 3 : - name = "qualifier"; //$NON-NLS-1$ - } - return name; - } - - /** - * The optional format - */ - private VersionFormat format; - - /** - * The optional original string - */ - private String original; - - /** - * Creates an OSGi version identifier from the specified numerical components. - * - * <p> - * The qualifier is set to the empty string. - * - * @param major Major component of the version identifier. - * @param minor Minor component of the version identifier. - * @param micro Micro component of the version identifier. - * @throws IllegalArgumentException If the numerical components are - * negative. - * @deprecated Use {@link #createOSGi(int, int, int)}. This constructor will not remain public - */ - public Version(int major, int minor, int micro) { - this(major, minor, micro, null); - } - - /** - * Creates an OSGi version identifier from the specified components. - * - * @param major Major component of the version identifier. - * @param minor Minor component of the version identifier. - * @param micro Micro component of the version identifier. - * @param qualifier Qualifier component of the version identifier. If - * <code>null</code> is specified, then the qualifier will be set to - * the empty string. - * @throws IllegalArgumentException If the numerical components are negative - * or the qualifier string is invalid. - * @deprecated Use {@link #createOSGi(int, int, int, String)}. This constructor will not remain public - */ - public Version(int major, int minor, int micro, String qualifier) { - if (qualifier != null && qualifier.length() == 0) - qualifier = null; - Comparable[] vector = new Comparable[qualifier == null ? 3 : 4]; - vector[0] = valueOf(major); - vector[1] = valueOf(minor); - vector[2] = valueOf(micro); - if (qualifier != null) - vector[3] = qualifier; - init(vector, null, VersionFormat.OSGI_FORMAT, null); - validateOSGI(true); - } - - /** - * Created a version identifier from the specified string. - * - * @param version String representation of the version identifier. - * @throws IllegalArgumentException If <code>version</code> is improperly - * formatted. - * @deprecated Use {@link #parseVersion(String)}. This constructor will not remain public - */ - public Version(String version) { - if (!VersionParser.parseInto(version, 0, version.length(), this)) { - // Version is OSGi empty - init(new Comparable[] {ZERO_INT, ZERO_INT, ZERO_INT}, null, VersionFormat.OSGI_FORMAT, null); - } - } - - Version() { - // Empty constructor - } - - Version(Comparable[] array, Comparable padValue, VersionFormat format, String original) { - init(array, padValue, format, original); - } - - /** - * Returns the optional format. - */ - public VersionFormat getFormat() { - return format; - } - - /** - * 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 int getMajor() { - return getIntElement(0); - } - - /** - * 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 int getMicro() { - return getIntElement(2); - } - - /** - * 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 int getMinor() { - return getIntElement(1); - } - - /** - * Returns the <code>original</code> part of the string for this version - * or <code>null</code> if no such part was provided when the version was - * created. An OSGi type version will always return the OSGi string representation. - * - * @return The <code>original</code> part of the version string or - * <code>null</code> if that part was missing. - */ - public String getOriginal() { - return original; - } - - /** - * 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 String getQualifier() { - Comparable[] vector = getVector(); - if (vector.length < 4) - return null; - if (!(vector[3] instanceof String)) - throw new UnsupportedOperationException(); - return (String) vector[3]; - } - - /** - * 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() { - return format == VersionFormat.OSGI_FORMAT || validateOSGI(false); - } - - /** - * 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) { - super.toString(sb, 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 (format == VersionFormat.OSGI_FORMAT) { - Comparable[] vector = getVector(); - sb.append(vector[0]); - sb.append('.'); - sb.append(vector[1]); - sb.append('.'); - sb.append(vector[2]); - if (vector.length > 3) { - sb.append('.'); - sb.append(vector[3]); - } - return; - } - sb.append(VersionParser.RAW_PREFIX); - super.toString(sb, false); - if (format != null || original != null) { - sb.append('/'); - if (format != null) - format.toString(sb); - if (original != null) { - sb.append(':'); - originalToString(sb, false); - } - } - } - - void init(Comparable[] vec, Comparable pad, VersionFormat fmt, String orig) { - init(vec, pad); - format = fmt; - //don't need to retain original for OSGi version - if (fmt != VersionFormat.OSGI_FORMAT) - original = orig; - } - - private int getIntElement(int i) { - Comparable[] vector = getVector(); - 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(MIN_VERSION)) - v = MIN_VERSION; - else if (equals(emptyVersion)) - v = emptyVersion; - else if (equals(VersionRange.OSGi_versionMax)) - v = VersionRange.OSGi_versionMax; - else if (equals(VersionRange.OSGi_versionMin)) - v = VersionRange.OSGi_versionMin; - return v; - } - - boolean validateOSGI(boolean throwDetailed) { - Comparable[] vector = getVector(); - if (vector.length < 3 || vector.length > 4) { - if (throwDetailed) - throw new IllegalArgumentException(NLS.bind(Messages.illegal_number_of_entries_0_in_osgi_1, valueOf(vector.length), this)); - return false; - } - - if (getPad() != null) { - if (throwDetailed) - throw new IllegalArgumentException(NLS.bind(Messages.pad_not_allowed_in_osgi_0, this)); - return false; - } - - for (int i = 0; i < 3; ++i) { - Object e = vector[i]; - if (!(e instanceof Integer && ((Integer) e).intValue() >= 0)) { - if (throwDetailed) - throw new IllegalArgumentException(NLS.bind(Messages._0_is_not_a_positive_integer_in_osgi_1, getOSGiEntryName(i), this)); - return false; - } - } - if (vector.length == 4) { - Object e = vector[3]; - if (!(e instanceof String)) { - if (throwDetailed) - throw new IllegalArgumentException(NLS.bind(Messages._0_is_not_a_string_in_osgi_1, getOSGiEntryName(3), this)); - return false; - } - - String s = (String) e; - int idx = s.length(); - char[] allowed = allowedOSGiChars; - int ctop = allowed.length; - outer: while (--idx >= 0) { - char c = s.charAt(idx); - int cdx = ctop; - while (--cdx >= 0) - if (c == allowed[cdx]) - continue outer; - if (throwDetailed) - throw new IllegalArgumentException(NLS.bind(Messages._0_is_not_a_valid_qualifier_in_osgi_1, getOSGiEntryName(3), this)); - return false; - } - } - return true; - } -} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/VersionFormatParser.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/VersionFormatParser.java deleted file mode 100644 index 903d15c92..000000000 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/VersionFormatParser.java +++ /dev/null @@ -1,530 +0,0 @@ -/******************************************************************************* - * 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.provisional.p2.metadata; - -import org.eclipse.equinox.internal.p2.metadata.Messages; - -import java.util.ArrayList; -import java.util.List; -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 VersionFormat.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; - boolean ignore = false; - boolean inverted = false; - Comparable padValue = null; - int rangeMax = Integer.MAX_VALUE; - int rangeMin = 0; - } - - static final VersionFormat.Qualifier EXACT_ONE_QUALIFIER = new VersionFormat.Qualifier(1, 1); - - static final VersionFormat.Qualifier ONE_OR_MANY_QUALIFIER = new VersionFormat.Qualifier(1, Integer.MAX_VALUE); - - static final VersionFormat.Qualifier ZERO_OR_MANY_QUALIFIER = new VersionFormat.Qualifier(0, Integer.MAX_VALUE); - - static final VersionFormat.Qualifier ZERO_OR_ONE_QUALIFIER = new VersionFormat.Qualifier(0, 1); - - private int current; - - private List currentList; - - private int eos; - - private String format; - - private int start; - - VersionFormat.Fragment compile(String fmt, int pos, int maxPos) throws FormatException { - format = fmt; - if (start >= maxPos) - throw new FormatException(Messages.format_is_empty); - - start = pos; - current = pos; - eos = maxPos; - currentList = new ArrayList(); - while (current < eos) - parseFragment(); - - VersionFormat.Fragment topFrag; - switch (currentList.size()) { - case 0 : - throw new FormatException(Messages.format_is_empty); - case 1 : - VersionFormat.Fragment frag = (VersionFormat.Fragment) currentList.get(0); - if (frag.isGroup()) { - topFrag = frag; - break; - } - // Fall through to default - default : - topFrag = VersionFormat.createGroupFragment(null, EXACT_ONE_QUALIFIER, (VersionFormat.Fragment[]) currentList.toArray(new VersionFormat.Fragment[currentList.size()]), false); - } - currentList = null; - return topFrag; - } - - private void assertChar(char expected) throws FormatException { - 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 FormatException formatException(char found, String expected) { - return formatException(new String(new char[] {found}), expected); - } - - private FormatException formatException(String message) { - return new FormatException(NLS.bind(Messages.syntax_error_in_version_format_0_1_2, new Object[] {format.substring(start, eos), new Integer(current), message})); - } - - private FormatException formatException(String found, String expected) { - return new FormatException(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 FormatException illegalControlCharacter(char c) { - return formatException(NLS.bind(Messages.illegal_character_encountered_ascii_0, Version.valueOf(c))); - } - - private String parseAndConsiderEscapeUntil(char endChar) throws FormatException { - 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 FormatException { - VersionFormatParser.Instructions ep = parseProcessing(); - if (ep != null) { - if (ep.padValue != null) - throw formatException(Messages.auto_can_not_have_pad_value); - } - currentList.add(VersionFormat.createAutoFragment(ep, parseQualifier())); - } - - private void parseBracketGroup() throws FormatException { - List saveList = currentList; - currentList = new ArrayList(); - 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(VersionFormat.createGroupFragment(ep, ZERO_OR_ONE_QUALIFIER, (VersionFormat.Fragment[]) currentList.toArray(new VersionFormat.Fragment[currentList.size()]), false)); - currentList = saveList; - } - - private void parseCharacterGroup(VersionFormatParser.Instructions ep) throws FormatException { - 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 FormatException { - 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(VersionFormat.createDelimiterFragment(ep, parseQualifier())); - } - - private void parseFragment() throws FormatException { - 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 FormatException { - List saveList = currentList; - currentList = new ArrayList(); - 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(VersionFormat.createGroupFragment(ep, parseQualifier(), (VersionFormat.Fragment[]) currentList.toArray(new VersionFormat.Fragment[currentList.size()]), array)); - currentList = saveList; - } - - private int parseIntegerLiteral() throws FormatException { - 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 FormatException { - 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(VersionFormat.createLiteralFragment(parseQualifier(), value)); - } - - private int[] parseMinMax() throws FormatException { - - 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 FormatException { - VersionFormatParser.Instructions ep = parseProcessing(); - if (ep != null) { - if (ep.padValue != null) - throw formatException(Messages.number_can_not_have_pad_value); - } - currentList.add(VersionFormat.createNumberFragment(ep, parseQualifier(), signed)); - } - - private void parsePad() throws FormatException { - currentList.add(VersionFormat.createPadFragment(parseQualifier())); - } - - private VersionFormatParser.Instructions parseProcessing() throws FormatException { - 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 FormatException { - 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); - processing.defaultValue = parseRawElement(); - } - assertChar(';'); - } - - private VersionFormat.Qualifier parseQualifier() throws FormatException { - 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 VersionFormat.Qualifier(min, max); - } - - private void parseQuotedString() throws FormatException { - VersionFormatParser.Instructions ep = parseProcessing(); - if (ep != null) { - if (ep.padValue != null) - throw formatException(Messages.string_can_not_have_pad_value); - } - currentList.add(VersionFormat.createQuotedFragment(ep, parseQualifier())); - } - - private void parseRaw() throws FormatException { - VersionFormatParser.Instructions ep = parseProcessing(); - if (ep != null) { - if (ep.padValue != null) - throw formatException(Messages.raw_element_can_not_have_pad_value); - } - currentList.add(VersionFormat.createRawFragment(ep, parseQualifier())); - } - - private Comparable parseRawElement() throws FormatException { - int[] position = new int[] {current}; - Comparable v = VersionParser.parseRawElement(format, position, eos); - if (v == null) - throw new FormatException(NLS.bind(Messages.raw_element_expected_0, format)); - current = position[0]; - return v; - } - - private void parseString(boolean unlimited) throws FormatException { - VersionFormatParser.Instructions ep = parseProcessing(); - if (ep != null) { - if (ep.padValue != null) - throw formatException(Messages.string_can_not_have_pad_value); - } - currentList.add(VersionFormat.createStringFragment(ep, parseQualifier(), unlimited)); - } -} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/CapabilityQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/CapabilityQuery.java deleted file mode 100644 index 4336989a9..000000000 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/CapabilityQuery.java +++ /dev/null @@ -1,61 +0,0 @@ -/******************************************************************************* - * 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.provisional.p2.metadata.query; - -import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit; -import org.eclipse.equinox.internal.provisional.p2.metadata.IRequiredCapability; - -/** - * A query that searches for {@link IInstallableUnit} instances that provide - * capabilities that match one or more required capabilities. - */ -public class CapabilityQuery extends MatchQuery { - private IRequiredCapability[] required; - - /** - * Creates a new query on the given required capability. - * @param required The required capability - */ - public CapabilityQuery(IRequiredCapability required) { - this.required = new IRequiredCapability[] {required}; - } - - /** - * Creates a new query on the given required capabilities. The installable - * unit must provide capabilities that match all of the given required capabilities - * for this query to be satisfied. - * @param required The required capabilities - */ - public CapabilityQuery(IRequiredCapability[] required) { - this.required = required; - } - - /** - * Returns the required capability that this query is matching against. - * @return the required capability that this query is matching against. - */ - public IRequiredCapability[] getRequiredCapabilities() { - return required; - } - - /* (non-Javadoc) - * @see org.eclipse.equinox.p2.query2.Query#isMatch(java.lang.Object) - */ - public boolean isMatch(Object object) { - if (!(object instanceof IInstallableUnit)) - return false; - IInstallableUnit candidate = (IInstallableUnit) object; - for (int i = 0; i < required.length; i++) - if (!candidate.satisfies(required[i])) - return false; - return true; - } -} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/LatestIUVersionQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/LatestIUVersionQuery.java deleted file mode 100644 index 098642d64..000000000 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/LatestIUVersionQuery.java +++ /dev/null @@ -1,51 +0,0 @@ -/******************************************************************************* -* 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.provisional.p2.metadata.query; - -import java.util.*; -import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit; - -/** - * This query returns the latest version for each unique IU Id. - * All other elements are discarded. - */ -public class LatestIUVersionQuery extends ContextQuery { - - /** - * Performs the LatestIUVersionQuery - */ - public Collector perform(Iterator iterator, Collector result) { - HashMap greatestIUVersion = new HashMap(); - while (iterator.hasNext()) { - Object next = iterator.next(); - - if (!(next instanceof IInstallableUnit)) - // Don't accept things if they are not IUs - continue; - IInstallableUnit iu = (IInstallableUnit) next; - if (greatestIUVersion.containsKey(iu.getId())) { - IInstallableUnit currentIU = (IInstallableUnit) greatestIUVersion.get(iu.getId()); - if (currentIU.getVersion().compareTo(iu.getVersion()) < 0) - greatestIUVersion.put(iu.getId(), iu); - } else - greatestIUVersion.put(iu.getId(), iu); - } - - Collection values = greatestIUVersion.values(); - Iterator valuesIterator = values.iterator(); - boolean continueGather = true; - - while (valuesIterator.hasNext() && continueGather) { - IInstallableUnit nextIU = (IInstallableUnit) valuesIterator.next(); - continueGather = result.accept(nextIU); - } - return result; - } -} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/UpdateQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/UpdateQuery.java deleted file mode 100644 index 6774bcbfb..000000000 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/UpdateQuery.java +++ /dev/null @@ -1,38 +0,0 @@ -/******************************************************************************* - * 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.provisional.p2.metadata.query; - -import org.eclipse.equinox.internal.provisional.p2.metadata.*; - -public class UpdateQuery extends MatchQuery { - private IInstallableUnit updateFrom; - - public UpdateQuery(IInstallableUnit updateFrom) { - this.updateFrom = updateFrom; - } - - public boolean isMatch(Object obj) { - if (!(obj instanceof IInstallableUnit)) - return false; - if (obj instanceof IInstallableUnitPatch) { - IInstallableUnitPatch potentialPatch = (IInstallableUnitPatch) obj; - IRequiredCapability lifeCycle = potentialPatch.getLifeCycle(); - if (lifeCycle == null) - return false; - return updateFrom.satisfies(lifeCycle); - } - IInstallableUnit candidate = (IInstallableUnit) obj; - IUpdateDescriptor descriptor = candidate.getUpdateDescriptor(); - if (descriptor != null && descriptor.isUpdateOf(updateFrom) && updateFrom.getVersion().compareTo(candidate.getVersion()) < 0) - return true; - return false; - } -} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IArtifactKey.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IArtifactKey.java index ee07d8176..fdcba7a80 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IArtifactKey.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IArtifactKey.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2007, 2009 IBM Corporation and others. + * Copyright (c) 2007, 2010 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 @@ -8,21 +8,21 @@ * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; - +package org.eclipse.equinox.p2.metadata; /** - * Provide standardized artifact information to uniquely identify the + * Provide standardised artifact information to uniquely identify the * corresponding bytes (perhaps not stored as a file). * <p> * Artifact keys represent both a unique opaque identifier as well as structured - * and standardized pieces of information. + * and standardised pieces of information. * * @noimplement This interface is not intended to be implemented by clients. * @noextend This interface is not intended to be extended by clients. + * @since 2.0 */ -public interface IArtifactKey { +public interface IArtifactKey extends IVersionedId { /** * Returns the classifier for this artifact key. The returned value can be empty. @@ -31,8 +31,8 @@ public interface IArtifactKey { public String getClassifier(); /** - * Returns the id for this artifact key. The returned value can be empty. - * @return the classifier segment of the key. + * Returns the id for this artifact key. + * @return the id segment of the key. */ public String getId(); diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/ICopyright.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/ICopyright.java index 990b87432..d8ad2bd43 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/ICopyright.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/ICopyright.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2008 EclipseSource and others. All rights reserved. This +* 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 @@ -7,7 +7,7 @@ * Contributors: * EclipseSource - initial API and implementation ******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; +package org.eclipse.equinox.p2.metadata; import java.net.URI; @@ -18,6 +18,7 @@ import java.net.URI; * * @noimplement This interface is not intended to be implemented by clients. * @noextend This interface is not intended to be extended by clients. + * @since 2.0 */ public interface ICopyright { diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IInstallableUnit.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IInstallableUnit.java new file mode 100644 index 000000000..fbc39cbb4 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IInstallableUnit.java @@ -0,0 +1,293 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 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 + * Genuitec, LLC - added license support + * EclipseSource - ongoing development + ******************************************************************************/ +package org.eclipse.equinox.p2.metadata; + +import java.util.*; +import org.osgi.framework.Filter; + +/** + * An installable unit represents an atomic, indivisible unit of installable functionality + * in a provisioned system. Everything that can be installed or uninstalled in a system, + * including both concrete artifacts and instructions describing steps to be performed + * during install, must be expressed as one or more installable units. Thus the set of + * installable units present in a system, together with the existing environment + * (operating system, etc), completely describes the initial installed state of that system. + * <p> + * Installable units may have dependencies on functionality provided by other installable + * units, such that the unit cannot be installed unless some other installable unit + * is present in the installed system that provides a matching capability. Such + * dependencies are referred to as <i>required capabilities</i>. Conversely, + * installable units may declared <i>provided capabilities</i>, describing the + * capabilities that they make available to other units in the system. Note the + * weak coupling at work here: installable units never directly depend on each other, + * but instead depend on abstract capabilities that any other installable unit may provide. + * </p> + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + * @since 2.0 + */ +public interface IInstallableUnit extends IVersionedId, Comparable<IInstallableUnit> { + + /** + * A capability namespace representing a particular InstallableUnit by id. + * Each InstallableUnit automatically provides a capability in this namespace representing + * itself, and other InstallableUnits can require such a capability to state that they + * require a particular InstallableUnit to be present. + * + * @see IInstallableUnit#getId() + */ + public static final String NAMESPACE_IU_ID = "org.eclipse.equinox.p2.iu"; //$NON-NLS-1$ + + /** + * A property key (value <code>"org.eclipse.equinox.p2.partial.iu"</code>) for a + * boolean property indicating the IU is generated from incomplete information and + * should be replaced by the complete IU if available. + * + * @see #getProperty(String) + */ + public static final String PROP_PARTIAL_IU = "org.eclipse.equinox.p2.partial.iu"; //$NON-NLS-1$ + + /** + * A property key (value <code>"org.eclipse.equinox.p2.contact"</code>) for a + * String property containing a contact address where problems can be reported, + * such as an email address. + * + * @see #getProperty(String) + */ + public static final String PROP_CONTACT = "org.eclipse.equinox.p2.contact"; //$NON-NLS-1$ + /** + * A property key (value <code>"org.eclipse.equinox.p2.description"</code>) for a + * String property containing a human-readable description of the installable unit. + * + * @see #getProperty(String) + */ + public static final String PROP_DESCRIPTION = "org.eclipse.equinox.p2.description"; //$NON-NLS-1$ + + /** + * A property key (value <code>"org.eclipse.equinox.p2.description.url"</code>) for a + * String property containing a URL to the description of the installable unit. + * + * @see #getProperty(String) + */ + public static final String PROP_DESCRIPTION_URL = "org.eclipse.equinox.p2.description.url"; //$NON-NLS-1$ + /** + * A property key (value <code>"org.eclipse.equinox.p2.doc.url"</code>) for a + * String property containing a URL for documentation about the installable unit. + * + * @see #getProperty(String) + */ + public static final String PROP_DOC_URL = "org.eclipse.equinox.p2.doc.url"; //$NON-NLS-1$ + + /** + * A property key (value <code>"org.eclipse.equinox.p2.bundle.localization"</code>) for a String + * property containing the bundle localization property file name + */ + public static final String PROP_BUNDLE_LOCALIZATION = "org.eclipse.equinox.p2.bundle.localization"; //$NON-NLS-1$ + + /** + * A property key (value <code>"org.eclipse.equinox.p2.name"</code>) for a + * String property containing a human-readable name for the installable unit. + * + * @see #getProperty(String) + */ + public static final String PROP_NAME = "org.eclipse.equinox.p2.name"; //$NON-NLS-1$ + /** + * A property key (value <code>"org.eclipse.equinox.p2.provider"</code>) for a + * String property containing information about the vendor or provider of the + * installable unit. + * + * @see #getProperty(String) + */ + public static final String PROP_PROVIDER = "org.eclipse.equinox.p2.provider"; //$NON-NLS-1$ + + /** + * Returns the collection of artifacts associated with this installable unit. + * Installing this unit into a system will cause these artifacts to be fetched from + * a repository and applied to the installed system. Uninstalling this unit + * will cause these artifacts to be removed from the system. + * + * @return The artifacts associated with this installable unit + */ + public Collection<IArtifactKey> getArtifacts(); + + /** + * Returns the filter on this installable unit. The filter is matched against + * the properties of the environment the unit is installed into. An installable + * unit will not be installed if it has a filter condition that is not satisfied by the + * properties of the environment. + * + * @return The installation filter for this unit, or <code>null</code> + */ + public Filter getFilter(); + + /** + * Returns the fragments that have been bound to this installable unit, or + * <code>null</code> if this unit is not resolved. + * + * @see #isResolved() + * @return The fragments bound to this installable unit, or <code>null</code> + */ + public List<IInstallableUnitFragment> getFragments(); + + /** + * Returns an <i>unmodifiable copy</i> of the properties + * associated with the installable unit. + * + * @return an <i>unmodifiable copy</i> of the properties of this installable unit. + */ + public Map<String, String> getProperties(); + + /** + * Returns the untranslated property of this installable unit associated with the given key. + * Returns <code>null</code> if no such property is defined. + * <p> + * If the property value has been externalized, this method will return a string containing + * the translation key rather than a human-readable string. For this reason, clients + * wishing to obtain the value for a property that is typically translated should use + * {@link #getProperty(String, String)} instead. + * </p> + * + * @param key The property key to retrieve a property value for + * @return the property that applies to this installable unit or <code>null</code> + */ + public String getProperty(String key); + + /** + * Returns the property of this installable unit associated with the given key. + * Returns <code>null</code> if no such property is defined or no applicable + * translation is available. + * + * @param key The property key to retrieve a property value for + * @param locale The locale to translate the property for, or null to use the current locale. + * @return the property that applies to this installable unit or <code>null</code> + */ + public String getProperty(String key, String locale); + + /** + * Returns the collection of capabilities provided by this installable unit. + * + * @return The collection of capabilities provided by this installable unit. + */ + public Collection<IProvidedCapability> getProvidedCapabilities(); + + public Collection<IRequirement> getRequiredCapabilities(); + + public Collection<IRequirement> getMetaRequiredCapabilities(); + + public List<ITouchpointData> getTouchpointData(); + + public ITouchpointType getTouchpointType(); + + /** + * Returns whether this installable unit has been resolved. A resolved + * installable unit represents the union of an installable unit and some + * fragments. + * + * @see #getFragments() + * @see #unresolved() + * @return <code>true</code> if this installable unit is resolved, and + * <code>false</code> otherwise. + */ + public boolean isResolved(); + + /** + * Returns whether this installable unit is a singleton. Only one singleton + * installable unit with a given id is allowed to exist in a given installed system. + * Attempting to install multiple versions of a singleton will fail. + * @return <code>true</code> if this unit is a singleton, and <code>false</code> otherwise + */ + public boolean isSingleton(); + + /** + * Returns whether this unit has a provided capability that satisfies the given + * requirement. + * @return <code>true</code> if this unit satisfies the given requirement, and <code>false</code> otherwise. + */ + public boolean satisfies(IRequirement candidate); + + /** + * Returns the unresolved equivalent of this installable unit. If this unit is + * already unresolved, this method returns the receiver. Otherwise, this + * method returns an installable unit with the same id and version, but without + * any fragments attached. + * + * @see #getFragments() + * @see #isResolved() + * @return The unresolved equivalent of this unit + */ + public IInstallableUnit unresolved(); + + /** + * Returns information about what this installable unit is an update of. + * @return The lineage information about the installable unit + */ + public IUpdateDescriptor getUpdateDescriptor(); + + /** + * Returns the untranslated licenses that apply to this installable unit. + * <p> + * If the license text has been externalized, this method will return strings containing + * the translation keys rather than human-readable strings. For this reason, clients + * wishing to obtain a license for display to an end user should use {@link #getLicenses(String)} + * instead. + * </p> + * @return the licenses that apply to this installable unit + */ + public Collection<ILicense> getLicenses(); + + /** + * Returns the licenses that apply to this installable unit. Any translation of the + * licenses for the given locale will be applied. Returns an empty array if this + * unit has no licenses, or if the available licenses are externalized and do not + * have translations available for the given locale. + * + * @param locale The locale to translate the license for, or null to use the current locale. + * @return the translated licenses that apply to this installable unit + */ + public ILicense[] getLicenses(String locale); + + /** + * Returns the untranslated copyright that applies to this installable unit. + * <p> + * If the copyright text has been externalized, this method will return strings containing + * the translation keys rather than human-readable strings. For this reason, clients + * wishing to obtain a copyright for display to an end user should use {@link #getCopyright(String)} + * instead. + * </p> + * @return the copyright that applies to this installable unit or <code>null</code> + */ + public ICopyright getCopyright(); + + /** + * Returns the copyright that applies to this installable unit. Any translation of the + * copyright for the given locale will be applied. Returns <code>null</code> if this + * unit has no copyright, or if the copyright is externalized and no translations are + * available for the given locale. + * + * @param locale The locale to translate the copyright for, or null to use the current locale. + * @return the copyright that applies to this installable unit or <code>null</code> + */ + public ICopyright getCopyright(String locale); + + /** + * Returns whether this InstallableUnit is equal to the given object. + * + * This method returns <i>true</i> if: + * <ul> + * <li> Both this object and the given object are of type IInstallableUnit + * <li> The result of <b>getId()</b> on both objects are equal + * <li> The result of <b>getVersion()</b> on both objects are equal + * </ul> + */ + public boolean equals(Object obj); +}
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IInstallableUnitFragment.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IInstallableUnitFragment.java index a803f63a0..d9c93283d 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IInstallableUnitFragment.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IInstallableUnitFragment.java @@ -8,12 +8,13 @@ * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; +package org.eclipse.equinox.p2.metadata; /** * @noimplement This interface is not intended to be implemented by clients. * @noextend This interface is not intended to be extended by clients. + * @since 2.0 */ public interface IInstallableUnitFragment extends IInstallableUnit { - public IRequiredCapability[] getHost(); + public IRequirement[] getHost(); }
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IInstallableUnitPatch.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IInstallableUnitPatch.java index f67c7127d..600a9f325 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IInstallableUnitPatch.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IInstallableUnitPatch.java @@ -8,7 +8,9 @@ * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; +package org.eclipse.equinox.p2.metadata; + +import java.util.List; /** * An installable unit patch is an installable unit that alters the required capabilities of another @@ -16,6 +18,7 @@ package org.eclipse.equinox.internal.provisional.p2.metadata; * * @noimplement This interface is not intended to be implemented by clients. * @noextend This interface is not intended to be extended by clients. + * @since 2.0 */ public interface IInstallableUnitPatch extends IInstallableUnit { /** @@ -30,13 +33,13 @@ public interface IInstallableUnitPatch extends IInstallableUnit { * a scope of [[r1, r2, r3], [r4, r5]] will match any unit whose provided capabilities * satisfy the expression ((r1 ^ r2 ^ r3) | (r4 ^ r5)). */ - IRequiredCapability[][] getApplicabilityScope(); + IRequirement[][] getApplicabilityScope(); /** * Returns the requirement changes imposed by the patch. * @return The patch requirement changes. */ - IRequirementChange[] getRequirementsChange(); + List<IRequirementChange> getRequirementsChange(); /** * Returns the required capability that defines the lifecycle of this patch. The @@ -44,5 +47,5 @@ public interface IInstallableUnitPatch extends IInstallableUnit { * is satisfied by some IU in the profile. If a future provisioning operation causes * the requirement to no longer be satisfied, the patch will be uninstalled. */ - IRequiredCapability getLifeCycle(); + IRequirement getLifeCycle(); } diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/ILicense.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/ILicense.java index 99916dec8..5d613ad9b 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/ILicense.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/ILicense.java @@ -8,9 +8,8 @@ * EclipseSource - initial API and implementation * IBM - ongoing development ******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; +package org.eclipse.equinox.p2.metadata; -import java.math.BigInteger; import java.net.URI; /** @@ -20,6 +19,7 @@ import java.net.URI; * * @noimplement This interface is not intended to be implemented by clients. * @noextend This interface is not intended to be extended by clients. + * @since 2.0 */ public interface ILicense { @@ -40,11 +40,9 @@ public interface ILicense { * Returns the message digest of the license body. The digest is calculated on a normalized * version of the license where all whitespace has been reduced to one space. * - * Any SPI must maintain the same semantics as: - * <code>{@link org.eclipse.equinox.internal.provisional.p2.metadata.ILicense#getDigest()}</code> * @return the message digest as a <code>BigInteger</code>, never <code>null</code> */ - public BigInteger getDigest(); + public String getUUID(); /** * Returns whether this license is equal to the given object. diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IProvidedCapability.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IProvidedCapability.java index 971d2f6f8..0496e8925 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IProvidedCapability.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IProvidedCapability.java @@ -8,15 +8,14 @@ * EclipseSource - initial API and implementation * IBM - ongoing development ******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; - - +package org.eclipse.equinox.p2.metadata; /** * Describes a capability as exposed or required by an installable unit * * @noimplement This interface is not intended to be implemented by clients. * @noextend This interface is not intended to be extended by clients. + * @since 2.0 */ public interface IProvidedCapability { @@ -27,25 +26,6 @@ public interface IProvidedCapability { public Version getVersion(); /** - * Returns whether this provided capability satisfies the given required capability. - * @return <code>true</code> if this capability satisfies the given required - * capability, and <code>false</code> otherwise. - * - * This method must maintain the following semantics: - * <ul> - * <li> If the provided capability and the candidate have different names, - * return false - * <li> If the provided capability and the candidate have different namespaces. - * return false - * <li> If the candidate's version range includes the provided capability's - * version, return true - * <li> otherwise, return false - * </ul> - * - */ - public boolean satisfies(IRequiredCapability candidate); - - /** * Returns whether this provided capability is equal to the given object. * * This method returns <i>true</i> if: diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IRequirement.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IRequirement.java new file mode 100644 index 000000000..cc866e2fa --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IRequirement.java @@ -0,0 +1,38 @@ +/******************************************************************************* +* Copyright (c) 2009 IBM 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 - initial API and implementation +******************************************************************************/ +package org.eclipse.equinox.p2.metadata; + +import org.eclipse.equinox.p2.metadata.expression.IMatchExpression; +import org.osgi.framework.Filter; + +/** + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + * @since 2.0 + */ +public interface IRequirement { + + int getMin(); + + int getMax(); + + Filter getFilter(); + + /** + * Returns a boolean match expression that will return true for any + * {@link IInstallableUnit} that matches the requirement. + * @return A boolean match expression for installable unit matching. + */ + IMatchExpression<IInstallableUnit> getMatches(); + + boolean isMatch(IInstallableUnit iu); + + boolean isGreedy(); +}
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IRequirementChange.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IRequirementChange.java index 59578f7da..16c21098b 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IRequirementChange.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IRequirementChange.java @@ -7,11 +7,14 @@ * Contributors: * EclipseSource - initial API and implementation ******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; +package org.eclipse.equinox.p2.metadata; + +import org.eclipse.equinox.internal.p2.metadata.IRequiredCapability; /** * @noimplement This interface is not intended to be implemented by clients. * @noextend This interface is not intended to be extended by clients. + * @since 2.0 */ public interface IRequirementChange { diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/ITouchpointData.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/ITouchpointData.java index c1ea83b1a..3ab8b8c93 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/ITouchpointData.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/ITouchpointData.java @@ -7,9 +7,10 @@ * Contributors: * EclipseSource - initial API and implementation ******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; +package org.eclipse.equinox.p2.metadata; import java.util.Map; +import org.eclipse.equinox.internal.provisional.p2.metadata.MetadataFactory; /** * ITouchpoint data instances contain the additional information needed by a touchpoint @@ -21,6 +22,7 @@ import java.util.Map; * * @noimplement This interface is not intended to be implemented by clients. * @noextend This interface is not intended to be extended by clients. + * @since 2.0 */ public interface ITouchpointData { @@ -35,7 +37,7 @@ public interface ITouchpointData { * * @return the touchpoint instructions */ - public Map getInstructions(); + public Map<String, ITouchpointInstruction> getInstructions(); /** * Returns whether this TouchpointData is equal to the given object. diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/ITouchpointInstruction.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/ITouchpointInstruction.java index e2322f5e7..e7c9b49b9 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/ITouchpointInstruction.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/ITouchpointInstruction.java @@ -7,7 +7,7 @@ * Contributors: * EclipseSource - initial API and implementation ******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; +package org.eclipse.equinox.p2.metadata; /** * A touchpoint instruction contains either a sequence of instruction statements @@ -43,6 +43,7 @@ package org.eclipse.equinox.internal.provisional.p2.metadata; * * @noimplement This interface is not intended to be implemented by clients. * @noextend This interface is not intended to be extended by clients. + * @since 2.0 */ public interface ITouchpointInstruction { diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/ITouchpointType.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/ITouchpointType.java index f003b91d9..02218f760 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/ITouchpointType.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/ITouchpointType.java @@ -7,8 +7,7 @@ * Contributors: * EclipseSource - initial API and implementation ******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; - +package org.eclipse.equinox.p2.metadata; /** @@ -17,6 +16,7 @@ package org.eclipse.equinox.internal.provisional.p2.metadata; * * @noimplement This interface is not intended to be implemented by clients. * @noextend This interface is not intended to be extended by clients. + * @since 2.0 */ public interface ITouchpointType { diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IUpdateDescriptor.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IUpdateDescriptor.java index 268ff247c..047eb9f43 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IUpdateDescriptor.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IUpdateDescriptor.java @@ -8,13 +8,13 @@ * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; - +package org.eclipse.equinox.p2.metadata; /** * @noimplement This interface is not intended to be implemented by clients. * @noextend This interface is not intended to be extended by clients. + * @since 2.0 */ public interface IUpdateDescriptor { public final int NORMAL = 0; diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IVersionFormat.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IVersionFormat.java new file mode 100644 index 000000000..e87df3624 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IVersionFormat.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.p2.metadata; + +/** + * <p>The IVersionFormat represents the Omni Version Format in compiled form. It + * is also a parser for versions of that format.</p> + * <p>An instance of IVersionFormat is immutable and thus thread safe. The parser + * does not maintain any state.</p> + * @since 2.0 + */ +public interface IVersionFormat { + + /** + * The string that by default will be interpreted as the logical max string when parsing + * optional elements of type string and a default that is the empty string (i.e. OSGi) + */ + static final String DEFAULT_MAX_STRING_TRANSLATION = "zzz"; //$NON-NLS-1$ + + /** + * The string that by default will be interpreted as the logical min string when parsing + * optional elements of type string and a default that is the max string (i.e. Maven triplets) + */ + static final String DEFAULT_MIN_STRING_TRANSLATION = "-"; //$NON-NLS-1$ + + /** + * Appends the string representation of this compiled format to + * the given StringBuffer. + * @param sb The buffer that will receive the string representation + */ + void toString(StringBuffer sb); + + /** + * Parse the given version string. + * @param version The version string to parse. + * @return A created version. + * @throws IllegalArgumentException If the version string could not be parsed. + */ + Version parse(String version); +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IVersionedId.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IVersionedId.java index 77184d3c7..eb8aec341 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/IVersionedId.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IVersionedId.java @@ -7,11 +7,14 @@ * * Contributors: * Cloudsmith Inc. - initial API and implementation + * IBM - Ongoing development *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; +package org.eclipse.equinox.p2.metadata; + /** * An interface representing a (id,version) pair. + * @since 2.0 */ public interface IVersionedId { /** diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/Version.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/Version.java new file mode 100644 index 000000000..d92b63630 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/Version.java @@ -0,0 +1,229 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc - initial API and implementation. + *******************************************************************************/ + +package org.eclipse.equinox.p2.metadata; + +import java.io.Serializable; +import org.eclipse.equinox.internal.p2.metadata.*; + +/** + * A class that represents a Version in the Omni Version format. A Version can be though of as an + * array of comparable elements and an optional pad value. The pad value is used when comparing + * two versions with a different number of segments. + * + * The Omni Version can convert almost any version into a raw format that it uses for comparisons. + * This enables a unified order of all such versions and solves problems that arise when the + * version semantics are different. A good example is the OSGi version versus the version used in Maven. + * The lack of qualifier in the OSGi version implies that the qualifier is an empty string. So a version + * without a qualifier is the smallest of all other versions with the same major,minor,micro number. + * With Maven semantics, it's the opposite. If the qualifier is removed, the resulting version is + * considered higher then all other versions with the same major, minor, and micro number. The + * Omni version solves this by using different raw representations of the OSGi and Maven versions. + * + * The Omni version addresses a lot of other issues as well, such as reordering of the elements + * or treating some parts of a version as irrelevant when comparing. + * + * The class is signature compatible with {@link org.osgi.framework.Version} but attempts + * to use it as such might render a {@link UnsupportedOperationException} in case the + * raw vector holds incompatible values. The method {@link #isOSGiCompatible()} can be used + * to test. + * @since 2.0 + */ +public abstract class Version implements Comparable<Version>, Serializable { + public static final String RAW_PREFIX = "raw:"; //$NON-NLS-1$ + + /** + * The version that is semantically greater then all other versions. + */ + public static final Version MAX_VERSION = OmniVersion.createMaxVersion(); + + /** + * The version that is semantically less then all other versions. + */ + public static final Version emptyVersion = OmniVersion.createMinVersion(); + + private static final long serialVersionUID = 6218979149720923857L; + + /** + * Compile a version format string into a compiled format.. + * + * @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 VersionFormat.compile(format, 0, format.length()); + } + + /** + * Parses a version identifier from the specified string. + * + * @param version String representation of the version identifier. Leading + * and trailing whitespace will be ignored. + * @return A <code>Version</code> object representing the version identifier + * or <code>null</code> if <code>version</code> is <code>null</code> or + * an empty string. + * @throws IllegalArgumentException If <code>version</code> is improperly + * formatted. + */ + public static Version create(String version) { + return version == null ? null : VersionParser.parse(version, 0, version.length()); + } + + /** + * Creates an OSGi version identifier from the specified numerical components. + * + * <p> + * The qualifier is set to the empty string. + * + * @param major Major component of the version identifier. + * @param minor Minor component of the version identifier. + * @param micro Micro component of the version identifier. + * @throws IllegalArgumentException If the numerical components are + * negative. + */ + public static Version createOSGi(int major, int minor, int micro) { + return createOSGi(major, minor, micro, null); + } + + /** + * Creates an OSGi version identifier from the specified components. + * + * @param major Major component of the version identifier. + * @param minor Minor component of the version identifier. + * @param micro Micro component of the version identifier. + * @param qualifier Qualifier component of the version identifier. If + * <code>null</code> is specified, then the qualifier will be set to + * the empty string. + * @throws IllegalArgumentException If the numerical components are negative + * or the qualifier string is invalid. + */ + public static Version createOSGi(int major, int minor, int micro, String qualifier) { + Comparable<?> logicQualifier; + if (qualifier == null || qualifier.length() == 0) { + if (major == 0 && minor == 0 && micro == 0) + return emptyVersion; + logicQualifier = VersionVector.MINS_VALUE; // So that we can do identity compare + } else if (qualifier.equals(IVersionFormat.DEFAULT_MAX_STRING_TRANSLATION)) + logicQualifier = VersionVector.MAXS_VALUE; + else + logicQualifier = qualifier; + return new OSGiVersion(major, minor, micro, logicQualifier); + } + + /** + * Create an omni version from an OSGi <code>version</code>. + * @param version The OSGi version. Can be <code>null</code>. + * @return The created omni version + */ + public static Version fromOSGiVersion(org.osgi.framework.Version version) { + if (version == null) + return null; + if (version.getMajor() == Integer.MAX_VALUE && version.getMicro() == Integer.MAX_VALUE && version.getMicro() == Integer.MAX_VALUE) + return MAX_VERSION; + return createOSGi(version.getMajor(), version.getMinor(), version.getMicro(), version.getQualifier()); + } + + /** + * Parses a version identifier from the specified string. This method is for backward + * compatibility with OSGi and will return the OSGi "0.0.0" version when + * the provided string is empty or <code>null</code>. + * + * @param version String representation of the version identifier. Leading + * and trailing whitespace will be ignored. + * @return A <code>Version</code> object representing the version + * identifier. If <code>version</code> is <code>null</code> or + * the empty string then the OSGi <code>emptyVersion</code> will be + * returned. + * @throws IllegalArgumentException If <code>version</code> is improperly + * formatted. + * @see #create(String) + */ + public static Version parseVersion(String version) { + if (version == null || version.length() == 0) + return Version.emptyVersion; + Version v = create(version); + return v == null ? Version.emptyVersion : v; + } + + /** + * Convert <code>version</code> into its OSGi equivalent if possible. + * + * @param version The version to convert. Can be <code>null</code> + * @return The converted version or <code>null</code> if the argument was <code>null</code> + * @throws UnsupportedOperationException if the version could not be converted into an OSGi version + */ + public static org.osgi.framework.Version toOSGiVersion(Version version) { + if (version == null) + return null; + if (version == emptyVersion) + return org.osgi.framework.Version.emptyVersion; + if (version == MAX_VERSION) + return new org.osgi.framework.Version(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); + + BasicVersion bv = (BasicVersion) version; + return new org.osgi.framework.Version(bv.getMajor(), bv.getMinor(), bv.getMicro(), bv.getQualifier()); + } + + /** + * Returns the optional format. + */ + public abstract IVersionFormat getFormat(); + + /** + * Returns the <code>original</code> part of the string for this version + * or <code>null</code> if no such part was provided when the version was + * created. An OSGi type version will always return the OSGi string representation. + * + * @return The <code>original</code> part of the version string or + * <code>null</code> if that part was missing. + */ + public abstract String getOriginal(); + + /** + * Returns the pad value used when comparing this versions to + * versions that has a larger number of segments + * @return The pad value or <code>null</code> if not set. + */ + public abstract Comparable<?> getPad(); + + /** + * An element from the raw vector representation of this version. + * @param index The zero based index of the desired element + * @return An element from the raw vector + */ + public abstract Comparable<?> getSegment(int index); + + /** + * Returns the number of elements in the raw vector representation of this version. + * @return The number of elements in the raw vector. + */ + public abstract int getSegmentCount(); + + /** + * 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 abstract boolean isOSGiCompatible(); + + public String toString() { + StringBuffer buf = new StringBuffer(20); + toString(buf); + return buf.toString(); + } + + /** + * Appends the string representation of this version onto the + * <code>sb</code> StringBuffer. + * @param sb The buffer that will receive the version string + */ + public abstract void toString(StringBuffer sb); +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/FormatException.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/VersionFormatException.java index 3e9855f54..4aeb4c9e1 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/FormatException.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/VersionFormatException.java @@ -8,17 +8,17 @@ * Contributors: * Cloudsmith Inc. - initial API and implementation *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; +package org.eclipse.equinox.p2.metadata; /** - * Exception thrown by the {@link VersionFormatParser} - * + * Exception thrown when parsing Omni Version formats. + * @since 2.0 */ -public class FormatException extends Exception { +public class VersionFormatException extends Exception { private static final long serialVersionUID = -867104101610941043L; - public FormatException(String message) { + public VersionFormatException(String message) { super(message); } } diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/VersionRange.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/VersionRange.java index 16484abe6..6d93d42a7 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/VersionRange.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/VersionRange.java @@ -9,11 +9,10 @@ * IBM Corporation - initial API and implementation * Cloudsmith Inc - rewrite to handle non-OSGi versions. *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata; - -import org.eclipse.equinox.internal.p2.metadata.Messages; +package org.eclipse.equinox.p2.metadata; import java.io.Serializable; +import org.eclipse.equinox.internal.p2.metadata.*; import org.eclipse.osgi.util.NLS; /** @@ -22,13 +21,11 @@ import org.eclipse.osgi.util.NLS; * * @Immutable * @noextend This class is not intended to be subclassed by clients. + * @since 2.0 */ public class VersionRange implements Serializable { private static final long serialVersionUID = 4988030307298088028L; - static final Version OSGi_versionMin = new Version(0, 0, 0); - static final Version OSGi_versionMax = new Version(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); - /** * TODO: This should not be OSGi but it has to be that for now since the resolver creates * a filter where the min and max are converted into strings. When the filter is evaluated an @@ -36,7 +33,7 @@ public class VersionRange implements Serializable { * * An empty OSGi Version range. */ - public static final VersionRange emptyRange = new VersionRange(OSGi_versionMin, true, OSGi_versionMax, true); + public static final VersionRange emptyRange = new VersionRange(Version.emptyVersion, true, Version.MAX_VERSION, true); private final Version minVersion; private final boolean includeMin; @@ -75,26 +72,19 @@ public class VersionRange implements Serializable { public VersionRange(Version minVersion, boolean includeMin, Version maxVersion, boolean includeMax) { if (minVersion == null) { if (maxVersion == null) { - // For backward compatibility with the OSGi version version range - minVersion = OSGi_versionMin; - maxVersion = OSGi_versionMax; + minVersion = Version.emptyVersion; + maxVersion = Version.MAX_VERSION; } else - minVersion = maxVersion.getFormat() == VersionFormat.OSGI_FORMAT ? OSGi_versionMin : Version.MIN_VERSION; + minVersion = Version.emptyVersion; } else { if (maxVersion == null) - maxVersion = minVersion.getFormat() == VersionFormat.OSGI_FORMAT ? OSGi_versionMax : Version.MAX_VERSION; + maxVersion = Version.MAX_VERSION; else { if (minVersion != maxVersion && minVersion.equals(maxVersion)) maxVersion = minVersion; else if (!(minVersion.getFormat() == null ? maxVersion.getFormat() == null : minVersion.getFormat().equals(maxVersion.getFormat()))) { - // We always allow the MIN and MAX boundaries but if the other end is OSGi, then they too must be OSGi - if (minVersion.equals(Version.MIN_VERSION)) { - if (maxVersion.getFormat() == VersionFormat.OSGI_FORMAT) - minVersion = OSGi_versionMin; - } else if (maxVersion.equals(Version.MAX_VERSION)) { - if (minVersion.getFormat() == VersionFormat.OSGI_FORMAT) - maxVersion = OSGi_versionMax; - } else + // We always allow the MIN and MAX boundaries + if (!(minVersion.equals(Version.emptyVersion) || maxVersion.equals(Version.MAX_VERSION))) throw new IllegalArgumentException(NLS.bind(Messages.range_boundaries_0_and_1_cannot_have_different_formats, minVersion, maxVersion)); } } @@ -121,9 +111,9 @@ public class VersionRange implements Serializable { } if (pos >= top) { - minVersion = OSGi_versionMin; + minVersion = Version.emptyVersion; includeMin = true; - maxVersion = OSGi_versionMax; + maxVersion = Version.MAX_VERSION; includeMax = true; return; } @@ -131,7 +121,7 @@ public class VersionRange implements Serializable { char c = versionRange.charAt(pos); int[] position = new int[1]; boolean rawPrefix = false; - VersionFormat fmt = null; + IVersionFormat fmt = null; if (VersionParser.isLetter(c)) { if (versionRange.startsWith("raw:", pos)) { //$NON-NLS-1$ rawPrefix = true; @@ -238,28 +228,14 @@ public class VersionRange implements Serializable { } } } - Version minV = VersionFormat.parseRaw(minStr, fmt, origMin); - - // We might have parsed the Version.MIN_VERSION. If so, replace it. The format is incorrect. - // - boolean isOSGi = (fmt == VersionFormat.OSGI_FORMAT); - boolean isMinMin = (minV.getVector().length == 0 && minV.getPad() == null); - minVersion = isMinMin ? (isOSGi ? OSGi_versionMin : Version.MIN_VERSION) : minV; - + minVersion = VersionFormat.parseRaw(minStr, fmt, origMin); if (maxStr != null) { if (maxStr.equals(minStr)) - maxVersion = minV; - else { - Version maxV = VersionFormat.parseRaw(maxStr, fmt, origMax); - Comparable[] maxVector = maxV.getVector(); - - // We might have parsed the Version.MAX_VERSION. If so, replace it. The format is incorrect. - // - boolean isMaxMax = (maxVector.length == 0 && maxV.getPad() == VersionVector.MAX_VALUE); - maxVersion = isMaxMax ? (isOSGi ? OSGi_versionMax : Version.MAX_VERSION) : maxV; - } + maxVersion = minVersion; + else + maxVersion = VersionFormat.parseRaw(maxStr, fmt, origMax); } else - maxVersion = (fmt == VersionFormat.OSGI_FORMAT ? OSGi_versionMax : Version.MAX_VERSION); + maxVersion = Version.MAX_VERSION; } else { if (fmt == null) fmt = VersionFormat.OSGI_FORMAT; @@ -270,13 +246,13 @@ public class VersionRange implements Serializable { else maxVersion = fmt.parse(maxStr); } else { - maxVersion = (fmt == VersionFormat.OSGI_FORMAT) ? OSGi_versionMax : Version.MAX_VERSION; + maxVersion = Version.MAX_VERSION; } } validateRange(); } - private static VersionFormat parseFormat(String versionRange, int[] position) { + private static IVersionFormat parseFormat(String versionRange, int[] position) { int pos = VersionParser.skipWhite(versionRange, position[0]); if (!versionRange.startsWith("format(", pos)) //$NON-NLS-1$ return null; @@ -286,7 +262,7 @@ public class VersionRange implements Serializable { try { position[0] = end + 1; return VersionFormat.compile(versionRange, pos, end); - } catch (FormatException e) { + } catch (VersionFormatException e) { throw new IllegalArgumentException(e.getMessage()); } } @@ -294,8 +270,8 @@ public class VersionRange implements Serializable { /** * Returns the version format. */ - public VersionFormat getFormat() { - return minVersion.equals(Version.MIN_VERSION) ? maxVersion.getFormat() : minVersion.getFormat(); + public IVersionFormat getFormat() { + return minVersion.equals(Version.emptyVersion) ? maxVersion.getFormat() : minVersion.getFormat(); } /** @@ -426,9 +402,15 @@ public class VersionRange implements Serializable { } public void toString(StringBuffer result) { - VersionFormat fmt = getFormat(); + boolean gtEqual = includeMin && includeMax && Version.MAX_VERSION.equals(maxVersion); + if (gtEqual && Version.emptyVersion.equals(minVersion)) { + minVersion.toString(result); + return; + } + + IVersionFormat fmt = getFormat(); if (fmt == VersionFormat.OSGI_FORMAT) { - if (includeMin && includeMax && OSGi_versionMax.equals(maxVersion)) { + if (gtEqual) { minVersion.toString(result); } else { result.append(includeMin ? '[' : '('); @@ -440,15 +422,14 @@ public class VersionRange implements Serializable { return; } - boolean gtEqual = includeMin && includeMax && Version.MAX_VERSION.equals(maxVersion); result.append("raw:"); //$NON-NLS-1$ if (gtEqual) { - minVersion.rawToString(result, true); + ((BasicVersion) minVersion).rawToString(result, true); } else { result.append(includeMin ? '[' : '('); - minVersion.rawToString(result, true); + ((BasicVersion) minVersion).rawToString(result, true); result.append(','); - maxVersion.rawToString(result, true); + ((BasicVersion) maxVersion).rawToString(result, true); result.append(includeMax ? ']' : ')'); } boolean hasOriginal = (minVersion.getOriginal() != null || maxVersion.getOriginal() != null); @@ -459,14 +440,14 @@ public class VersionRange implements Serializable { if (hasOriginal) { result.append(':'); if (gtEqual) { - minVersion.originalToString(result, true); + ((BasicVersion) minVersion).originalToString(result, true); } else { - if (Version.MIN_VERSION.equals(minVersion)) - minVersion.rawToString(result, true); + if (Version.emptyVersion.equals(minVersion)) + ((BasicVersion) minVersion).rawToString(result, true); else - minVersion.originalToString(result, true); + ((BasicVersion) minVersion).originalToString(result, true); result.append(','); - maxVersion.originalToString(result, true); + ((BasicVersion) maxVersion).originalToString(result, true); } } } diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/ExpressionParseException.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/ExpressionParseException.java new file mode 100644 index 000000000..059f30482 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/ExpressionParseException.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.expression; + +/** + * An exception used by an expression parser that indicates that something went wrong when + * parsing. + * @since 2.0 + */ +public class ExpressionParseException extends RuntimeException { + private static final long serialVersionUID = 8432875384760577764L; + + public ExpressionParseException(String message) { + super(message); + } + + public ExpressionParseException(String expression, String message, int position) { + super("Parse error in string \"" + expression + "\": " + message + " at position " + position); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/ExpressionUtil.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/ExpressionUtil.java new file mode 100644 index 000000000..02861aeed --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/ExpressionUtil.java @@ -0,0 +1,179 @@ +/******************************************************************************* + * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.expression; + +import org.eclipse.equinox.internal.p2.metadata.expression.*; +import org.eclipse.equinox.internal.p2.metadata.expression.parser.ExpressionParser; +import org.eclipse.equinox.internal.p2.metadata.expression.parser.LDAPFilterParser; + +/** + * Global access to factory, parser, and methods for introspection + */ +public abstract class ExpressionUtil { + private static final LDAPFilterParser ldapFilterParser = new LDAPFilterParser(ExpressionFactory.INSTANCE); + private static final ExpressionParser expressionParser = new ExpressionParser(ExpressionFactory.INSTANCE); + + /** + * Returns the global expression factory + * @return The global expression factory. + */ + public static IExpressionFactory getFactory() { + return ExpressionFactory.INSTANCE; + } + + /** + * Creates and returns a new expression parser + * @return The new parser + */ + public static IExpressionParser newParser() { + return new ExpressionParser(getFactory()); + } + + /** + * Parse an LDAP filter from the <code>filter</code> string. If <code>filter</code> is <code>null</code> + * or a string that is empty or only consists of whitespace, then this method returns <code>null</code>. + * @param filter The filter to parse. Can be <code>null</code> or empty. + * @return An expression that corresponds to the LDAP filter or <code>null</code>. + * @throws ExpressionParseException If the syntax was invalid + */ + public static IFilterExpression parseLDAP(String filter) throws IllegalArgumentException { + filter = trimmedOrNull(filter); + return filter == null ? null : ldapFilterParser.parse(filter); + } + + /** + * Parse a boolean Expression from the <code>expression</code> string. If <code>expression</code> is <code>null</code> + * or a string that is empty or only consists of whitespace, then this method returns <code>null</code>. + * @param expression The expression to parse. Can be <code>null</code> or empty. + * @return An expression that corresponds to the LDAP filter or <code>null</code>. + * @throws ExpressionParseException If the syntax was invalid + */ + public static IExpression parse(String expression) { + expression = trimmedOrNull(expression); + return expression == null ? null : expressionParser.parse(expression); + } + + /** + * If <code>str</code> is <code>null</code>, then this method returns <code>null</code>. + * Otherwise <code>str</code> is trimmed from whitespace at both ends. If the result + * of the trim is an empty string, then <code>null</code> is returned, otherwise the + * result of the trim is returned. + * @param str The string to trim. Can be <code>null</code>. + * @return The trimmed string or <code>null</code>. + */ + public static String trimmedOrNull(String str) { + if (str != null) { + str = str.trim(); + if (str.length() == 0) + str = null; + } + return str; + } + + /** + * Obtains the Left Hand Side (LHS) of a binary expression. + * @param expression The expression to introspect + * @return The left hand side operator + * @throws IllegalArgumentException if the expression is not a binary expression + * @see IExpression#TYPE_AT + * @see IExpression#TYPE_EQUALS + * @see IExpression#TYPE_GREATER + * @see IExpression#TYPE_GREATER_EQUAL + * @see IExpression#TYPE_LESS + * @see IExpression#TYPE_LESS_EQUAL + * @see IExpression#TYPE_MATCHES + * @see IExpression#TYPE_NOT_EQUALS + */ + public static IExpression getLHS(IExpression expression) { + if (expression instanceof Binary) + return ((Binary) expression).lhs; + throw new IllegalArgumentException(); + } + + /** + * Obtains the name of a variable or member expression. + * @param expression The expression to introspect + * @return The name of the expression + * @throws IllegalArgumentException if the expression is not a variable or a member + * @see IExpression#TYPE_MEMBER + * @see IExpression#TYPE_VARIABLE + */ + public static String getName(IExpression expression) { + if (expression instanceof Member) + return ((Member) expression).getName(); + if (expression instanceof Variable) + return ((Variable) expression).getName(); + throw new IllegalArgumentException(); + } + + /** + * Obtains the operand of an unary expression + * @param expression The expression to introspect + * @return The expression operand + * @throws IllegalArgumentException if the expression is not an unary expression + * @see IExpression#TYPE_ALL + * @see IExpression#TYPE_EXISTS + * @see IExpression#TYPE_LAMBDA + * @see IExpression#TYPE_NOT + */ + public static IExpression getOperand(IExpression expression) { + if (expression instanceof Unary) + return ((Unary) expression).operand; + throw new IllegalArgumentException(); + } + + /** + * Obtains the operands of an n-ary expression + * @param expression The expression to introspect + * @return The expression operand + * @throws IllegalArgumentException if the expression is not a n-ary expression + * @see IExpression#TYPE_AND + * @see IExpression#TYPE_OR + */ + public static IExpression[] getOperands(IExpression expression) { + if (expression instanceof NAry) + return ((NAry) expression).operands; + throw new IllegalArgumentException(); + } + + /** + * Obtains the Right Hand Side (RHS) of a binary expression. + * @param expression The expression to introspect + * @return The right hand side operator + * @throws IllegalArgumentException if the expression is not a binary expression + * @see IExpression#TYPE_AT + * @see IExpression#TYPE_EQUALS + * @see IExpression#TYPE_GREATER + * @see IExpression#TYPE_GREATER_EQUAL + * @see IExpression#TYPE_LESS + * @see IExpression#TYPE_LESS_EQUAL + * @see IExpression#TYPE_MATCHES + * @see IExpression#TYPE_NOT_EQUALS + */ + public static IExpression getRHS(IExpression expression) { + if (expression instanceof Binary) + return ((Binary) expression).rhs; + throw new IllegalArgumentException(); + } + + /** + * Obtains the value of a literal expression + * @param expression The expression to introspect + * @return The literal value + * @throws IllegalArgumentException if the expression is not a literal + * @see IExpression#TYPE_LITERAL + */ + public static Object getValue(IExpression expression) { + if (expression instanceof Literal) + return ((Literal) expression).value; + throw new IllegalArgumentException(); + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IEvaluationContext.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IEvaluationContext.java new file mode 100644 index 000000000..4b049c054 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IEvaluationContext.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.expression; + +/** + * The evaluation context. Contexts can be nested and new contexts are pushed for each closure + * during an evaluation of an expression. + * @since 2.0 + */ +public interface IEvaluationContext { + /** + * Retrieve the value of the given <code>variable</code> from this context + * @param variable The variable who's value should be retrieved + * @return The current value for the variable + */ + Object getValue(IExpression variable); + + /** + * Set the current value for the given <code>variable</code> to <code>value</code> + * @param variable The variable who's value should be set + * @param value The new value for the variable. + */ + void setValue(IExpression variable, Object value); + + /** + * Returns the value of the parameter at the given <code>position</code> + * @param position The zero based position for the parameter + * @return The parameter value + */ + Object getParameter(int position); +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpression.java new file mode 100644 index 000000000..889675381 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpression.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.expression; + +/** + * A node in the expression tree + */ +public interface IExpression { + int TYPE_ALL = 1; + int TYPE_AND = 2; + int TYPE_AT = 3; + int TYPE_EQUALS = 4; + int TYPE_EXISTS = 5; + int TYPE_GREATER = 6; + int TYPE_GREATER_EQUAL = 7; + int TYPE_LAMBDA = 8; + int TYPE_LESS = 9; + int TYPE_LESS_EQUAL = 10; + int TYPE_LITERAL = 11; + int TYPE_MATCHES = 12; + int TYPE_MEMBER = 13; + int TYPE_NOT = 14; + int TYPE_NOT_EQUALS = 15; + int TYPE_OR = 16; + int TYPE_PARAMETER = 17; + int TYPE_VARIABLE = 18; + + /** + * Let the visitor visit this instance and all expressions that this + * instance contains. + * @param visitor The visiting visitor. + * @return <code>true</code> if the visitor should continue visiting, <code>false</code> otherwise. + */ + boolean accept(IExpressionVisitor visitor); + + /** + * Evaluate this expression with given context and variables. + * @param context The evaluation context + * @return The result of the evaluation. + */ + Object evaluate(IEvaluationContext context); + + /** + * Returns the expression type (see TYPE_xxx constants). + */ + int getExpressionType(); + + /** + * Appends the string representation of this expression to the collector <code>collector</code>. + */ + void toString(StringBuffer collector); + + /** + * Appends the an LDAP filter representation of this expression to the <code>collector</code>. + * @throws UnsupportedOperationException if the expression contains nodes + * that cannot be represented in an LDAP filter + */ + void toLDAPString(StringBuffer collector); +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionFactory.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionFactory.java new file mode 100644 index 000000000..f76079a5d --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionFactory.java @@ -0,0 +1,206 @@ +/******************************************************************************* + * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.expression; + +import java.util.List; + +/** + * This inteface provides all the factory methods needed to create the + * nodes of the expression tree. + */ +public interface IExpressionFactory { + String FUNC_BOOLEAN = "boolean"; //$NON-NLS-1$ + String FUNC_VERSION = "version"; //$NON-NLS-1$ + String FUNC_CLASS = "class"; //$NON-NLS-1$ + String FUNC_RANGE = "range"; //$NON-NLS-1$ + String FUNC_FILTER = "filter"; //$NON-NLS-1$ + + IExpression[] NO_ARGS = new IExpression[0]; + + /** + * Create a collection filter that yields true if the <code>lambda</code> yields true for + * all of the elements of the <code>collection</code> + * @param collection The collection providing the elements to test + * @param lambda The lambda that performs the test + * @return A boolean expression + */ + IExpression all(IExpression collection, IExpression lambda); + + /** + * Create a logical <i>and</i> of its <code>operands</code>. + * @param operands The boolean operands + * @return A boolean expression + */ + IExpression and(IExpression... operands); + + /** + * Create an lookup of <code>key</code> in the <code>target</code>. + * The key expression should evaluate to a string or an integer. + * @param target The target for the lookup + * @param key The key to use for the lookup + * @return A lookup expression + */ + IExpression at(IExpression target, IExpression key); + + /** + * Create an evaluation context with one single variable + * @param params Indexed parameters to use in the expression + * @return the context + */ + IEvaluationContext createContext(Object... params); + + /** + * Create an evaluation context with one single variable + * @param params Indexed parameters to use in the expression + * @param variables The variables that will be maintained by the context + * @return the context + */ + IEvaluationContext createContext(IExpression[] variables, Object... params); + + /** + * Creates an expression that evaluates to the constant <code>value</code>. + * @param value The constant + * @return A constant expression + */ + IExpression constant(Object value); + + /** + * Create an expression that tests if <code>lhs</code> is equal to <code>rhs</code>. + * @param lhs The left hand side value. + * @param rhs The right hand side value. + * @return A boolean expression + */ + IExpression equals(IExpression lhs, IExpression rhs); + + /** + * Create a collection filter that yields true if the <code>lambda</code> yields true for + * at least one of the elements of the <code>collection</code> + * @param collection The collection providing the elements to test + * @param lambda The lambda that performs the test + * @return A boolean expression + */ + IExpression exists(IExpression collection, IExpression lambda); + + /** + * Creates a top level expression suitable for predicate matching + * @param expression The boolean expression + * @return A top level predicate expression + */ + IFilterExpression filterExpression(IExpression expression); + + /** + * Create an expression that tests if <code>lhs</code> is greater than <code>rhs</code>. + * @param lhs The left hand side value. + * @param rhs The right hand side value. + * @return A boolean expression + */ + IExpression greater(IExpression lhs, IExpression rhs); + + /** + * Create an expression that tests if <code>lhs</code> is greater than or equal to <code>rhs</code>. + * @param lhs The left hand side value. + * @param rhs The right hand side value. + * @return A boolean expression + */ + IExpression greaterEqual(IExpression lhs, IExpression rhs); + + /** + * Creates an indexed parameter expression + * @param index The index to use + * @return a parameter expression + */ + IExpression indexedParameter(int index); + + /** + * Creates a lambda expression that takes exactly one variable. Suitable for use + * in most collection expressions. + * @param variable The element variable that the lambda uses + * @param body The body of the lambda + * @return A lambda expression + */ + IExpression lambda(IExpression variable, IExpression body); + + /** + * Create an expression that tests if <code>lhs</code> is less than <code>rhs</code>. + * @param lhs The left hand side value. + * @param rhs The right hand side value. + * @return A boolean expression + */ + IExpression less(IExpression lhs, IExpression rhs); + + /** + * Create an expression that tests if <code>lhs</code> is less than or equal to <code>rhs</code>. + * @param lhs The left hand side value. + * @param rhs The right hand side value. + * @return A boolean expression + */ + IExpression lessEqual(IExpression lhs, IExpression rhs); + + /** + * Performs boolean normalizations on the expression to create a canonical form. + * @param operands The operands to normalize + * @param expressionType The type (must be either {@link IExpression#TYPE_AND} + * or {@link IExpression#TYPE_OR}. + * @return The normalized expression + */ + IExpression normalize(List<? extends IExpression> operands, int expressionType); + + /** + * Create an expression that tests if <code>lhs</code> matches <code>rhs</code>. + * @param lhs The left hand side value. + * @param rhs The right hand side value. + * @return A boolean expression + */ + IExpression matches(IExpression lhs, IExpression rhs); + + /** + * Creates a parameterized top level expression suitable for predicate matching + * @param expression The boolean expression + * @param parameters The parameters to use in the call + * @return A top level predicate expression + */ + <T> IMatchExpression<T> matchExpression(IExpression expression, Object... parameters); + + /** + * Creates a member accessor expression. + * @param target The target for the member access + * @param name The name of the member + * @return A member expression + */ + IExpression member(IExpression target, String name); + + /** + * Creates an expression that negates the result of evaluating its <code>operand</code>. + * @param operand The boolean expression to negate + * @return A boolean expression + */ + IExpression not(IExpression operand); + + /** + * Create a logical <i>or</i> of its <code>operands</code>. + * @param operands The boolean operands + * @return A boolean expression + */ + IExpression or(IExpression... operands); + + /** + * Returns the variable that represents <code>this</this> in an expression + * @return The <code>this</this> variable. + */ + IExpression thisVariable(); + + /** + * Creates an expression that represents a variable + * @param name The name of the variable + * @return A variable expression + */ + IExpression variable(String name); +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionParser.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionParser.java new file mode 100644 index 000000000..6e716af06 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionParser.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.expression; + +/** + * A parser that produces an expression tree based on a string representation. An + * implementation will use the {@link IExpressionFactory} to create the actual expressions + * @since 2.0 + */ +public interface IExpressionParser { + /** + * Create a new expression. The expression will have access to the global + * variable and to the context parameters. + * @param exprString The string representing the boolean expression. + * @return The resulting expression tree. + */ + IExpression parse(String exprString); +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionVisitor.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionVisitor.java new file mode 100644 index 000000000..1c314d804 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IExpressionVisitor.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.expression; + +/** + * A general purpose visitor that will visit each node in an expression tree. + * @since 2.0 + */ +public interface IExpressionVisitor { + /** + * The method that will be called for each expression that is + * visited. + * @param expression The expression that the visitor visits. + * @return <code>true</code> to continue visiting other expressions or + * <code>false</code> to break out. + */ + boolean visit(IExpression expression); +}
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IFilterExpression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IFilterExpression.java new file mode 100644 index 000000000..8e5bcba92 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IFilterExpression.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.expression; + +import java.util.Map; +import org.osgi.framework.Filter; + +/** + * An interface that combines the IExpression with the LDAP filter. The + * string representation is the LDAP filter syntax. + * @since 2.0 + */ +public interface IFilterExpression extends IExpression, Filter { + /** + * Filter using a <code>Map</code>. This <code>Filter</code> is + * executed using the specified <code>Map</code>'s keys and values. + * The keys are case insensitively matched with this <code>Filter</code>. + * + * @param map The <code>Map</code> whose keys are used in the + * match. + * @return <code>true</code> if the <code>map</code>'s keys and + * values match this filter; <code>false</code> otherwise. + * @throws IllegalArgumentException If <code>map</code> contains case + * variants of the same key name. + */ + boolean match(Map<String, ? extends Object> map); + + /** + * Filter with case sensitivity using a <code>Map</code>. This + * <code>Filter</code> is executed using the specified + * <code>Map</code>'s keys and values. The keys are case sensitively + * matched with this <code>Filter</code>. + * + * @param map The <code>Map</code> whose keys are used in the + * match. + * @return <code>true</code> if the <code>map</code>'s keys and + * values match this filter; <code>false</code> otherwise. + */ + boolean matchCase(Map<String, ? extends Object> map); +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IMatchExpression.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IMatchExpression.java new file mode 100644 index 000000000..419e3f4ba --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/IMatchExpression.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.expression; + +/** + * A match expression is a boolean expression matching a candidate of a + * specific type. An {@link IEvaluationContext} is needed in order to evaluate + * a match and this class provides two ways of doing that. Either a context + * is created first and then reused in several subsequent calls to + * {@link #isMatch(IEvaluationContext, Object)} or, if no repeated calls are + * expected, the {@link #isMatch(Object)} method can be used. It will then + * create a context on each call. + */ +public interface IMatchExpression<T> extends IExpression { + /** + * <p>Creates a new context to be passed to repeated subsequent evaluations. The context + * will introduce 'this' as an uninitialized variable and make the parameters available. + * @return A new evaluation context. + */ + IEvaluationContext createContext(); + + /** + * Returns the parameters that this match expression was created with. + * @return An array of parameters, possibly empty but never <code>null</code>. + */ + Object[] getParameters(); + + /** + * This method creates a new evaluation context and assigns the <code>candidate</code> + * to the 'this' variable of the <code>context</code> and then evaluates the expression. + * This is essentially a short form for <pre>isMatch(createContext(), candidate)</pre>. + * @param candidate The object to test. + * @return the result of the evaluation. + */ + boolean isMatch(T candidate); + + /** + * This method assigns <code>candidate</code> to the 'this' variable of the + * <code>context</code> and then evaluates the expression. + * @param context A context + * @param candidate The object to test. + * @return the result of the evaluation. + */ + boolean isMatch(IEvaluationContext context, T candidate); +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/SimplePattern.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/SimplePattern.java new file mode 100644 index 000000000..12087cb94 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/expression/SimplePattern.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2009 - 2010 Cloudsmith Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.expression; + +import java.io.Serializable; + +/** + * A simple compiled pattern. It supports two kinds of wildcards. The '*' (any character zero to many times) + * and the '?' (any character exactly one time). + * @since 2.0 + */ +public class SimplePattern implements Serializable, Comparable<SimplePattern> { + private static final long serialVersionUID = -2477990705739062410L; + + /** + * Matches the <code>value</code> with the compiled expression. The value + * is considered matching if all characters are matched by the expression. A + * partial match is not enough. + * @param value The value to match + * @return <code>true</code> if the value was a match. + */ + public synchronized boolean isMatch(CharSequence value) { + if (node == null) + node = parse(pattern, 0); + return node.match(value, 0); + } + + public String toString() { + return pattern; + } + + public int compareTo(SimplePattern o) { + return pattern.compareTo(o.pattern); + } + + public boolean equals(Object o) { + return o == this || (o instanceof SimplePattern && ((SimplePattern) o).pattern.equals(pattern)); + } + + public int hashCode() { + return 3 * pattern.hashCode(); + } + + private final String pattern; + private transient Node node; + + private SimplePattern(String pattern, Node node) { + this.pattern = pattern; + this.node = node; + } + + private static class RubberBandNode extends Node { + RubberBandNode(Node next) { + super(next); + } + + boolean match(CharSequence value, int pos) { + if (next == null) + return true; + + int top = value.length(); + while (pos < top) { + if (next.match(value, pos++)) + return true; + } + return false; + } + } + + private static class AnyCharacterNode extends Node { + AnyCharacterNode(Node next) { + super(next); + } + + boolean match(CharSequence value, int pos) { + int top = value.length(); + return next == null ? pos + 1 == top : next.match(value, pos + 1); + } + } + + private static class ConstantNode extends Node { + final String constant; + + ConstantNode(Node next, String constant) { + super(next); + this.constant = constant; + } + + boolean match(CharSequence value, int pos) { + int vtop = value.length(); + int ctop = constant.length(); + if (ctop + pos > vtop) + return false; + + for (int idx = 0; idx < ctop; ++idx, ++pos) + if (constant.charAt(idx) != value.charAt(pos)) + return false; + + return next == null ? true : next.match(value, pos); + } + } + + private static abstract class Node { + final Node next; + + Node(Node next) { + this.next = next; + } + + abstract boolean match(CharSequence value, int pos); + } + + public static SimplePattern compile(String pattern) { + if (pattern == null) + throw new IllegalArgumentException("Pattern can not be null"); //$NON-NLS-1$ + return new SimplePattern(pattern, null); + } + + private static Node parse(String pattern, int pos) { + int top = pattern.length(); + StringBuffer bld = null; + Node parsedNode = null; + while (pos < top) { + char c = pattern.charAt(pos); + switch (c) { + case '*' : + parsedNode = new RubberBandNode(parse(pattern, pos + 1)); + break; + case '?' : + parsedNode = new AnyCharacterNode(parse(pattern, pos + 1)); + break; + case '\\' : + if (++pos == top) + throw new IllegalArgumentException("Pattern ends with escape"); //$NON-NLS-1$ + c = pattern.charAt(pos); + // fall through + default : + if (bld == null) + bld = new StringBuffer(); + bld.append(c); + ++pos; + continue; + } + break; + } + + if (bld != null) + parsedNode = new ConstantNode(parsedNode, bld.toString()); + return parsedNode; + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/CategoryMemberQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/CategoryMemberQuery.java new file mode 100644 index 000000000..d6324d360 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/CategoryMemberQuery.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.query; + +import java.util.Collection; +import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.metadata.IRequirement; +import org.eclipse.equinox.p2.query.MatchQuery; + +/** +/** + * A query matching every {@link IInstallableUnit} that is a member + * of the specified category. + * + * @since 2.0 + */ +public class CategoryMemberQuery extends MatchQuery<IInstallableUnit> { + private final Collection<IRequirement> required; + + /** + * Creates a new query that will return the members of the + * given category. If the specified {@link IInstallableUnit} + * is not a category, then no installable unit will satisfy the query. + * + * @param category The category + */ + public CategoryMemberQuery(IInstallableUnit category) { + if (CategoryQuery.isCategory(category)) + this.required = category.getRequiredCapabilities(); + else + this.required = CollectionUtils.emptyList(); + } + + /* + * (non-Javadoc) + * @see org.eclipse.equinox.internal.provisional.p2.metadata.query.MatchQuery#isMatch(java.lang.Object) + */ + public boolean isMatch(IInstallableUnit candidate) { + // since a category lists its members as requirements, then meeting + // any requirement means the candidate is a member of the category. + for (IRequirement req : required) { + if (candidate.satisfies(req)) + return true; + } + return false; + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/CategoryQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/CategoryQuery.java new file mode 100644 index 000000000..d1d86f912 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/CategoryQuery.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.query; + +import org.eclipse.equinox.internal.p2.metadata.query.IUPropertyQuery; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.query.MatchQuery; + +/** + * A query matching every {@link IInstallableUnit} that is a category. + * @since 2.0 + */ +public final class CategoryQuery extends MatchQuery<IInstallableUnit> { + private static final String PROP_TYPE_CATEGORY = "org.eclipse.equinox.p2.type.category"; //$NON-NLS-1$ + private IUPropertyQuery query; + + public CategoryQuery() { + query = new IUPropertyQuery(PROP_TYPE_CATEGORY, null); + } + + public boolean isMatch(IInstallableUnit candidate) { + return query.isMatch(candidate); + } + + /** + * Test if the {@link IInstallableUnit} is a category. + * @param iu the element being tested. + * @return <tt>true</tt> if the parameter is a category. + */ + public static boolean isCategory(IInstallableUnit iu) { + String value = iu.getProperty(PROP_TYPE_CATEGORY); + if (value != null && (value.equals(Boolean.TRUE.toString()))) + return true; + return false; + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/ExpressionQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/ExpressionQuery.java new file mode 100644 index 000000000..ea0cb676b --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/ExpressionQuery.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.query; + +import org.eclipse.equinox.internal.p2.metadata.expression.ExpressionFactory; +import org.eclipse.equinox.p2.metadata.expression.*; +import org.eclipse.equinox.p2.query.MatchQuery; + +/** + * A query that matches candidates against an expression. + */ +public class ExpressionQuery<T> extends MatchQuery<T> { + private final IMatchExpression<T> expression; + private final IEvaluationContext context; + private final Class<T> matchingClass; + + public ExpressionQuery(Class<T> matchingClass, IExpression expression, Object... parameters) { + this(matchingClass, ExpressionUtil.getFactory().<T> matchExpression(expression, parameters)); + } + + public ExpressionQuery(Class<T> matchingClass, IMatchExpression<T> expression) { + this.matchingClass = matchingClass; + this.expression = expression; + this.context = expression.createContext(); + } + + @Override + public boolean isMatch(T candidate) { + if (!matchingClass.isInstance(candidate)) + return false; + ExpressionFactory.THIS.setValue(context, candidate); + return Boolean.TRUE == expression.evaluate(context); + } + + public IMatchExpression<T> getExpression() { + return expression; + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/FragmentQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/FragmentQuery.java new file mode 100644 index 000000000..fc70dd9e9 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/FragmentQuery.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.query; + +import org.eclipse.equinox.p2.metadata.IInstallableUnitFragment; + +import org.eclipse.equinox.internal.p2.metadata.query.IUPropertyQuery; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.query.MatchQuery; + +/** + * A query matching every {@link IInstallableUnit} that is a category. + * @since 2.0 + */ +public final class FragmentQuery extends MatchQuery<IInstallableUnit> { + private static final String PROP_TYPE_FRAGMENT = "org.eclipse.equinox.p2.type.fragment"; //$NON-NLS-1$ + private IUPropertyQuery query; + + public FragmentQuery() { + query = new IUPropertyQuery(PROP_TYPE_FRAGMENT, null); + } + + public boolean isMatch(IInstallableUnit candidate) { + return query.isMatch(candidate); + } + + /** + * Test if the {@link IInstallableUnit} is a fragment. + * @param iu the element being tested. + * @return <tt>true</tt> if the parameter is a fragment. + */ + public static boolean isFragment(IInstallableUnit iu) { + if (iu instanceof IInstallableUnitFragment) + return true; + // String value = iu.getProperty(PROP_TYPE_FRAGMENT); + // if (value != null && (value.equals(Boolean.TRUE.toString()))) + // return true; + return false; + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/GroupQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/GroupQuery.java new file mode 100644 index 000000000..298b04a2f --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/GroupQuery.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.query; + +import org.eclipse.equinox.internal.p2.metadata.query.IUPropertyQuery; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.query.MatchQuery; + +/** + * A query matching every {@link IInstallableUnit} that is a group. + * @since 2.0 + */ +public final class GroupQuery extends MatchQuery<IInstallableUnit> { + private static final String PROP_TYPE_GROUP = "org.eclipse.equinox.p2.type.group"; //$NON-NLS-1$ + private IUPropertyQuery query; + + public GroupQuery() { + query = new IUPropertyQuery(PROP_TYPE_GROUP, null); + } + + public boolean isMatch(IInstallableUnit candidate) { + return query.isMatch(candidate); + } + + /** + * Test if the {@link IInstallableUnit} is a group. + * @param iu the element being tested. + * @return <tt>true</tt> if the parameter is a group. + */ + public static boolean isGroup(IInstallableUnit iu) { + String value = iu.getProperty(PROP_TYPE_GROUP); + if (value != null && (value.equals(Boolean.TRUE.toString()))) + return true; + return false; + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/InstallableUnitQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/InstallableUnitQuery.java index a0152a1e5..ac2110b03 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/InstallableUnitQuery.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/InstallableUnitQuery.java @@ -8,14 +8,20 @@ * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata.query; +package org.eclipse.equinox.p2.metadata.query; -import org.eclipse.equinox.internal.provisional.p2.metadata.*; +import org.eclipse.equinox.p2.metadata.Version; +import org.eclipse.equinox.p2.metadata.VersionRange; + +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.metadata.IVersionedId; +import org.eclipse.equinox.p2.query.MatchQuery; /** * A query that matches on the id and version of an {@link IInstallableUnit}. + * @since 2.0 */ -public class InstallableUnitQuery extends MatchQuery { +public class InstallableUnitQuery extends MatchQuery<IInstallableUnit> { /** * A convenience query that will match any {@link IInstallableUnit} * it encounters. @@ -89,10 +95,7 @@ public class InstallableUnitQuery extends MatchQuery { /* (non-Javadoc) * @see org.eclipse.equinox.p2.query2.Query#isMatch(java.lang.Object) */ - public boolean isMatch(Object object) { - if (!(object instanceof IInstallableUnit)) - return false; - IInstallableUnit candidate = (IInstallableUnit) object; + public boolean isMatch(IInstallableUnit candidate) { if (id != null && !id.equals(candidate.getId())) return false; if (range != null && !range.isIncluded(candidate.getVersion())) diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/PatchQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/PatchQuery.java new file mode 100644 index 000000000..d066db376 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/query/PatchQuery.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.p2.metadata.query; + +import org.eclipse.equinox.internal.p2.metadata.query.IUPropertyQuery; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.query.MatchQuery; + +/** + * A query matching every {@link IInstallableUnit} that is a patch. + * @since 2.0 + */ +public final class PatchQuery extends MatchQuery<IInstallableUnit> { + private static final String PROP_TYPE_PATCH = "org.eclipse.equinox.p2.type.patch"; //$NON-NLS-1$ + private IUPropertyQuery query; + + public PatchQuery() { + query = new IUPropertyQuery(PROP_TYPE_PATCH, Boolean.TRUE.toString()); + } + + public boolean isMatch(IInstallableUnit candidate) { + return query.isMatch(candidate); + } + + /** + * Test if the {@link IInstallableUnit} is a patch. + * @param iu the element being tested. + * @return <tt>true</tt> if the parameter is a patch. + */ + public static boolean isPatch(IInstallableUnit iu) { + String value = iu.getProperty(PROP_TYPE_PATCH); + if (value != null && (value.equals(Boolean.TRUE.toString()))) + return true; + return false; + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/Collector.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/Collector.java index 024280495..f7e36e07d 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/Collector.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/Collector.java @@ -9,14 +9,14 @@ * IBM Corporation - initial API and implementation * EclipseSource - ongoing development *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata.query; - -import org.eclipse.equinox.internal.p2.metadata.Messages; +package org.eclipse.equinox.p2.query; import java.lang.reflect.Array; import java.util.*; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils; +import org.eclipse.equinox.internal.p2.metadata.Messages; /** * A collector is a generic visitor that collects objects passed to it, @@ -26,9 +26,22 @@ import org.eclipse.core.runtime.NullProgressMonitor; * <p> * This default collector just accepts all objects passed to it. Clients may subclass * to perform different processing on the objects passed to it. + * @param <T> The type of object accepted by this collector + * @since 2.0 */ -public class Collector implements IQueryable { - private Set collected = null; +public class Collector<T> implements IQueryResult<T> { + private Set<T> collected = null; + + public static final Collector<?> EMPTY_COLLECTOR = new Collector<Object>() { + public boolean accept(Object val) { + return false; + } + }; + + @SuppressWarnings("unchecked") + public static final <T> Collector<T> emptyCollector() { + return (Collector<T>) EMPTY_COLLECTOR; + } /** * Creates a new collector. @@ -49,22 +62,33 @@ public class Collector implements IQueryable { * @return <code>true</code> if the traversal should continue, * or <code>false</code> to indicate the traversal should stop. */ - public boolean accept(Object object) { + public boolean accept(T object) { getCollection().add(object); return true; } /** - * Returns the collection that is being used to collect results. Unlike {@linkplain #toCollection()}, + * Adds the elements from one collector to this collector + * @param queryResult The collector from which the elements should be retrieved + */ + public void addAll(IQueryResult<T> queryResult) { + boolean keepGoing = true; + for (Iterator<T> iter = queryResult.iterator(); iter.hasNext() && keepGoing;) { + keepGoing = accept(iter.next()); + } + } + + /** + * Returns the collection that is being used to collect results. Unlike {@linkplain #toSet()}, * this returns the actual modifiable collection that is being used to store results. The * return value is only intended to be used within subclasses and should not be exposed * outside of a collection class. * * @return the collection being used to collect results. */ - protected Collection getCollection() { + protected Collection<T> getCollection() { if (collected == null) - collected = new HashSet(); + collected = new HashSet<T>(); return collected; } @@ -82,8 +106,8 @@ public class Collector implements IQueryable { * * @return an iterator of the collected objects. */ - public Iterator iterator() { - return collected == null ? Collections.EMPTY_LIST.iterator() : collected.iterator(); + public Iterator<T> iterator() { + return collected == null ? CollectionUtils.<T> emptyList().iterator() : collected.iterator(); } /** @@ -101,37 +125,48 @@ public class Collector implements IQueryable { * @throws ArrayStoreException the runtime type of the specified array is * not a supertype of the runtime type of every collected object */ - public Object[] toArray(Class clazz) { + public T[] toArray(Class<? extends T> clazz) { int size = collected == null ? 0 : collected.size(); - Object[] result = (Object[]) Array.newInstance(clazz, size); + @SuppressWarnings("unchecked") + T[] result = (T[]) Array.newInstance(clazz, size); if (size != 0) collected.toArray(result); return result; } /** - * Returns the collected objects as an immutable collection. + * Returns a copy of the collected objects. * * @return An unmodifiable collection of the collected objects */ - public Collection toCollection() { - return collected == null ? Collections.EMPTY_SET : Collections.unmodifiableSet(collected); + public Set<T> toSet() { + return collected == null ? new HashSet<T>() : new HashSet<T>(collected); } /** * Performs a query on this results of this collector. */ - public Collector query(Query query, Collector collector, IProgressMonitor monitor) { - Iterator iter = collector == this ? toCollection().iterator() : iterator(); + public IQueryResult<T> query(IQuery<T> query, IProgressMonitor monitor) { + IQueryResult<T> result; if (monitor == null) monitor = new NullProgressMonitor(); try { monitor.beginTask(Messages.performing_subquery, 1); - collector = query.perform(iter, collector); + result = query.perform(iterator()); monitor.worked(1); } finally { monitor.done(); } - return collector; + return result; + } + + /** + * Returns the collected objects as an immutable collection. + * + * @return An unmodifiable collection of the collected objects + */ + @SuppressWarnings("unchecked") + public Set<T> unmodifiableSet() { + return collected == null ? Collections.EMPTY_SET : Collections.unmodifiableSet(collected); } } diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/CompoundQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/CompoundQuery.java index 45cc8b66d..594738036 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/CompoundQuery.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/CompoundQuery.java @@ -9,10 +9,12 @@ * IBM Corporation - initial API and implementation * EclipseSource - ongoing development *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata.query; - +package org.eclipse.equinox.p2.query; +import java.lang.reflect.Array; import java.util.*; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.equinox.internal.p2.query.QueryHelpers; /** * A query that combines a group of sub-queries.<P> @@ -20,15 +22,16 @@ import java.util.*; * In a CompoundQuery each sub-query is executed and the results are combined using * either logical AND or logical OR operations. <P> * - * Clients are expected to call {@link CompoundQuery#createCompoundQuery(Query[], boolean)} + * Clients are expected to call {@link CompoundQuery#createCompoundQuery(IQuery[], boolean)} * to create a concrete instance of a CompoundQuery. If all Queries are instances of * {@link IMatchQuery} then the resulting compound query will be a MatchCompoundQuery, otherwise the * resulting compound query will be a {@link ContextQuery}. * * @noextend This class is not intended to be subclassed by clients. + * @since 2.0 */ -public abstract class CompoundQuery implements Query { - protected Query[] queries; +public abstract class CompoundQuery<T> implements ICompositeQuery<T> { + protected IQuery<T>[] queries; protected boolean and; /** @@ -45,18 +48,23 @@ public abstract class CompoundQuery implements Query { * @param and <code>true</code> if this query represents a logical 'and', and * <code>false</code> if this query represents a logical 'or'. */ - public static CompoundQuery createCompoundQuery(Query[] queries, boolean and) { + public static <T> CompoundQuery<T> createCompoundQuery(IQuery<T>[] queries, boolean and) { if (isMatchQueries(queries)) { - return new CompoundQuery.MatchCompoundQuery(queries, and); + return new CompoundQuery.MatchCompoundQuery<T>(queries, and); } - return new CompoundQuery.ContextCompoundQuery(queries, and); + return new CompoundQuery.ContextCompoundQuery<T>(queries, and); + } + + @SuppressWarnings("unchecked") + public static <T> CompoundQuery<T> createCompoundQuery(IQuery<T> query1, IQuery<T> query2, boolean and) { + return createCompoundQuery(new IQuery[] {query1, query2}, and); } /** * Returns the queries that make up this compound query */ - public Query[] getQueries() { - return queries; + public List<IQuery<T>> getQueries() { + return Arrays.asList(queries); } /** @@ -69,7 +77,7 @@ public abstract class CompoundQuery implements Query { return and; } - protected CompoundQuery(Query[] queries, boolean and) { + protected CompoundQuery(IQuery<T>[] queries, boolean and) { this.queries = queries; this.and = and; } @@ -77,9 +85,9 @@ public abstract class CompoundQuery implements Query { /** * @param queries */ - private static boolean isMatchQueries(Query[] queries) { + private static boolean isMatchQueries(IQuery<?>[] queries) { for (int i = 0; i < queries.length; i++) { - if (!(queries[i] instanceof IMatchQuery)) { + if (!(queries[i] instanceof IMatchQuery<?>)) { return false; } } @@ -104,15 +112,15 @@ public abstract class CompoundQuery implements Query { /** * The compound query instantiated when all queries are Match Queries. */ - private static class MatchCompoundQuery extends CompoundQuery implements IMatchQuery { + private static class MatchCompoundQuery<T> extends CompoundQuery<T> implements IMatchQuery<T> { - protected MatchCompoundQuery(Query[] queries, boolean and) { + protected MatchCompoundQuery(IQuery<T>[] queries, boolean and) { super(queries, and); } - public boolean isMatch(Object candidate) { + public boolean isMatch(T candidate) { for (int i = 0; i < queries.length; i++) { - boolean valid = ((IMatchQuery) queries[i]).isMatch(candidate); + boolean valid = ((IMatchQuery<T>) queries[i]).isMatch(candidate); // if we are OR'ing then the first time we find a requirement that is met, return success if (valid && !and) return true; @@ -130,11 +138,12 @@ public abstract class CompoundQuery implements Query { * Performs this query on the given iterator, passing all objects in the iterator * that match the criteria of this query to the given result. */ - public final Collector perform(Iterator iterator, Collector result) { + public final IQueryResult<T> perform(Iterator<T> iterator) { prePerform(); + Collector<T> result = new Collector<T>(); try { while (iterator.hasNext()) { - Object candidate = iterator.next(); + T candidate = iterator.next(); if (isMatch(candidate)) if (!result.accept(candidate)) break; @@ -147,13 +156,13 @@ public abstract class CompoundQuery implements Query { public void prePerform() { for (int i = 0; i < queries.length; i++) { - ((IMatchQuery) queries[i]).prePerform(); + ((IMatchQuery<T>) queries[i]).prePerform(); } } public void postPerform() { for (int i = 0; i < queries.length; i++) { - ((IMatchQuery) queries[i]).postPerform(); + ((IMatchQuery<T>) queries[i]).postPerform(); } } } @@ -162,57 +171,82 @@ public abstract class CompoundQuery implements Query { * The compound query instantiated when any of the queries are not * match queries. */ - private static class ContextCompoundQuery extends CompoundQuery { + private static class ContextCompoundQuery<T> extends CompoundQuery<T> { - protected ContextCompoundQuery(Query[] queries, boolean and) { + protected ContextCompoundQuery(IQuery<T>[] queries, boolean and) { super(queries, and); } /* * A collector that takes the set to puts the elements in. */ - class SetCollector extends Collector { - Set s = null; + static class SetCollector<T> implements IQueryResult<T> { + private final Set<T> s; - public SetCollector(Set s) { + public SetCollector(Set<T> s) { this.s = s; } - public boolean accept(Object object) { - s.add(object); - return true; + public boolean isEmpty() { + return s.isEmpty(); } - } - public Collector perform(Iterator iterator, Collector result) { - if (queries.length < 1) - return result; + public Iterator<T> iterator() { + return s.iterator(); + } - Collection data = new LinkedList(); + @SuppressWarnings("unchecked") + public T[] toArray(Class<? extends T> clazz) { + return s.toArray((T[]) Array.newInstance(clazz, s.size())); + } - while (iterator.hasNext()) { - data.add(iterator.next()); + public IQueryResult<T> query(IQuery<T> query, IProgressMonitor monitor) { + return query.perform(iterator()); } - Set[] resultSets = new Set[queries.length]; - for (int i = 0; i < queries.length; i++) { - resultSets[i] = new HashSet(); - queries[i].perform(data.iterator(), new SetCollector(resultSets[i])); + public Set<T> toSet() { + return new HashSet<T>(s); } - Set set = resultSets[0]; - for (int i = 1; i < resultSets.length; i++) { - if (isAnd()) - set.retainAll(resultSets[i]); - else - set.addAll(resultSets[i]); + public Set<T> unmodifiableSet() { + return Collections.unmodifiableSet(s); } + } - Iterator resultIterator = set.iterator(); - boolean gatherResults = true; - while (resultIterator.hasNext() && gatherResults) - gatherResults = result.accept(resultIterator.next()); - return result; + public IQueryResult<T> perform(Iterator<T> iterator) { + if (queries.length < 1) + return Collector.emptyCollector(); + + if (queries.length == 1) + return queries[0].perform(iterator); + + Collection<T> data = new ArrayList<T>(); + while (iterator.hasNext()) + data.add(iterator.next()); + + Set<T> result; + if (isAnd()) { + result = queries[0].perform(data.iterator()).unmodifiableSet(); + for (int i = 1; i < queries.length && result.size() > 0; i++) { + HashSet<T> retained = new HashSet<T>(); + Iterator<T> itor = queries[i].perform(data.iterator()).iterator(); + while (itor.hasNext()) { + T nxt = itor.next(); + if (result.contains(nxt)) + retained.add(nxt); + } + result = retained; + } + } else { + result = queries[0].perform(data.iterator()).toSet(); + for (int i = 1; i < queries.length; i++) { + Iterator<T> itor = queries[i].perform(data.iterator()).iterator(); + while (itor.hasNext()) { + result.add(itor.next()); + } + } + } + return new SetCollector<T>(result); } } } diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/CompoundQueryable.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/CompoundQueryable.java index ff7484c73..bbfd2bd20 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/CompoundQueryable.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/CompoundQueryable.java @@ -7,55 +7,79 @@ * Contributors: * EclipseSource - initial API and implementation ******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata.query; +package org.eclipse.equinox.p2.query; import java.lang.reflect.Array; import java.util.*; import org.eclipse.core.runtime.*; +import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils; /** * A queryable that holds a number of other IQueryables and provides * a mechanism for querying the entire set. + * @since 2.0 */ -public class CompoundQueryable implements IQueryable { +public class CompoundQueryable<T> implements IQueryable<T> { - private IQueryable[] queryables; + private IQueryable<T>[] queryables; - public CompoundQueryable(IQueryable[] queryables) { + private CompoundQueryable(IQueryable<T>[] queryables) { this.queryables = queryables; } - public Collector query(Query query, Collector collector, IProgressMonitor monitor) { + @SuppressWarnings("unchecked") + public CompoundQueryable(Collection<? extends IQueryable<T>> queryables) { + this(queryables.toArray(new IQueryable[queryables.size()])); + } + + @SuppressWarnings("unchecked") + public CompoundQueryable(IQueryable<T> q) { + this(new IQueryable[] {q}); + } + + @SuppressWarnings("unchecked") + public CompoundQueryable(IQueryable<T> q1, IQueryable<T> q2) { + this(new IQueryable[] {q1, q2}); + } + + @SuppressWarnings("unchecked") + public CompoundQueryable(IQueryable<T> q1, IQueryable<T> q2, IQueryable<T> q3) { + this(new IQueryable[] {q1, q2, q3}); + } + + public IQueryResult<T> query(IQuery<T> query, IProgressMonitor monitor) { + IQueryResult<T> subResults = null; if (monitor == null) { monitor = new NullProgressMonitor(); } - boolean isMatchQuery = query instanceof IMatchQuery; - Collector results = collector; + boolean isMatchQuery = query instanceof IMatchQuery<?>; int totalWork = isMatchQuery ? queryables.length : queryables.length + 1; try { SubMonitor subMonitor = SubMonitor.convert(monitor, totalWork * 10); + Collector<T> results; if (!isMatchQuery) { // If it is not a match query, then collect the results // as a list, we will query this list for the final results - results = new ListCollector(); - } + results = new ListCollector<T>(); + } else + results = new Collector<T>(); + for (int i = 0; i < queryables.length; i++) { if (subMonitor.isCanceled()) break; - results = queryables[i].query(query, results, subMonitor.newChild(10)); + subResults = queryables[i].query(query, subMonitor.newChild(10)); + results.addAll(subResults); } - if (!isMatchQuery) { - // If it is not a MatchQuery then we must query the results. - collector = results.query(query, collector, subMonitor.newChild(10)); - } else - collector = results; + if (isMatchQuery) + return results; + + // If it is not a MatchQuery then we must query the results. + return results.query(query, subMonitor.newChild(10)); } finally { monitor.done(); } - - return collector; } /** @@ -70,16 +94,16 @@ public class CompoundQueryable implements IQueryable { * @noextend This class is not intended to be subclassed by clients. * */ - public class ListCollector extends Collector { - private List collected; + public static class ListCollector<T> extends Collector<T> { + private List<T> collected; public ListCollector() { super(); } - protected Collection getCollection() { + protected Collection<T> getCollection() { if (collected == null) - collected = new ArrayList(); + collected = new ArrayList<T>(); return collected; } @@ -87,17 +111,18 @@ public class CompoundQueryable implements IQueryable { return collected == null || collected.isEmpty(); } - public Object[] toArray(Class clazz) { + @SuppressWarnings("unchecked") + public T[] toArray(Class<? extends T> clazz) { int size = collected == null ? 0 : collected.size(); - Object[] result = (Object[]) Array.newInstance(clazz, size); + T[] result = (T[]) Array.newInstance(clazz, size); if (size != 0) collected.toArray(result); return result; } - public boolean accept(Object object) { + public boolean accept(T object) { if (collected == null) - collected = new ArrayList(); + collected = new ArrayList<T>(); collected.add(object); return true; } @@ -107,12 +132,12 @@ public class CompoundQueryable implements IQueryable { * * @return An unmodifiable collection of the collected objects */ - public Collection toCollection() { - return collected == null ? Collections.EMPTY_LIST : Collections.unmodifiableList(collected); + public Set<T> toSet() { + return collected == null ? new HashSet<T>() : new HashSet<T>(collected); } - public Iterator iterator() { - return collected == null ? Collections.EMPTY_LIST.iterator() : collected.iterator(); + public Iterator<T> iterator() { + return collected == null ? CollectionUtils.<T> emptyList().iterator() : collected.iterator(); } public int size() { diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/ContextQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/ContextQuery.java index 532f9e691..f65aa8a52 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/ContextQuery.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/ContextQuery.java @@ -7,10 +7,10 @@ * Contributors: * EclipseSource - initial API and implementation ******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata.query; - +package org.eclipse.equinox.p2.query; import java.util.Iterator; +import org.eclipse.equinox.internal.p2.query.QueryHelpers; /** * ContextQuery is the abstract superclass for Queries that require the entire @@ -22,7 +22,7 @@ import java.util.Iterator; * need for a non-transitive query, please see: * https://bugs.eclipse.org/bugs/show_bug.cgi?id=261403 * <p> - * Users of this query must call {@link #perform(Iterator, Collector)} to compute + * Users of this query must call {@link #perform(Iterator)} to compute * the results. <P> * This class may be subclassed by clients. Subclasses should specify the type * of object they support querying on. Subclasses are also encouraged to clearly @@ -30,19 +30,18 @@ import java.util.Iterator; * computation, to allow {@link IQueryable} implementations to optimize their * execution of the query. <P> * + * @since 2.0 */ -public abstract class ContextQuery implements Query { +public abstract class ContextQuery<T> implements IQuery<T> { /** * Evaluates the query for a specific input. * * @param iterator The elements for which to evaluate the query on - * @param result A collector to collect the results. For each element accepted - * by the query,{@link Collector#accept(Object)} must be called. * @return The results of the query. The collector returned must be * the collector passed in. */ - public abstract Collector perform(Iterator iterator, Collector result); + public abstract IQueryResult<T> perform(Iterator<T> iterator); /** * Gets the ID for this Query. diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/ICompositeQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/ICompositeQuery.java new file mode 100644 index 000000000..1b0baeae5 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/ICompositeQuery.java @@ -0,0 +1,27 @@ +/******************************************************************************* +* Copyright (c) 2009, 2010 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.p2.query; + +import java.util.List; + +/** + * A query that contains a number of sub queries. All queries that support sub-queries + * should implement this interface so clients can access the sub queries. + * @since 2.0 + */ +public interface ICompositeQuery<T> extends IQuery<T> { + + /** + * Returns all the child queries of a CompositeQuery. + * @return All the child queries. + */ + public List<IQuery<T>> getQueries(); + +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/IMatchQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/IMatchQuery.java index 6a3830611..b25b044b6 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/IMatchQuery.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/IMatchQuery.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2009 EclipseSource and others. All rights reserved. This +* Copyright (c) 2009, 2010 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 @@ -7,7 +7,7 @@ * Contributors: * EclipseSource - initial API and implementation ******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata.query; +package org.eclipse.equinox.p2.query; /** * A query in which the elements can be evaluated by calling isMatch on. Each @@ -15,9 +15,11 @@ package org.eclipse.equinox.internal.provisional.p2.metadata.query; * can be evaluated in parallel as each call {@link #isMatch(Object)} is mutually * exclusive from all other calls. <P> * - * @spi Clients should not implement this interface, but rather extend {@link MatchQuery}. + * @noimplement This interface is not intended to be implemented by clients. Clients + * creating custom queries must extend {@link MatchQuery} instead. + * @since 2.0 */ -public interface IMatchQuery extends Query { +public interface IMatchQuery<T> extends IQuery<T> { /** * Returns whether the given object satisfies the parameters of this query. @@ -28,7 +30,7 @@ public interface IMatchQuery extends Query { * * @noreference This method is not intended to be referenced by clients. */ - public boolean isMatch(Object candidate); + public boolean isMatch(T candidate); /** * Execute any pre-processing that must be done before this query is performed against diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/Query.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/IQuery.java index a9acfdfa2..9c61de05f 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/Query.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/IQuery.java @@ -9,7 +9,7 @@ * IBM Corporation - initial API and implementation * EclipseSource - ongoing development *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata.query; +package org.eclipse.equinox.p2.query; import java.util.Iterator; @@ -18,24 +18,20 @@ import java.util.Iterator; * The superclass of all queries that can be performed on an {@link IQueryable}. * <p> * - * <B>NOTE: This interface does not follow the proper naming convention. It should - * be IQuery, however, for historic reasons it is Query. This is likely to change.</B> - * * @noimplement This interface is not intended to be implemented by clients. * @noextend This interface is not intended to be extended by clients. + * @since 2.0 */ -public interface Query { +public interface IQuery<T> { /** * Evaluates the query for a specific input. * * @param iterator The elements for which to evaluate the query on - * @param result A collector to collect the results. For each element accepted - * by the query,{@link Collector#accept(Object)} must be called. * @return The results of the query. The collector returned must be * the collector passed in. */ - public abstract Collector perform(Iterator iterator, Collector result); + public abstract IQueryResult<T> perform(Iterator<T> iterator); /** * Gets the ID for this Query. diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/IQueryResult.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/IQueryResult.java new file mode 100644 index 000000000..12e0cbf1c --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/IQueryResult.java @@ -0,0 +1,57 @@ +/******************************************************************************* +* 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.p2.query; + +import java.util.Iterator; +import java.util.Set; + +/** + * An IQueryResult represents the results of a query. + * @since 2.0 + * + */ +public interface IQueryResult<T> extends IQueryable<T> { + /** + * Returns whether this QueryResult is empty. + * @return <code>true</code> if this QueryResult has accepted any results, + * and <code>false</code> otherwise. + */ + public boolean isEmpty(); + + /** + * Returns an iterator on the collected objects. + * + * @return an iterator of the collected objects. + */ + public Iterator<T> iterator(); + + /** + * Returns the collected objects as an array + * + * @param clazz The type of array to return + * @return The array of results + * @throws ArrayStoreException the runtime type of the specified array is + * not a supertype of the runtime type of every collected object + */ + public T[] toArray(Class<? extends T> clazz); + + /** + * Creates a new Set copy with the contents of this query result. The + * copy can be altered without any side effects on its origin. + * @return A detached copy of the result. + */ + public Set<T> toSet(); + + /** + * Returns a Set backed by this query result. The set is immutable. + * @return A Set backed by this query result. + */ + public Set<T> unmodifiableSet(); +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/IQueryable.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/IQueryable.java index 3389d5182..22b4d8bd7 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/IQueryable.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/IQueryable.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2007, 2008 IBM Corporation and others. + * Copyright (c) 2007, 2010 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 @@ -8,8 +8,7 @@ * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata.query; - +package org.eclipse.equinox.p2.query; import org.eclipse.core.runtime.IProgressMonitor; @@ -17,8 +16,9 @@ import org.eclipse.core.runtime.IProgressMonitor; * An IQueryable contains objects, and is able to perform queries on those objects. * <p> * This interface may be implemented by clients. + * @since 2.0 */ -public interface IQueryable { +public interface IQueryable<T> { /** * Performs a query, passing any objects that satisfy the * query to the provided collector. @@ -28,10 +28,9 @@ public interface IQueryable { * </p> * * @param query The query to perform - * @param collector Collects the results of the query * @param monitor a progress monitor, or <code>null</code> if progress * reporting is not desired * @return The collector argument */ - public Collector query(Query query, Collector collector, IProgressMonitor monitor); + public IQueryResult<T> query(IQuery<T> query, IProgressMonitor monitor); } diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/LimitQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/LimitQuery.java new file mode 100644 index 000000000..6da75ca1f --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/LimitQuery.java @@ -0,0 +1,57 @@ +/******************************************************************************* +* 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.p2.query; + +import java.util.*; + +/** + * A limit query can be used to limit the number of query results returned. Once + * the limit is reached, the query is terminated. + * @since 2.0 + */ +public class LimitQuery<T> extends ContextQuery<T> implements ICompositeQuery<T> { + + private final IQuery<T> query; + private final int limit; + + public LimitQuery(IQuery<T> query, int limit) { + this.query = query; + this.limit = limit; + } + + public IQueryResult<T> perform(Iterator<T> iterator) { + if (limit == 0) + return Collector.emptyCollector(); + + int count = 0; + Collector<T> result = new Collector<T>(); + if (query instanceof IMatchQuery<?>) { + IMatchQuery<T> matchQuery = (IMatchQuery<T>) query; + while (iterator.hasNext()) { + T candidate = iterator.next(); + if (matchQuery.isMatch(candidate)) { + result.accept(candidate); + if (++count >= limit) + break; + } + } + } else { + iterator = query.perform(iterator).iterator(); + while (++count <= limit && iterator.hasNext()) + result.accept(iterator.next()); + } + return result; + } + + public List<IQuery<T>> getQueries() { + return Collections.singletonList(query); + } + +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/MatchQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/MatchQuery.java index 86b647663..f8e4e8a2d 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/MatchQuery.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/MatchQuery.java @@ -8,10 +8,10 @@ * EclipseSource - initial API and implementation * IBM Corporation - ongoing development ******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata.query; - +package org.eclipse.equinox.p2.query; import java.util.Iterator; +import org.eclipse.equinox.internal.p2.query.QueryHelpers; /** * This class represents the superclass of most of p2's queries. Every element @@ -24,8 +24,9 @@ import java.util.Iterator; * specify their match algorithm, and expose the parameters involved in the match * computation, to allow {@link IQueryable} implementations to optimize their * execution of the query. + * @since 2.0 */ -public abstract class MatchQuery implements IMatchQuery { +public abstract class MatchQuery<T> implements IMatchQuery<T> { /** * Returns whether the given object satisfies the parameters of this query. @@ -35,9 +36,9 @@ public abstract class MatchQuery implements IMatchQuery { * of this query, and <code>false</code> otherwise * * @noreference This method is not intended to be referenced by clients. - * Clients should call {@link #perform(Iterator, Collector)} + * Clients should call {@link #perform(Iterator)} */ - public abstract boolean isMatch(Object candidate); + public abstract boolean isMatch(T candidate); /** * Gets the ID for this Query. @@ -58,12 +59,13 @@ public abstract class MatchQuery implements IMatchQuery { * Performs this query on the given iterator, passing all objects in the iterator * that match the criteria of this query to the given result. */ - public final Collector perform(Iterator iterator, Collector result) { + public final IQueryResult<T> perform(Iterator<T> iterator) { + Collector<T> result = new Collector<T>(); prePerform(); try { while (iterator.hasNext()) { - Object candidate = iterator.next(); - if (isMatch(candidate)) + T candidate = iterator.next(); + if (candidate != null && isMatch(candidate)) if (!result.accept(candidate)) break; } diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/CompositeQuery.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/PipedQuery.java index a4c88a629..07147a1cf 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/provisional/p2/metadata/query/CompositeQuery.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/query/PipedQuery.java @@ -7,23 +7,29 @@ * Contributors: * EclipseSource - initial API and implementation ******************************************************************************/ -package org.eclipse.equinox.internal.provisional.p2.metadata.query; +package org.eclipse.equinox.p2.query; - -import java.util.Iterator; +import java.util.*; +import org.eclipse.equinox.internal.p2.query.QueryHelpers; /** - * A Composite Query is an aggregate query in which each sub-query + * A PipedQuery is an aggregate query in which each sub-query * is executed in succession. The results from the ith sub-query * are piped as input into the i+1th sub-query. + * @since 2.0 */ -public class CompositeQuery implements Query { - protected Query[] queries; +public class PipedQuery<T> implements ICompositeQuery<T> { + protected final IQuery<T>[] queries; - public CompositeQuery(Query[] queries) { + public PipedQuery(IQuery<T>[] queries) { this.queries = queries; } + @SuppressWarnings("unchecked") + public PipedQuery(IQuery<T> query1, IQuery<T> query2) { + this(new IQuery[] {query1, query2}); + } + /** * Gets the ID for this Query. */ @@ -40,25 +46,21 @@ public class CompositeQuery implements Query { } /** - * Set the queries of this composite. This is needed to allow subclasses of - * CompsiteQuery to set the queries in a constructor + * Returns the queries that make up this compound query */ - protected final void setQueries(Query[] queries) { - this.queries = queries; + public List<IQuery<T>> getQueries() { + return Arrays.asList(queries); } - public Collector perform(Iterator iterator, Collector result) { - Collector collector; - Iterator iter = iterator; - for (int i = 0; i < queries.length; i++) { - // Take the results of the previous query and using them - // to drive the next one (i.e. composing queries) - collector = queries[i].perform(iter, new Collector()); - iter = collector.iterator(); + public IQueryResult<T> perform(Iterator<T> iterator) { + IQueryResult<T> last = Collector.emptyCollector(); + if (queries.length > 0) { + last = queries[0].perform(iterator); + for (int i = 1; i < queries.length; i++) + // Take the results of the previous query and using them + // to drive the next one (i.e. composing queries) + last = queries[i].perform(last.iterator()); } - boolean gatherResults = true; - while (iter.hasNext() && gatherResults) - gatherResults = result.accept(iter.next()); - return result; + return last; } } |