From b5fe42dfc2ca68d6153010efb901560e415c6dad Mon Sep 17 00:00:00 2001 From: Alexander Kurtakov Date: Tue, 23 Jan 2018 00:18:39 +0200 Subject: Revert "Revert "Bug 528387 - Dedicated xml elements for generic requirements"" This reverts commit 0e3caa9359428dcb20c1dc3a2c580fff64777d3d. Change-Id: Ifafe2feedb963b90f54a19b2e08a49d3b18d475a Signed-off-by: Alexander Kurtakov --- .../metadata/repository/MetadataRepositoryIO.java | 2 +- .../p2/metadata/repository/io/MetadataParser.java | 250 ++++++++---- .../p2/metadata/repository/io/MetadataWriter.java | 121 +++--- .../p2/metadata/repository/io/XMLConstants.java | 32 +- .../internal/p2/metadata/ProvidedCapability.java | 125 +++--- .../internal/p2/metadata/RequiredCapability.java | 51 +-- .../p2/metadata/RequiredPropertiesMatch.java | 84 ++++ .../equinox/internal/p2/metadata/Requirement.java | 14 +- .../p2/metadata/expression/CoercingComparator.java | 4 +- .../p2/metadata/index/CapabilityIndex.java | 4 +- .../internal/p2/metadata/messages.properties | 2 +- .../equinox/p2/metadata/IProvidedCapability.java | 39 +- .../equinox/p2/metadata/MetadataFactory.java | 137 ++++--- .../META-INF/MANIFEST.MF | 2 + .../p2/publisher/eclipse/BundlesAction.java | 91 +++-- .../p2/publisher/AbstractPublisherAction.java | 2 +- .../internal/p2/persistence/XMLConstants.java | 16 + .../equinox/internal/p2/persistence/XMLWriter.java | 82 +++- .../p2/tests/director/AutomatedDirectorTest.java | 255 ++++++++---- .../equinox/p2/tests/engine/PhaseSetTest.java | 33 +- .../p2/tests/metadata/RequirementToString.java | 13 +- .../p2/tests/metadata/expression/AllTests.java | 9 +- .../p2/tests/metadata/expression/FilterTest.java | 439 +++++++++++---------- .../repository/SPIMetadataRepositoryTest.java | 20 +- .../p2/tests/planner/PropertyMatchRequirement.java | 111 ++++++ .../p2/tests/publisher/actions/ActionTest.java | 23 +- .../tests/publisher/actions/BundlesActionTest.java | 109 +++-- 27 files changed, 1315 insertions(+), 755 deletions(-) create mode 100644 bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequiredPropertiesMatch.java create mode 100644 bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/planner/PropertyMatchRequirement.java diff --git a/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/MetadataRepositoryIO.java b/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/MetadataRepositoryIO.java index ed67c081a..4243ad59d 100644 --- a/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/MetadataRepositoryIO.java +++ b/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/MetadataRepositoryIO.java @@ -104,7 +104,7 @@ public class MetadataRepositoryIO { // A format version number for metadata repository XML. public static final Version COMPATIBLE_VERSION = Version.createOSGi(1, 0, 0); - public static final Version CURRENT_VERSION = Version.createOSGi(1, 1, 0); + public static final Version CURRENT_VERSION = Version.createOSGi(1, 2, 0); public static final VersionRange XML_TOLERANCE = new VersionRange(COMPATIBLE_VERSION, true, Version.createOSGi(2, 0, 0), false); // Constants for processing Instructions diff --git a/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/io/MetadataParser.java b/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/io/MetadataParser.java index 20fc56a8d..024631745 100644 --- a/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/io/MetadataParser.java +++ b/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/io/MetadataParser.java @@ -12,11 +12,14 @@ *******************************************************************************/ package org.eclipse.equinox.internal.p2.metadata.repository.io; +import static java.util.stream.Collectors.toList; + import java.net.URI; import java.util.*; import java.util.Map.Entry; import org.eclipse.equinox.internal.p2.core.helpers.OrderedProperties; -import org.eclipse.equinox.internal.p2.metadata.*; +import org.eclipse.equinox.internal.p2.metadata.ArtifactKey; +import org.eclipse.equinox.internal.p2.metadata.InstallableUnit; import org.eclipse.equinox.internal.p2.persistence.XMLParser; import org.eclipse.equinox.p2.metadata.*; import org.eclipse.equinox.p2.metadata.MetadataFactory.*; @@ -30,18 +33,6 @@ import org.xml.sax.ContentHandler; public abstract class MetadataParser extends XMLParser implements XMLConstants { static final ILicense[] NO_LICENSES = new ILicense[0]; - static final String ATTR_TYPE_LIST_HEAD = "List<"; //$NON-NLS-1$ - static final String ATTR_TYPE_STRING = String.class.getSimpleName(); - static final String ATTR_TYPE_INTEGER = Integer.class.getSimpleName(); - static final String ATTR_TYPE_LONG = Long.class.getSimpleName(); - static final String ATTR_TYPE_FLOAT = Float.class.getSimpleName(); - static final String ATTR_TYPE_DOUBLE = Double.class.getSimpleName(); - static final String ATTR_TYPE_BYTE = Byte.class.getSimpleName(); - static final String ATTR_TYPE_SHORT = Short.class.getSimpleName(); - static final String ATTR_TYPE_CHARACTER = Character.class.getSimpleName(); - static final String ATTR_TYPE_BOOLEAN = Boolean.class.getSimpleName(); - static final String ATTR_TYPE_VERSION = Version.class.getSimpleName(); - public MetadataParser(BundleContext context, String bundleId) { super(context, bundleId); } @@ -322,7 +313,7 @@ public abstract class MetadataParser extends XMLParser implements XMLConstants { IProvidedCapability[] providedCapabilities = (providedCapabilitiesHandler == null ? new IProvidedCapability[0] : providedCapabilitiesHandler.getProvidedCapabilities()); currentUnit.setCapabilities(providedCapabilities); - IRequirement[] requiredCapabilities = (requiredCapabilitiesHandler == null ? new IRequirement[0] : requiredCapabilitiesHandler.getRequiredCapabilities()); + IRequirement[] requiredCapabilities = (requiredCapabilitiesHandler == null ? new IRequirement[0] : requiredCapabilitiesHandler.getRequirements()); currentUnit.setRequirements(requiredCapabilities); IRequirement[] metaRequiredCapabilities = (metaRequiredCapabilitiesHandler == null ? new IRequirement[0] : metaRequiredCapabilitiesHandler.getMetaRequiredCapabilities()); currentUnit.setMetaRequirements(metaRequiredCapabilities); @@ -389,7 +380,7 @@ public abstract class MetadataParser extends XMLParser implements XMLConstants { @Override protected void finished() { if (children != null) { - scopes.add(children.getRequiredCapabilities()); + scopes.add(children.getRequirements()); } } } @@ -517,7 +508,7 @@ public abstract class MetadataParser extends XMLParser implements XMLConstants { private String namespace; private String name; private Version version; - private ProvidedCapabilityAttributesHandler attributesHandler; + private ProvidedCapabilityPropertiesHandler propertiesHandler; private List capabilities; @@ -534,63 +525,68 @@ public abstract class MetadataParser extends XMLParser implements XMLConstants { @Override public void startElement(String elem, Attributes attributes) { - if (elem.equals(CAPABILITY_ATTRIBUTES_ELEMENT)) { - this.attributesHandler = new ProvidedCapabilityAttributesHandler(this, attributes); - } else { - invalidElement(elem, attributes); + switch (elem) { + case PROPERTIES_ELEMENT : + this.propertiesHandler = new ProvidedCapabilityPropertiesHandler(this, attributes); + break; + default : + invalidElement(elem, attributes); + break; } } @Override protected void finished() { - Map capAttrs = (attributesHandler != null) - ? attributesHandler.getAttributes() + Map properties = (propertiesHandler != null) + ? propertiesHandler.getProperties() : new HashMap<>(); - capAttrs.put(namespace, name); - capAttrs.put(ProvidedCapability.ATTRIBUTE_VERSION, version); - IProvidedCapability cap = MetadataFactory.createProvidedCapability(namespace, capAttrs); + properties.put(namespace, name); + properties.put(IProvidedCapability.PROPERTY_VERSION, version); + IProvidedCapability cap = MetadataFactory.createProvidedCapability(namespace, properties); capabilities.add(cap); } } - protected class ProvidedCapabilityAttributesHandler extends AbstractMetadataHandler { - private Map capAttributes; + protected class ProvidedCapabilityPropertiesHandler extends AbstractMetadataHandler { + private Map properties; - public ProvidedCapabilityAttributesHandler(AbstractHandler parentHandler, Attributes attributes) { - super(parentHandler, CAPABILITY_ATTRIBUTES_ELEMENT); - // TODO add getOptionalSize(attributes, 4) - this.capAttributes = new HashMap<>(); + public ProvidedCapabilityPropertiesHandler(AbstractHandler parentHandler, Attributes attributes) { + super(parentHandler, PROPERTIES_ELEMENT); + this.properties = new HashMap<>(getOptionalSize(attributes, 2)); } - public Map getAttributes() { - return capAttributes; + public Map getProperties() { + return properties; } @Override - public void startElement(String name, Attributes attributes) { - if (name.equals(CAPABILITY_ATTRIBUTE_ELEMENT)) { - new ProvidedCapabilityAttributeHandler(this, attributes, capAttributes); - } else { - invalidElement(name, attributes); + public void startElement(String elem, Attributes attributes) { + switch (elem) { + case PROPERTY_ELEMENT : + new ProvidedCapabilityPropertyHandler(this, attributes, properties); + break; + default : + invalidElement(elem, attributes); + break; } } } - protected class ProvidedCapabilityAttributeHandler extends AbstractMetadataHandler { - public ProvidedCapabilityAttributeHandler(AbstractHandler parentHandler, Attributes attributes, Map capAttributes) { - super(parentHandler, CAPABILITY_ATTRIBUTE_ELEMENT); + protected class ProvidedCapabilityPropertyHandler extends AbstractMetadataHandler { + public ProvidedCapabilityPropertyHandler(AbstractHandler parentHandler, Attributes attributes, Map properties) { + super(parentHandler, PROPERTY_ELEMENT); - String[] values = parseRequiredAttributes(attributes, CAPABILITY_ATTRIBUTE_REQUIRED_ATTRIBUTES); + String[] values = parseAttributes(attributes, PROPERTY_ATTRIBUTES, PROPERTY_OPTIONAL_ATTRIBUTES); String name = values[0]; String value = values[1]; - String type = values[2]; + String type = values[2] == null ? PROPERTY_TYPE_STRING : values[2]; - if (type.startsWith(ATTR_TYPE_LIST_HEAD)) { - capAttributes.put(name, parseList(type, value)); + if (type.startsWith(PROPERTY_TYPE_LIST)) { + properties.put(name, parseList(type, value)); } else { - capAttributes.put(name, parseScalar(type, value)); + properties.put(name, parseScalar(type, value)); } } @@ -600,49 +596,53 @@ public abstract class MetadataParser extends XMLParser implements XMLConstants { } private List parseList(String type, String value) { - String elType = type.substring(ATTR_TYPE_LIST_HEAD.length(), type.length() - 1); - - List res = new ArrayList<>(); - for (String el : value.split("\\s*,\\s*")) { //$NON-NLS-1$ - res.add(parseScalar(elType, el)); + final String elType; + if (type.length() > PROPERTY_TYPE_LIST.length()) { + // Strip the leading "List<" and trailing ">" + elType = type.substring(PROPERTY_TYPE_LIST.length() + 1, type.length() - 1); + } else { + elType = PROPERTY_TYPE_STRING; } - return res; + return Arrays.stream(value.split("\\s*,\\s*")) //$NON-NLS-1$ + .map(val -> parseScalar(elType, val)) + .collect(toList()); } private Object parseScalar(String type, String value) { - if (ATTR_TYPE_STRING.equals(type)) { + if (PROPERTY_TYPE_STRING.equals(type)) { return value; } - if (ATTR_TYPE_INTEGER.equals(type)) { + if (PROPERTY_TYPE_INTEGER.equals(type)) { return Integer.parseInt(value); } - if (ATTR_TYPE_LONG.equals(type)) { + if (PROPERTY_TYPE_LONG.equals(type)) { return Long.parseLong(value); } - if (ATTR_TYPE_FLOAT.equals(type)) { + if (PROPERTY_TYPE_FLOAT.equals(type)) { return Float.parseFloat(value); } - if (ATTR_TYPE_DOUBLE.equals(type)) { + if (PROPERTY_TYPE_DOUBLE.equals(type)) { return Double.parseDouble(value); } - if (ATTR_TYPE_BYTE.equals(type)) { + if (PROPERTY_TYPE_BYTE.equals(type)) { return Byte.parseByte(value); } - if (ATTR_TYPE_SHORT.equals(type)) { + if (PROPERTY_TYPE_SHORT.equals(type)) { return Short.parseShort(value); } - if (ATTR_TYPE_CHARACTER.equals(type)) { + if (PROPERTY_TYPE_CHARACTER.equals(type)) { return value.charAt(0); } - if (ATTR_TYPE_BOOLEAN.equals(type)) { + if (PROPERTY_TYPE_BOOLEAN.equals(type)) { return Boolean.parseBoolean(value); } - if (ATTR_TYPE_VERSION.equals(type)) { + if (PROPERTY_TYPE_VERSION.equals(type)) { return Version.create(value); } - // TODO Throw what? - return value.toString(); + + // String is the default + return value; } } @@ -691,23 +691,29 @@ public abstract class MetadataParser extends XMLParser implements XMLConstants { } protected class RequirementsHandler extends AbstractMetadataHandler { - private List requiredCapabilities; + private List requirements; public RequirementsHandler(AbstractHandler parentHandler, Attributes attributes) { super(parentHandler, REQUIREMENTS_ELEMENT); - requiredCapabilities = new ArrayList<>(getOptionalSize(attributes, 4)); + requirements = new ArrayList<>(getOptionalSize(attributes, 4)); } - public IRequirement[] getRequiredCapabilities() { - return requiredCapabilities.toArray(new IRequirement[requiredCapabilities.size()]); + public IRequirement[] getRequirements() { + return requirements.toArray(new IRequirement[requirements.size()]); } @Override public void startElement(String name, Attributes attributes) { - if (name.equals(REQUIREMENT_ELEMENT)) { - new RequirementHandler(this, attributes, requiredCapabilities); - } else { - invalidElement(name, attributes); + switch (name) { + case REQUIREMENT_ELEMENT : + new RequirementHandler(this, attributes, requirements); + break; + case REQUIREMENT_PROPERTIES_ELEMENT : + new RequirementPropertiesHandler(this, attributes, requirements); + break; + default : + invalidElement(name, attributes); + break; } } } @@ -734,31 +740,34 @@ public abstract class MetadataParser extends XMLParser implements XMLConstants { public RequirementHandler(AbstractHandler parentHandler, Attributes attributes, List capabilities) { super(parentHandler, REQUIREMENT_ELEMENT); this.capabilities = capabilities; + + // Version range requirement if (attributes.getIndex(NAMESPACE_ATTRIBUTE) >= 0) { - String[] values = parseAttributes(attributes, REQIURED_CAPABILITY_ATTRIBUTES, OPTIONAL_CAPABILITY_ATTRIBUTES); + String[] values = parseAttributes(attributes, REQIURED_CAPABILITY_ATTRIBUTES, REQUIRED_CAPABILITY_OPTIONAL_ATTRIBUTES); namespace = values[0]; name = values[1]; range = checkVersionRange(REQUIREMENT_ELEMENT, VERSION_RANGE_ATTRIBUTE, values[2]); - boolean isOptional = checkBoolean(REQUIREMENT_ELEMENT, CAPABILITY_OPTIONAL_ATTRIBUTE, values[3], false).booleanValue(); + boolean isOptional = checkBoolean(REQUIREMENT_ELEMENT, REQUIRED_CAPABILITY_OPTIONAL_ATTRIBUTE, values[3], false).booleanValue(); min = isOptional ? 0 : 1; - boolean isMultiple = checkBoolean(REQUIREMENT_ELEMENT, CAPABILITY_MULTIPLE_ATTRIBUTE, values[4], false).booleanValue(); + boolean isMultiple = checkBoolean(REQUIREMENT_ELEMENT, REQUIRED_CAPABILITY_MULTIPLE_ATTRIBUTE, values[4], false).booleanValue(); max = isMultiple ? Integer.MAX_VALUE : 1; - greedy = checkBoolean(REQUIREMENT_ELEMENT, CAPABILITY_GREED_ATTRIBUTE, values[5], true).booleanValue(); - } else { - // Expression based requirement - String[] values = parseAttributes(attributes, REQIUREMENT_ATTRIBUTES, OPTIONAL_REQUIREMENT_ATTRIBUTES); + greedy = checkBoolean(REQUIREMENT_ELEMENT, REQUIREMENT_GREED_ATTRIBUTE, values[5], true).booleanValue(); + } + // IU match expression requirement + else { + String[] values = parseAttributes(attributes, REQUIRED_IU_MATCH_ATTRIBUTES, REQUIRED_IU_MATCH_OPTIONAL_ATTRIBUTES); match = values[0]; matchParams = values[1]; min = values[2] == null ? 1 : checkInteger(REQUIREMENT_ELEMENT, MIN_ATTRIBUTE, values[2]); max = values[3] == null ? 1 : checkInteger(REQUIREMENT_ELEMENT, MAX_ATTRIBUTE, values[3]); - greedy = checkBoolean(REQUIREMENT_ELEMENT, CAPABILITY_GREED_ATTRIBUTE, values[4], true).booleanValue(); + greedy = checkBoolean(REQUIREMENT_ELEMENT, REQUIREMENT_GREED_ATTRIBUTE, values[4], true).booleanValue(); } } @Override public void startElement(String elem, Attributes attributes) { - if (elem.equals(CAPABILITY_FILTER_ELEMENT)) { - filterHandler = new TextHandler(this, CAPABILITY_FILTER_ELEMENT, attributes); + if (elem.equals(REQUIREMENT_FILTER_ELEMENT)) { + filterHandler = new TextHandler(this, REQUIREMENT_FILTER_ELEMENT, attributes); } else if (elem.equals(REQUIREMENT_DESCRIPTION_ELEMENT)) { descriptionHandler = new TextHandler(this, REQUIREMENT_DESCRIPTION_ELEMENT, attributes); } else { @@ -805,6 +814,83 @@ public abstract class MetadataParser extends XMLParser implements XMLConstants { } } + protected class RequirementPropertiesHandler extends AbstractHandler { + private List requirements; + + private String namespace; + private String match; + private int min; + private int max; + private boolean greedy; + + private TextHandler filterHandler; + private TextHandler descriptionHandler; + + public RequirementPropertiesHandler(AbstractHandler parentHandler, Attributes attributes, List requirements) { + super(parentHandler, REQUIREMENT_PROPERTIES_ELEMENT); + this.requirements = requirements; + + String[] values = parseAttributes(attributes, REQIURED_PROPERTIES_MATCH_ATTRIBUTES, REQIURED_PROPERTIES_MATCH_OPTIONAL_ATTRIBUTES); + namespace = values[0]; + match = values[1]; + min = (values[2] == null) ? 1 : checkInteger(REQUIREMENT_PROPERTIES_ELEMENT, MIN_ATTRIBUTE, values[2]); + max = (values[3] == null) ? 1 : checkInteger(REQUIREMENT_PROPERTIES_ELEMENT, MAX_ATTRIBUTE, values[3]); + greedy = checkBoolean(REQUIREMENT_PROPERTIES_ELEMENT, REQUIREMENT_GREED_ATTRIBUTE, values[4], true).booleanValue(); + } + + @Override + public void startElement(String elem, Attributes attributes) { + switch (elem) { + case REQUIREMENT_FILTER_ELEMENT : + filterHandler = new TextHandler(this, REQUIREMENT_FILTER_ELEMENT, attributes); + break; + case REQUIREMENT_DESCRIPTION_ELEMENT : + descriptionHandler = new TextHandler(this, REQUIREMENT_DESCRIPTION_ELEMENT, attributes); + break; + default : + invalidElement(elem, attributes); + break; + } + } + + @Override + protected void finished() { + if (!isValidXML()) { + return; + } + + IMatchExpression filter = null; + if (filterHandler != null) { + try { + filter = InstallableUnit.parseFilter(filterHandler.getText()); + } catch (ExpressionParseException e) { + if (removeWhiteSpace(filterHandler.getText()).equals("(&(|)(|)(|))")) {//$NON-NLS-1$ + // We could log this I guess + } else { + throw e; + } + } + } + + String description = (descriptionHandler != null) ? descriptionHandler.getText() : null; + + IFilterExpression attrMatch = ExpressionUtil.parseLDAP(match); + IRequirement requirement = MetadataFactory.createRequirement(namespace, attrMatch, filter, min, max, greedy, description); + requirements.add(requirement); + } + + private String removeWhiteSpace(String s) { + if (s == null) + return ""; //$NON-NLS-1$ + StringBuffer builder = new StringBuffer(); + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) != ' ') + builder.append(s.charAt(i)); + } + return builder.toString(); + } + } + protected class ArtifactsHandler extends AbstractHandler { private List artifacts; diff --git a/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/io/MetadataWriter.java b/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/io/MetadataWriter.java index 2b7686e35..f88d52bdd 100644 --- a/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/io/MetadataWriter.java +++ b/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/io/MetadataWriter.java @@ -14,11 +14,10 @@ package org.eclipse.equinox.internal.p2.metadata.repository.io; import java.io.OutputStream; import java.net.MalformedURLException; import java.util.*; -import java.util.Map.Entry; import org.eclipse.core.runtime.*; import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; -import org.eclipse.equinox.internal.p2.metadata.ProvidedCapability; import org.eclipse.equinox.internal.p2.metadata.RequiredCapability; +import org.eclipse.equinox.internal.p2.metadata.RequiredPropertiesMatch; import org.eclipse.equinox.internal.p2.metadata.repository.Activator; import org.eclipse.equinox.internal.p2.persistence.XMLWriter; import org.eclipse.equinox.p2.metadata.*; @@ -96,23 +95,23 @@ public class MetadataWriter extends XMLWriter implements XMLConstants { private boolean hasOnlySimpleRequirements(IInstallableUnit iu) { for (IRequirement r : iu.getRequirements()) - if (r.getMax() == 0 || !RequiredCapability.isSimpleRequirement(r.getMatches())) + if (r.getMax() == 0 || !RequiredCapability.isVersionRangeRequirement(r.getMatches())) return false; if (iu.getUpdateDescriptor() != null) { for (IMatchExpression m : iu.getUpdateDescriptor().getIUsBeingUpdated()) { - if (!RequiredCapability.isSimpleRequirement(m)) + if (!RequiredCapability.isVersionRangeRequirement(m)) return false; } } for (IRequirement r : iu.getMetaRequirements()) - if (r.getMax() == 0 || !RequiredCapability.isSimpleRequirement(r.getMatches())) + if (r.getMax() == 0 || !RequiredCapability.isVersionRangeRequirement(r.getMatches())) return false; if (iu instanceof IInstallableUnitFragment) { for (IRequirement r : ((IInstallableUnitFragment) iu).getHost()) - if (!RequiredCapability.isSimpleRequirement(r.getMatches())) + if (!RequiredCapability.isVersionRangeRequirement(r.getMatches())) return false; } @@ -120,11 +119,11 @@ public class MetadataWriter extends XMLWriter implements XMLConstants { IInstallableUnitPatch iuPatch = (IInstallableUnitPatch) iu; for (IRequirement[] rArr : iuPatch.getApplicabilityScope()) for (IRequirement r : rArr) - if (!RequiredCapability.isSimpleRequirement(r.getMatches())) + if (!RequiredCapability.isVersionRangeRequirement(r.getMatches())) return false; IRequirement lifeCycle = iuPatch.getLifeCycle(); - if (lifeCycle != null && !RequiredCapability.isSimpleRequirement(lifeCycle.getMatches())) + if (lifeCycle != null && !RequiredCapability.isVersionRangeRequirement(lifeCycle.getMatches())) return false; } return true; @@ -167,50 +166,12 @@ public class MetadataWriter extends XMLWriter implements XMLConstants { attribute(NAME_ATTRIBUTE, capability.getName()); attribute(VERSION_ATTRIBUTE, capability.getVersion()); - Map attrs = new HashMap<>(capability.getAttributes()); - attrs.remove(capability.getNamespace()); - attrs.remove(ProvidedCapability.ATTRIBUTE_VERSION); + Map props = new HashMap<>(capability.getProperties()); + props.remove(capability.getNamespace()); + props.remove(IProvidedCapability.PROPERTY_VERSION); - if (!attrs.isEmpty()) { - start(CAPABILITY_ATTRIBUTES_ELEMENT); - attribute(COLLECTION_SIZE_ATTRIBUTE, attrs.size()); - - for (Entry attr : attrs.entrySet()) { - start(CAPABILITY_ATTRIBUTE_ELEMENT); - - String name = attr.getKey(); - Object val = attr.getValue(); - String type; - - if (Collection.class.isAssignableFrom(val.getClass())) { - Collection coll = (Collection) val; - - String elType = coll.iterator().next().getClass().getSimpleName(); - type = String.format("List<%s>", elType); //$NON-NLS-1$ - - StringBuilder valBuff = new StringBuilder(); - for (Iterator iter = coll.iterator(); iter.hasNext();) { - String el = iter.next().toString(); - - valBuff.append(el); - if (iter.hasNext()) { - valBuff.append(","); //$NON-NLS-1$ - } - } - - val = valBuff.toString(); - } else { - type = val.getClass().getSimpleName(); - val = val.toString(); - } - - attribute(CAPABILITY_ATTRIBUTE_NAME_ATTRIBUTE, name); - attribute(CAPABILITY_ATTRIBUTE_TYPE_ATTRIBUTE, type); - attribute(CAPABILITY_ATTRIBUTE_VALUE_ATTRIBUTE, val); - - end(CAPABILITY_ATTRIBUTE_ELEMENT); - } - end(CAPABILITY_ATTRIBUTES_ELEMENT); + if (!props.isEmpty()) { + writeProperties(props); } end(PROVIDED_CAPABILITY_ELEMENT); @@ -246,7 +207,7 @@ public class MetadataWriter extends XMLWriter implements XMLConstants { throw new IllegalStateException(); IMatchExpression singleUD = descriptor.getIUsBeingUpdated().iterator().next(); start(UPDATE_DESCRIPTOR_ELEMENT); - if (RequiredCapability.isSimpleRequirement(singleUD)) { + if (RequiredCapability.isVersionRangeRequirement(singleUD)) { attribute(ID_ATTRIBUTE, RequiredCapability.extractName(singleUD)); attribute(VERSION_RANGE_ATTRIBUTE, RequiredCapability.extractRange(singleUD)); } else { @@ -291,27 +252,59 @@ public class MetadataWriter extends XMLWriter implements XMLConstants { } protected void writeRequirement(IRequirement requirement) { - start(REQUIREMENT_ELEMENT); IMatchExpression match = requirement.getMatches(); - if (requirement.getMax() > 0 && RequiredCapability.isSimpleRequirement(match)) { + + // A (namespace, name, version-range) type of requirement + if (requirement.getMax() > 0 && RequiredCapability.isVersionRangeRequirement(match)) { + start(REQUIREMENT_ELEMENT); + attribute(NAMESPACE_ATTRIBUTE, RequiredCapability.extractNamespace(match)); attribute(NAME_ATTRIBUTE, RequiredCapability.extractName(match)); attribute(VERSION_RANGE_ATTRIBUTE, RequiredCapability.extractRange(match)); - attribute(CAPABILITY_OPTIONAL_ATTRIBUTE, requirement.getMin() == 0, false); - attribute(CAPABILITY_MULTIPLE_ATTRIBUTE, requirement.getMax() > 1, false); - } else { + attribute(REQUIRED_CAPABILITY_OPTIONAL_ATTRIBUTE, requirement.getMin() == 0, false); + attribute(REQUIRED_CAPABILITY_MULTIPLE_ATTRIBUTE, requirement.getMax() > 1, false); + } + // A (namespace, attributes-match) type of requirement + else if (RequiredPropertiesMatch.isPropertiesMatchRequirement(match)) { + start(REQUIREMENT_PROPERTIES_ELEMENT); + + attribute(NAMESPACE_ATTRIBUTE, RequiredPropertiesMatch.extractNamespace(match)); + attribute(MATCH_ATTRIBUTE, RequiredPropertiesMatch.extractPropertiesMatch(match)); + + if (requirement.getMin() != 1) { + attribute(MIN_ATTRIBUTE, requirement.getMin()); + } + + if (requirement.getMax() != 1) { + attribute(MAX_ATTRIBUTE, requirement.getMax()); + } + } + // A general match expression type of requirement + else { + start(REQUIREMENT_ELEMENT); + writeMatchExpression(match); - if (requirement.getMin() != 1) + + if (requirement.getMin() != 1) { attribute(MIN_ATTRIBUTE, requirement.getMin()); - if (requirement.getMax() != 1) + } + + if (requirement.getMax() != 1) { attribute(MAX_ATTRIBUTE, requirement.getMax()); + } + } + + attribute(REQUIREMENT_GREED_ATTRIBUTE, requirement.isGreedy(), true); + + if (requirement.getFilter() != null) { + writeTrimmedCdata(REQUIREMENT_FILTER_ELEMENT, requirement.getFilter().getParameters()[0].toString()); } - attribute(CAPABILITY_GREED_ATTRIBUTE, requirement.isGreedy(), true); - if (requirement.getFilter() != null) - writeTrimmedCdata(CAPABILITY_FILTER_ELEMENT, requirement.getFilter().getParameters()[0].toString()); - if (requirement.getDescription() != null) + + if (requirement.getDescription() != null) { writeTrimmedCdata(REQUIREMENT_DESCRIPTION_ELEMENT, requirement.getDescription()); - end(REQUIREMENT_ELEMENT); + } + + end(); } private void writeMatchExpression(IMatchExpression match) { diff --git a/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/io/XMLConstants.java b/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/io/XMLConstants.java index 44db99925..621356e4a 100644 --- a/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/io/XMLConstants.java +++ b/bundles/org.eclipse.equinox.p2.metadata.repository/src/org/eclipse/equinox/internal/p2/metadata/repository/io/XMLConstants.java @@ -39,9 +39,7 @@ public interface XMLConstants extends org.eclipse.equinox.internal.p2.persistenc public static final String REQUIREMENTS_ELEMENT = "requires"; //$NON-NLS-1$ public static final String HOST_REQUIREMENTS_ELEMENT = "hostRequirements"; //$NON-NLS-1$ public static final String META_REQUIREMENTS_ELEMENT = "metaRequirements"; //$NON-NLS-1$ - public static final String REQUIREMENT_ELEMENT = "required"; //$NON-NLS-1$ public static final String PROVIDED_CAPABILITIES_ELEMENT = "provides"; //$NON-NLS-1$ - public static final String PROVIDED_CAPABILITY_ELEMENT = "provided"; //$NON-NLS-1$ public static final String[] REQUIRED_PROVIDED_CAPABILITY_ATTRIBUTES = new String[] {NAMESPACE_ATTRIBUTE, NAME_ATTRIBUTE, VERSION_ATTRIBUTE}; public static final String TOUCHPOINT_TYPE_ELEMENT = "touchpoint"; //$NON-NLS-1$ public static final String TOUCHPOINT_DATA_ELEMENT = "touchpointData"; //$NON-NLS-1$ @@ -63,26 +61,26 @@ public interface XMLConstants extends org.eclipse.equinox.internal.p2.persistenc public static final String[] OPTIONAL_IU_ATTRIBUTES = new String[] {SINGLETON_ATTRIBUTE}; public static final String GENERATION_ATTRIBUTE = "generation"; //$NON-NLS-1$ + // Constants for the provided capability element + public static final String PROVIDED_CAPABILITY_ELEMENT = "provided"; //$NON-NLS-1$ + // Constants for sub-elements of a required capability element - public static final String CAPABILITY_FILTER_ELEMENT = "filter"; //$NON-NLS-1$ + public static final String REQUIREMENT_ELEMENT = "required"; //$NON-NLS-1$ + public static final String REQUIREMENT_PROPERTIES_ELEMENT = "requiredProperties"; //$NON-NLS-1$ + public static final String REQUIREMENT_FILTER_ELEMENT = "filter"; //$NON-NLS-1$ public static final String REQUIREMENT_DESCRIPTION_ELEMENT = "description"; //$NON-NLS-1$ + public static final String REQUIREMENT_GREED_ATTRIBUTE = "greedy"; //$NON-NLS-1$ - // Constants for attributes of a required capability element - public static final String CAPABILITY_OPTIONAL_ATTRIBUTE = "optional"; //$NON-NLS-1$ - public static final String CAPABILITY_MULTIPLE_ATTRIBUTE = "multiple"; //$NON-NLS-1$ - public static final String CAPABILITY_GREED_ATTRIBUTE = "greedy"; //$NON-NLS-1$ + public static final String REQUIRED_CAPABILITY_OPTIONAL_ATTRIBUTE = "optional"; //$NON-NLS-1$ + public static final String REQUIRED_CAPABILITY_MULTIPLE_ATTRIBUTE = "multiple"; //$NON-NLS-1$ + public static final String[] REQIURED_CAPABILITY_ATTRIBUTES = new String[] {NAMESPACE_ATTRIBUTE, NAME_ATTRIBUTE, VERSION_RANGE_ATTRIBUTE}; + public static final String[] REQUIRED_CAPABILITY_OPTIONAL_ATTRIBUTES = new String[] {REQUIRED_CAPABILITY_OPTIONAL_ATTRIBUTE, REQUIRED_CAPABILITY_MULTIPLE_ATTRIBUTE, REQUIREMENT_GREED_ATTRIBUTE}; - public static final String CAPABILITY_ATTRIBUTES_ELEMENT = "attributes"; //$NON-NLS-1$ - public static final String CAPABILITY_ATTRIBUTE_ELEMENT = "attribute"; //$NON-NLS-1$ - public static final String CAPABILITY_ATTRIBUTE_NAME_ATTRIBUTE = "name"; //$NON-NLS-1$ - public static final String CAPABILITY_ATTRIBUTE_TYPE_ATTRIBUTE = "type"; //$NON-NLS-1$ - public static final String CAPABILITY_ATTRIBUTE_VALUE_ATTRIBUTE = "value"; //$NON-NLS-1$ - public static final String[] CAPABILITY_ATTRIBUTE_REQUIRED_ATTRIBUTES = new String[] {CAPABILITY_ATTRIBUTE_NAME_ATTRIBUTE, CAPABILITY_ATTRIBUTE_VALUE_ATTRIBUTE, CAPABILITY_ATTRIBUTE_TYPE_ATTRIBUTE}; + public static final String[] REQIURED_PROPERTIES_MATCH_ATTRIBUTES = new String[] {NAMESPACE_ATTRIBUTE, MATCH_ATTRIBUTE}; + public static final String[] REQIURED_PROPERTIES_MATCH_OPTIONAL_ATTRIBUTES = new String[] {MIN_ATTRIBUTE, MAX_ATTRIBUTE, REQUIREMENT_GREED_ATTRIBUTE}; - public static final String[] REQIURED_CAPABILITY_ATTRIBUTES = new String[] {NAMESPACE_ATTRIBUTE, NAME_ATTRIBUTE, VERSION_RANGE_ATTRIBUTE}; - public static final String[] REQIUREMENT_ATTRIBUTES = new String[] {MATCH_ATTRIBUTE}; - public static final String[] OPTIONAL_CAPABILITY_ATTRIBUTES = new String[] {CAPABILITY_OPTIONAL_ATTRIBUTE, CAPABILITY_MULTIPLE_ATTRIBUTE, CAPABILITY_GREED_ATTRIBUTE}; - public static final String[] OPTIONAL_REQUIREMENT_ATTRIBUTES = new String[] {MATCH_PARAMETERS_ATTRIBUTE, MIN_ATTRIBUTE, MAX_ATTRIBUTE, CAPABILITY_GREED_ATTRIBUTE}; + public static final String[] REQUIRED_IU_MATCH_ATTRIBUTES = new String[] {MATCH_ATTRIBUTE}; + public static final String[] REQUIRED_IU_MATCH_OPTIONAL_ATTRIBUTES = new String[] {MATCH_PARAMETERS_ATTRIBUTE, MIN_ATTRIBUTE, MAX_ATTRIBUTE, REQUIREMENT_GREED_ATTRIBUTE}; // Constants for attributes of an artifact key element public static final String ARTIFACT_KEY_CLASSIFIER_ATTRIBUTE = "classifier"; //$NON-NLS-1$ 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 13eb67642..31297abf8 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 @@ -9,10 +9,14 @@ * IBM Corporation - initial API and implementation * EclipseSource - ongoing development * SAP - ongoing development + * Todor Boev *******************************************************************************/ package org.eclipse.equinox.internal.p2.metadata; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.eclipse.core.runtime.Assert; @@ -31,44 +35,65 @@ public class ProvidedCapability implements IProvidedCapability, IMemberProvider public static final String MEMBER_NAME = "name"; //$NON-NLS-1$ /** Used for fast access from P2 queries to the {@link #getVersion} method */ public static final String MEMBER_VERSION = "version"; //$NON-NLS-1$ - /** Used for fast access from P2 queries to the {@link #getAttributes} method */ - public static final String MEMBER_ATTRIBUTES = "attributes"; //$NON-NLS-1$ - - // TODO Move this to IProvidedCapability? - // The "version" attribute is part of the public contract of getVersion() and getAttributes() - public static final String ATTRIBUTE_VERSION = "version"; //$NON-NLS-1$ + /** Used for fast access from P2 queries to the {@link #getProperties} method */ + public static final String MEMBER_PROPERTIES = "properties"; //$NON-NLS-1$ private final String namespace; - private final Map attributes; + private final Map properties; - public ProvidedCapability(String namespace, Map attrs) { + public ProvidedCapability(String namespace, Map props) { Assert.isNotNull(namespace, NLS.bind(Messages.provided_capability_namespace_not_defined, null)); this.namespace = namespace; - Assert.isNotNull(attrs); - Assert.isTrue(!attrs.isEmpty()); + Assert.isNotNull(props); + Assert.isTrue(!props.isEmpty()); + + assertValidPropertyTypes(props); - this.attributes = new HashMap<>(attrs); + Map resolvedProps = new HashMap<>(props); // Verify the name - Assert.isTrue(attributes.containsKey(namespace) && (attributes.get(namespace) instanceof String), NLS.bind(Messages.provided_capability_name_not_defined, namespace)); + Assert.isTrue(resolvedProps.containsKey(namespace) && (resolvedProps.get(namespace) instanceof String), NLS.bind(Messages.provided_capability_name_not_defined, namespace)); // Verify the version - Object version = attributes.get(ATTRIBUTE_VERSION); + Object version = resolvedProps.get(PROPERTY_VERSION); if (version != null) { - Assert.isTrue(attributes.get(ATTRIBUTE_VERSION) instanceof Version); + Assert.isTrue(props.get(PROPERTY_VERSION) instanceof Version); } else { - attributes.put(ATTRIBUTE_VERSION, Version.emptyVersion); + resolvedProps.put(PROPERTY_VERSION, Version.emptyVersion); } + + this.properties = Collections.unmodifiableMap(props); } public ProvidedCapability(String namespace, String name, Version version) { Assert.isNotNull(namespace, NLS.bind(Messages.provided_capability_namespace_not_defined, null)); Assert.isNotNull(name, NLS.bind(Messages.provided_capability_name_not_defined, namespace)); this.namespace = namespace; - this.attributes = new HashMap<>(); - attributes.put(namespace, name); - attributes.put(ATTRIBUTE_VERSION, version == null ? Version.emptyVersion : version); + this.properties = new HashMap<>(); + properties.put(namespace, name); + properties.put(PROPERTY_VERSION, version == null ? Version.emptyVersion : version); + } + + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + str.append(namespace); + + for (Entry attr : properties.entrySet()) { + String key = attr.getKey(); + Object val = attr.getValue(); + String type = val.getClass().getSimpleName(); + + str.append("; ").append(key).append(":").append(type).append("=").append(val); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + return str.toString(); + } + + @Override + public int hashCode() { + return namespace.hashCode() * properties.hashCode(); } @Override @@ -87,7 +112,7 @@ public class ProvidedCapability implements IProvidedCapability, IMemberProvider return false; } - if (!(attributes.equals(otherCapability.getAttributes()))) { + if (!(properties.equals(otherCapability.getProperties()))) { return false; } @@ -101,38 +126,17 @@ public class ProvidedCapability implements IProvidedCapability, IMemberProvider @Override public String getName() { - return (String) attributes.get(namespace); + return (String) properties.get(namespace); } @Override public Version getVersion() { - return (Version) attributes.get(ATTRIBUTE_VERSION); + return (Version) properties.get(PROPERTY_VERSION); } @Override - public Map getAttributes() { - return attributes; - } - - @Override - public int hashCode() { - return namespace.hashCode() * attributes.hashCode(); - } - - @Override - public String toString() { - StringBuilder str = new StringBuilder(); - str.append(namespace); - - for (Entry attr : attributes.entrySet()) { - String key = attr.getKey(); - Object val = attr.getValue(); - String type = val.getClass().getSimpleName(); - - str.append("; ").append(key).append(":").append(type).append("=").append(val); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - } - - return str.toString(); + public Map getProperties() { + return properties; } @Override @@ -141,13 +145,36 @@ public class ProvidedCapability implements IProvidedCapability, IMemberProvider case MEMBER_NAMESPACE : return namespace; case MEMBER_NAME : - return attributes.get(namespace); + return properties.get(namespace); case MEMBER_VERSION : - return attributes.get(ATTRIBUTE_VERSION); - case MEMBER_ATTRIBUTES : - return attributes; + return properties.get(PROPERTY_VERSION); + case MEMBER_PROPERTIES : + return properties; default : - throw new IllegalArgumentException("No such member: " + memberName); //$NON-NLS-1$ + throw new IllegalArgumentException(String.format("No such member: %s", memberName)); //$NON-NLS-1$ + } + } + + private void assertValidPropertyTypes(Map props) { + props.forEach(this::assertValidValueType); + } + + private void assertValidValueType(String key, Object prop) { + if (prop instanceof List) { + int idx = 0; + for (Object scalar : (List) prop) { + assertValidScalarType(String.format("%s[%s]", key, idx++), scalar); //$NON-NLS-1$ + } + } else { + assertValidScalarType(key, prop); } } + + private void assertValidScalarType(String key, Object scalar) { + Arrays.asList(Version.class, String.class, Long.class, Integer.class, Short.class, Byte.class, Double.class, Float.class, Boolean.class, Character.class) + .stream() + .filter(t -> t.isAssignableFrom(scalar.getClass())) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(String.format("Invalid type %s of property %s", scalar.getClass(), key))); //$NON-NLS-1$ + } } 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 cd8f8dac8..c08af575d 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 @@ -39,27 +39,14 @@ import org.eclipse.equinox.p2.metadata.expression.IMatchExpression; * @see IInstallableUnit#NAMESPACE_IU_ID */ public class RequiredCapability extends Requirement implements IRequiredCapability { - private static final IExpression simpleMatchExpression; - - static { - IExpressionFactory factory = ExpressionUtil.getFactory(); - - IExpression xVar = factory.variable("cap"); //$NON-NLS-1$ - - IExpression name = factory.member(xVar, MEMBER_NAME); - IExpression nameEqual = factory.equals(name, factory.indexedParameter(0)); - - IExpression namespace = factory.member(xVar, MEMBER_NAMESPACE); - IExpression namespaceEqual = factory.equals(namespace, factory.indexedParameter(1)); - - IExpression version = factory.member(xVar, MEMBER_VERSION); - IExpression versionInRange = factory.matches(version, factory.indexedParameter(2)); - - IExpression pvMember = factory.member(factory.thisVariable(), MEMBER_PROVIDED_CAPABILITIES); - - // Place nameEqual first to eliminate quickly most non-matching candidates - simpleMatchExpression = factory.exists(pvMember, factory.lambda(xVar, factory.and(nameEqual, namespaceEqual, versionInRange))); - } + /** + * Argument $0 must evaluate to a String + * Argument $1 must evaluate to a String + * Argument $2 must evaluate to a {@link VersionRange} + */ + private static final IExpression VERSION_RANGE_MATCH = ExpressionUtil.parse( + String.format("%s.exists(cap | cap.%s == $0 && cap.%s == $1 && cap.%s ~= $2)", //$NON-NLS-1$ + MEMBER_PROVIDED_CAPABILITIES, MEMBER_NAME, MEMBER_NAMESPACE, MEMBER_VERSION)); /** * TODO Remove. This is a private impl class. Users must call the analogous MetadataFactory.createRequirement() @@ -104,17 +91,11 @@ public class RequiredCapability extends Requirement implements IRequiredCapabili public String toString() { StringBuilder result = new StringBuilder(); - // Namespace result.append(getNamespace()); - result.append(' '); - - // Name + result.append("; "); //$NON-NLS-1$ result.append(getName()); - result.append(' '); - - // Version range - VersionRange range = getRange(); - result.append(range); + result.append(" "); //$NON-NLS-1$ + result.append(getRange()); return result.toString(); } @@ -124,7 +105,7 @@ public class RequiredCapability extends Requirement implements IRequiredCapabili Assert.isNotNull(name); Object resolvedRange = (range != null) ? range : VersionRange.emptyRange; IExpressionFactory factory = ExpressionUtil.getFactory(); - return factory.matchExpression(simpleMatchExpression, name, namespace, resolvedRange); + return factory.matchExpression(VERSION_RANGE_MATCH, name, namespace, resolvedRange); } public static String extractNamespace(IMatchExpression matchExpression) { @@ -144,7 +125,7 @@ public class RequiredCapability extends Requirement implements IRequiredCapabili } public static boolean isVersionStrict(IMatchExpression matchExpression) { - if (!isSimpleRequirement(matchExpression)) { + if (!isVersionRangeRequirement(matchExpression)) { return false; } @@ -153,12 +134,12 @@ public class RequiredCapability extends Requirement implements IRequiredCapabili return range.getMinimum().equals(range.getMaximum()); } - public static boolean isSimpleRequirement(IMatchExpression matchExpression) { - return simpleMatchExpression.equals(ExpressionUtil.getOperand(matchExpression)); + public static boolean isVersionRangeRequirement(IMatchExpression matchExpression) { + return VERSION_RANGE_MATCH.equals(ExpressionUtil.getOperand(matchExpression)); } private static void assertValid(IMatchExpression matchExpression) { - if (!isSimpleRequirement(matchExpression)) { + if (!isVersionRangeRequirement(matchExpression)) { throw new IllegalArgumentException(); } } diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequiredPropertiesMatch.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequiredPropertiesMatch.java new file mode 100644 index 000000000..a108fadc0 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/RequiredPropertiesMatch.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2007, 2017 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: + * Todor Boev + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.metadata; + +import static org.eclipse.equinox.internal.p2.metadata.InstallableUnit.MEMBER_PROVIDED_CAPABILITIES; +import static org.eclipse.equinox.internal.p2.metadata.ProvidedCapability.MEMBER_NAMESPACE; +import static org.eclipse.equinox.internal.p2.metadata.ProvidedCapability.MEMBER_PROPERTIES; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.metadata.IProvidedCapability; +import org.eclipse.equinox.p2.metadata.IRequirement; +import org.eclipse.equinox.p2.metadata.expression.ExpressionUtil; +import org.eclipse.equinox.p2.metadata.expression.IExpression; +import org.eclipse.equinox.p2.metadata.expression.IExpressionFactory; +import org.eclipse.equinox.p2.metadata.expression.IFilterExpression; +import org.eclipse.equinox.p2.metadata.expression.IMatchExpression; + +/** + * A required capability match represents some external constraint on an {@link IInstallableUnit}. + *

+ * This is a flavor of the general {@link IRequirement} that searches for + * a capability that has {@link IProvidedCapability#getProperties() properties} that match a given expression. + * I.e. this is much more limited that an arbitrary match expression executed over all metadata of the IU. + */ +public class RequiredPropertiesMatch extends Requirement { + /** + * Argument $0 must evaluate to a String + * Argument $2 must evaluate to an expression compatible with the match operator "~=" + */ + private static final IExpression PROPERTIES_MATCH = ExpressionUtil.parse( + String.format("%s.exists(cap | cap.%s == $0 && cap.%s ~= $1)", //$NON-NLS-1$ + MEMBER_PROVIDED_CAPABILITIES, MEMBER_NAMESPACE, MEMBER_PROPERTIES)); + + public RequiredPropertiesMatch(String namespace, IFilterExpression attrFilter, IMatchExpression envFilter, int min, int max, boolean greedy, String description) { + super(createMatchExpressionFromFilter(namespace, attrFilter), envFilter, min, max, greedy, description); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + + result.append(extractNamespace(getMatches())); + result.append("; "); //$NON-NLS-1$ + result.append(extractPropertiesMatch(getMatches())); + + return result.toString(); + } + + public static IMatchExpression createMatchExpressionFromFilter(String namespace, IFilterExpression attrFilter) { + Assert.isNotNull(namespace); + Assert.isNotNull(attrFilter); + IExpressionFactory factory = ExpressionUtil.getFactory(); + return factory.matchExpression(PROPERTIES_MATCH, namespace, attrFilter); + } + + public static String extractNamespace(IMatchExpression matchExpression) { + assertValid(matchExpression); + return (String) matchExpression.getParameters()[0]; + } + + public static IFilterExpression extractPropertiesMatch(IMatchExpression matchExpression) { + assertValid(matchExpression); + return (IFilterExpression) matchExpression.getParameters()[1]; + } + + public static boolean isPropertiesMatchRequirement(IMatchExpression matchExpression) { + return PROPERTIES_MATCH.equals(ExpressionUtil.getOperand(matchExpression)); + } + + private static void assertValid(IMatchExpression matchExpression) { + if (!isPropertiesMatchRequirement(matchExpression)) { + throw new IllegalArgumentException(); + } + } +} diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/Requirement.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/Requirement.java index 036721c5f..5fe660e82 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/Requirement.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/Requirement.java @@ -11,7 +11,11 @@ *******************************************************************************/ package org.eclipse.equinox.internal.p2.metadata; -import org.eclipse.equinox.p2.metadata.*; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.metadata.IProvidedCapability; +import org.eclipse.equinox.p2.metadata.IRequirement; +import org.eclipse.equinox.p2.metadata.MetadataFactory; +import org.eclipse.equinox.p2.metadata.VersionRange; import org.eclipse.equinox.p2.metadata.expression.IMatchExpression; import org.eclipse.equinox.p2.metadata.expression.IMemberProvider; @@ -66,10 +70,14 @@ public class Requirement implements IRequirement, IMemberProvider { // Parameters Object[] params = matchExpression.getParameters(); if (params.length > 0) { - result.append(' '); + result.append(" ("); //$NON-NLS-1$ for (int i = 0; i < params.length; i++) { - result.append(params[i]).append(' '); + if (i > 0) { + result.append(", "); //$NON-NLS-1$ + } + result.append(params[i]); } + result.append(')'); } return result.toString(); 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 index 6cb8008f7..c6d31687d 100644 --- 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 @@ -229,8 +229,6 @@ public abstract class CoercingComparator { 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); @@ -333,7 +331,7 @@ public abstract class CoercingComparator { * @return The coercing comparator */ @SuppressWarnings("unchecked") - public static CoercingComparator getComparator(V value, Object v2) { + public static CoercingComparator getComparator(V value, Object v2) { Class vClass = (Class) value.getClass(); CoercingComparator[] carr = coercers; int idx = carr.length; diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/index/CapabilityIndex.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/index/CapabilityIndex.java index d7b2b3cac..8d89b2d13 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/index/CapabilityIndex.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/index/CapabilityIndex.java @@ -110,7 +110,7 @@ public class CapabilityIndex extends Index { // index usage query // IMatchExpression rm = ((IRequirement) rhsObj).getMatches(); - return RequiredCapability.isSimpleRequirement(rm) ? concatenateUnique(queriedKeys, rm.getParameters()[0]) : getRequirementIDs(rm.createContext(), ((Unary) rm).operand, queriedKeys); + return RequiredCapability.isVersionRangeRequirement(rm) ? concatenateUnique(queriedKeys, rm.getParameters()[0]) : getRequirementIDs(rm.createContext(), ((Unary) rm).operand, queriedKeys); } @Override @@ -185,7 +185,7 @@ public class CapabilityIndex extends Index { // index usage query // IMatchExpression rm = ((IRequirement) rhsObj).getMatches(); - queriedKeys = RequiredCapability.isSimpleRequirement(rm) ? concatenateUnique(queriedKeys, rm.getParameters()[0]) : getRequirementIDs(rm.createContext(), ((Unary) rm).operand, queriedKeys); + queriedKeys = RequiredCapability.isVersionRangeRequirement(rm) ? concatenateUnique(queriedKeys, rm.getParameters()[0]) : getRequirementIDs(rm.createContext(), ((Unary) rm).operand, queriedKeys); break; default : 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 aa3b72b2e..ac979c288 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 @@ -60,7 +60,7 @@ premature_end_of_format=Premature end of format premature_end_of_format_expected_0=Premature end of format, "{0}" expected premature_EOS_0=Premature end of string in "{0}" provided_capability_name_not_defined=the name of provided capability "{0}" is not defined -provided_capability_namespace_not_defined=the namespace of the provided capabilty is not defined +provided_capability_namespace_not_defined=the namespace of the provided capability is not defined range_defined_more_then_once=Range defined more then once range_max_cannot_be_less_then_range_min=The range maximum must not be less then its minimum range_max_cannot_be_zero=The range maximum cannot be zero diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IProvidedCapability.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IProvidedCapability.java index de61165eb..7cf08a170 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IProvidedCapability.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/IProvidedCapability.java @@ -6,11 +6,13 @@ * * Contributors: * EclipseSource - initial API and implementation - * IBM - ongoing development +* IBM - ongoing development +* Todor Boev ******************************************************************************/ package org.eclipse.equinox.p2.metadata; import java.util.Map; +import org.eclipse.equinox.p2.metadata.expression.IFilterExpression; /** * Describes a capability that is exposed by an installable unit. These capabilities @@ -28,46 +30,57 @@ import java.util.Map; * @see IRequirement */ public interface IProvidedCapability { + /** + * The name of the property under which the capability version is stored. + * + * Can be used with {@link #getProperties()}. The same value can be obtained with {@link #getVersion()} + * + * @since 2.4 + */ + String PROPERTY_VERSION = "version"; //$NON-NLS-1$ + /** * * @return String the namespace of this capability. * @noreference This method is not intended to be referenced by clients. */ - public String getNamespace(); + String getNamespace(); /** * - * @return String the attribute stored under a key equal to {@link #getNamespace()} attribute of this capability. + * @return String the attribute stored under a key equal to the {@link #getNamespace()} attribute of this capability. * @noreference This method is not intended to be referenced by clients. */ - public String getName(); + String getName(); /** * - * @return String the special version attribute of this capability. + * @return String the special {@link #PROPERTY_VERSION} attribute of this capability. * @noreference This method is not intended to be referenced by clients. */ - public Version getVersion(); + Version getVersion(); /** + * A full description of this capability including the name and the version. + *

+ * Such a description can be used to match this capability with an {@link IFilterExpression LDAP filter} for example. * - * @return A full description of this capability + * @return An unmodifiable map * @noreference This method is not intended to be referenced by clients. * @since 2.4 */ - public Map getAttributes(); + Map getProperties(); /** * Returns whether this provided capability is equal to the given object. * * This method returns true if: *

    - *
  • Both this object and the given object are of type IProvidedCapability - *
  • The result of getNamespace() on both objects are equal - *
  • The result of getAttributes() on both objects are equal + *
  • Both this object and the given object are of type IProvidedCapability
  • + *
  • The result of {@link #getNamespace()} on both objects are equal
  • + *
  • The result of {@link #getProperties()} on both objects are equal
  • *
*/ @Override - public boolean equals(Object other); - + boolean equals(Object other); } \ No newline at end of file diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/MetadataFactory.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/MetadataFactory.java index e3f89b18d..6c9d91a77 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/MetadataFactory.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/p2/metadata/MetadataFactory.java @@ -30,6 +30,7 @@ import org.eclipse.equinox.internal.p2.metadata.InstallableUnitPatch; import org.eclipse.equinox.internal.p2.metadata.License; import org.eclipse.equinox.internal.p2.metadata.ProvidedCapability; import org.eclipse.equinox.internal.p2.metadata.RequiredCapability; +import org.eclipse.equinox.internal.p2.metadata.RequiredPropertiesMatch; import org.eclipse.equinox.internal.p2.metadata.Requirement; import org.eclipse.equinox.internal.p2.metadata.RequirementChange; import org.eclipse.equinox.internal.p2.metadata.ResolvedInstallableUnit; @@ -37,6 +38,8 @@ import org.eclipse.equinox.internal.p2.metadata.TouchpointData; import org.eclipse.equinox.internal.p2.metadata.TouchpointInstruction; import org.eclipse.equinox.internal.p2.metadata.TouchpointType; import org.eclipse.equinox.internal.p2.metadata.UpdateDescriptor; +import org.eclipse.equinox.p2.metadata.expression.ExpressionUtil; +import org.eclipse.equinox.p2.metadata.expression.IFilterExpression; import org.eclipse.equinox.p2.metadata.expression.IMatchExpression; /** @@ -461,11 +464,29 @@ public final class MetadataFactory { * Returns a {@link IProvidedCapability} with the given values. * * @param namespace The capability namespace - * @param attributes The description of the capability + * @param properties The description of the capability * @since 2.4 */ - public static IProvidedCapability createProvidedCapability(String namespace, Map attributes) { - return new ProvidedCapability(namespace, attributes); + public static IProvidedCapability createProvidedCapability(String namespace, Map properties) { + return new ProvidedCapability(namespace, properties); + } + + /** + * Create and return a new requirement ({@link IRequirement}) with the specified values. + * + * @param namespace the namespace for the requirement. Must not be null. + * @param name the name for the requirement. Must not be null. + * @param range the version range. A value of null is equivalent to {@link VersionRange#emptyRange} and matches all versions. + * @param filter The filter used to evaluate whether this capability is applicable in the + * current environment, or null to indicate this capability is always applicable + * @param optional true if this requirement is optional, and false otherwise. + * @param multiple true if this requirement can be satisfied by multiple provided capabilities, or false + * if it requires exactly one match + * @param greedy true if the requirement should be considered greedy and false otherwise + * @return the requirement + */ + public static IRequirement createRequirement(String namespace, String name, VersionRange range, String filter, boolean optional, boolean multiple, boolean greedy) { + return new RequiredCapability(namespace, name, range, InstallableUnit.parseFilter(filter), optional ? 0 : 1, multiple ? Integer.MAX_VALUE : 1, greedy, null); } /** @@ -507,87 +528,81 @@ public final class MetadataFactory { /** * Create and return a new requirement ({@link IRequirement}) with the specified values. * - * @param requirement the match expression + * @param namespace the namespace for the requirement. Must not be null. + * @param name the name for the requirement. Must not be null. + * @param range the version range. A value of null is equivalent to {@link VersionRange#emptyRange} and matches all versions. * @param filter The filter used to evaluate whether this capability is applicable in the * current environment, or null to indicate this capability is always applicable * @param minCard minimum cardinality * @param maxCard maximum cardinality * @param greedy true if the requirement should be considered greedy and false otherwise + * @param description a String description of the requirement, or null * @return the requirement */ - public static IRequirement createRequirement(IMatchExpression requirement, IMatchExpression filter, int minCard, int maxCard, boolean greedy) { - // IRequiredCapability is simply a requirement with a match expression derived from a (namespace, name, version) tripet. - // However the xml format also requires that maxCard > 1 or it is serialized in the generic format. - // When parsing back from xml try to convert to an IRequiredCapability to retain the representation prior to serialization - if (RequiredCapability.isSimpleRequirement(requirement)) { - String namespace = RequiredCapability.extractNamespace(requirement); - String name = RequiredCapability.extractName(requirement); - VersionRange range = RequiredCapability.extractRange(requirement); - return new RequiredCapability(namespace, name, range, filter, minCard, maxCard, greedy, null); - } + public static IRequirement createRequirement(String namespace, String name, VersionRange range, IMatchExpression filter, int minCard, int maxCard, boolean greedy, String description) { + return new RequiredCapability(namespace, name, range, filter, minCard, maxCard, greedy, description); + } - return new Requirement(requirement, filter, minCard, maxCard, greedy, null); + /** + * + * @param namespace + * @param propsFilter filter applied on {@link IProvidedCapability#getProperties()} of every {@link IInstallableUnit#getProvidedCapabilities()} + * @param envFilter matcher over {@link IInstallableUnit#getProperties()} + * @param minCard + * @param maxCard + * @param greedy + * @return the requirement + * @since 2.4 + */ + public static IRequirement createRequirement(String namespace, String propsFilter, IMatchExpression envFilter, int minCard, int maxCard, boolean greedy) { + IFilterExpression attrFilterExpr = ExpressionUtil.parseLDAP(propsFilter); + return new RequiredPropertiesMatch(namespace, attrFilterExpr, envFilter, minCard, maxCard, greedy, null); } /** - * Create and return a new requirement ({@link IRequirement}) with the specified values. * - * @param namespace the namespace for the requirement. Must not be null. - * @param name the name for the requirement. Must not be null. - * @param range the version range. A value of null is equivalent to {@link VersionRange#emptyRange} and matches all versions. - * @param filter The filter used to evaluate whether this capability is applicable in the - * current environment, or null to indicate this capability is always applicable - * @param optional true if this requirement is optional, and false otherwise. - * @param multiple true if this requirement can be satisfied by multiple provided capabilities, or false - * if it requires exactly one match - * @param greedy true if the requirement should be considered greedy and false otherwise + * @param namespace + * @param propsFilter + * @param envFilter + * @param minCard + * @param maxCard + * @param greedy + * @param description * @return the requirement + * @since 2.4 */ - public static IRequirement createRequirement(String namespace, String name, VersionRange range, String filter, boolean optional, boolean multiple, boolean greedy) { - return new RequiredCapability(namespace, name, range, InstallableUnit.parseFilter(filter), optional ? 0 : 1, multiple ? Integer.MAX_VALUE : 1, greedy, null); + public static IRequirement createRequirement(String namespace, IFilterExpression propsFilter, IMatchExpression envFilter, int minCard, int maxCard, boolean greedy, String description) { + return new RequiredPropertiesMatch(namespace, propsFilter, envFilter, minCard, maxCard, greedy, null); } /** * Create and return a new requirement ({@link IRequirement}) with the specified values. * - * @param namespace the namespace for the requirement. Must not be null. - * @param name the name for the requirement. Must not be null. - * @param range the version range. A value of null is equivalent to {@link VersionRange#emptyRange} and matches all versions. - * @param filter The filter used to evaluate whether this capability is applicable in the + * @param requirement the match expression + * @param envFilter The filter used to evaluate whether this capability is applicable in the * current environment, or null to indicate this capability is always applicable * @param minCard minimum cardinality * @param maxCard maximum cardinality * @param greedy true if the requirement should be considered greedy and false otherwise - * @param description a String description of the requirement, or null * @return the requirement */ - public static IRequirement createRequirement(String namespace, String name, VersionRange range, IMatchExpression filter, int minCard, int maxCard, boolean greedy, String description) { - return new RequiredCapability(namespace, name, range, filter, minCard, maxCard, greedy, description); + public static IRequirement createRequirement(IMatchExpression requirement, IMatchExpression envFilter, int minCard, int maxCard, boolean greedy) { + return createRequirementInternal(requirement, envFilter, minCard, maxCard, greedy, null); } /** * Create and return a new requirement ({@link IRequirement}) with the specified values. * * @param requirement the match expression - * @param filter the filter, or null + * @param envFilter the filter, or null * @param minCard minimum cardinality * @param maxCard maximum cardinality * @param greedy true if the requirement should be considered greedy and false otherwise * @param description a String description of the requirement, or null * @return the requirement */ - public static IRequirement createRequirement(IMatchExpression requirement, IMatchExpression filter, int minCard, int maxCard, boolean greedy, String description) { - // IRequiredCapability is simply a requirement with a match expression derived from a (namespace, name, version) tripet. - // However the xml format also requires that maxCard > 1 or it is serialized in the generic format. - // When parsing back from xml try to convert to an IRequiredCapability to retain the representation prior to serialization - if (RequiredCapability.isSimpleRequirement(requirement)) { - String namespace = RequiredCapability.extractNamespace(requirement); - String name = RequiredCapability.extractName(requirement); - VersionRange range = RequiredCapability.extractRange(requirement); - return new RequiredCapability(namespace, name, range, filter, minCard, maxCard, greedy, description); - } - - return new Requirement(requirement, filter, minCard, maxCard, greedy, description); + public static IRequirement createRequirement(IMatchExpression requirement, IMatchExpression envFilter, int minCard, int maxCard, boolean greedy, String description) { + return createRequirementInternal(requirement, envFilter, minCard, maxCard, greedy, description); } /** @@ -737,6 +752,14 @@ public final class MetadataFactory { } } + /** + * + * @param descriptors + * @param severity + * @param description + * @param location + * @return A new update descriptor + */ public static IUpdateDescriptor createUpdateDescriptor(Collection> descriptors, int severity, String description, URI location) { return new UpdateDescriptor(descriptors, severity, description, location); } @@ -770,6 +793,26 @@ public final class MetadataFactory { return createUpdateDescriptor(descriptors, severity, description, location); } + private static IRequirement createRequirementInternal(IMatchExpression requirement, IMatchExpression envFilter, int minCard, int maxCard, boolean greedy, String description) { + // IRequiredCapability is simply a requirement with a match expression derived from a (namespace, name, version) tripet. + // However the xml format also requires that maxCard > 1 or it is serialized in the generic format. + // When parsing back from xml try to convert to an IRequiredCapability to retain the representation prior to serialization + if (RequiredCapability.isVersionRangeRequirement(requirement)) { + String namespace = RequiredCapability.extractNamespace(requirement); + String name = RequiredCapability.extractName(requirement); + VersionRange range = RequiredCapability.extractRange(requirement); + return new RequiredCapability(namespace, name, range, envFilter, minCard, maxCard, greedy, description); + } + + if (RequiredPropertiesMatch.isPropertiesMatchRequirement(requirement)) { + String namespace = RequiredPropertiesMatch.extractNamespace(requirement); + IFilterExpression attrMatch = RequiredPropertiesMatch.extractPropertiesMatch(requirement); + return new RequiredPropertiesMatch(namespace, attrMatch, envFilter, minCard, maxCard, greedy, description); + } + + return new Requirement(requirement, envFilter, minCard, maxCard, greedy, description); + } + private static ITouchpointType getCachedTouchpointType(String id, Version version) { for (int i = 0; i < typeCache.length; i++) { if (typeCache[i] != null && typeCache[i].getId().equals(id) && typeCache[i].getVersion().equals(version)) diff --git a/bundles/org.eclipse.equinox.p2.publisher.eclipse/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.publisher.eclipse/META-INF/MANIFEST.MF index 5ff218a9e..36dbe0f51 100644 --- a/bundles/org.eclipse.equinox.p2.publisher.eclipse/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.equinox.p2.publisher.eclipse/META-INF/MANIFEST.MF @@ -39,6 +39,8 @@ Import-Package: org.eclipse.equinox.app;version="[1.0.0,2.0.0)", org.eclipse.osgi.service.resolver;version="1.5.0", org.eclipse.osgi.util;version="1.1.0", org.osgi.framework;version="1.3.0", + org.osgi.framework.wiring;version="1.2.0", + org.osgi.resource;version="1.0.0", org.osgi.service.application;version="1.1.0", org.osgi.service.packageadmin;version="1.2.0" Export-Package: org.eclipse.equinox.internal.p2.publisher.compatibility;x-internal:=true, diff --git a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/BundlesAction.java b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/BundlesAction.java index b3411b045..c2ec094be 100644 --- a/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/BundlesAction.java +++ b/bundles/org.eclipse.equinox.p2.publisher.eclipse/src/org/eclipse/equinox/p2/publisher/eclipse/BundlesAction.java @@ -12,6 +12,9 @@ ******************************************************************************/ package org.eclipse.equinox.p2.publisher.eclipse; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; + import java.io.*; import java.util.*; import java.util.Map.Entry; @@ -28,7 +31,7 @@ import org.eclipse.equinox.p2.metadata.*; import org.eclipse.equinox.p2.metadata.MetadataFactory.InstallableUnitDescription; import org.eclipse.equinox.p2.metadata.MetadataFactory.InstallableUnitFragmentDescription; import org.eclipse.equinox.p2.metadata.VersionRange; -import org.eclipse.equinox.p2.metadata.expression.*; +import org.eclipse.equinox.p2.metadata.expression.IMatchExpression; import org.eclipse.equinox.p2.publisher.*; import org.eclipse.equinox.p2.publisher.actions.*; import org.eclipse.equinox.p2.query.IQueryResult; @@ -43,6 +46,8 @@ import org.eclipse.osgi.util.NLS; import org.eclipse.pde.internal.publishing.Activator; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; +import org.osgi.framework.wiring.BundleRequirement; +import org.osgi.resource.Namespace; /** * Publish IUs for all of the bundles in a given set of locations or described by a set of @@ -78,7 +83,6 @@ public class BundlesAction extends AbstractPublisherAction { public static final String OSGI_BUNDLE_CLASSIFIER = "osgi.bundle"; //$NON-NLS-1$ public static final String CAPABILITY_NS_OSGI_BUNDLE = "osgi.bundle"; //$NON-NLS-1$ public static final String CAPABILITY_NS_OSGI_FRAGMENT = "osgi.fragment"; //$NON-NLS-1$ - public static final String CAPABILITY_ATTR_VERSION = "version"; //$NON-NLS-1$ public static final IProvidedCapability BUNDLE_CAPABILITY = MetadataFactory.createProvidedCapability(PublisherHelper.NAMESPACE_ECLIPSE_TYPE, TYPE_ECLIPSE_BUNDLE, Version.createOSGi(1, 0, 0)); public static final IProvidedCapability SOURCE_BUNDLE_CAPABILITY = MetadataFactory.createProvidedCapability(PublisherHelper.NAMESPACE_ECLIPSE_TYPE, TYPE_ECLIPSE_SOURCE, Version.createOSGi(1, 0, 0)); @@ -194,7 +198,7 @@ public class BundlesAction extends AbstractPublisherAction { // Process generic requirements ManifestElement[] rawRequireCapHeader = parseManifestHeader(Constants.REQUIRE_CAPABILITY, manifest, bd.getLocation()); for (GenericSpecification requiredCap : bd.getGenericRequires()) { - addGenericRequirement(requirements, requiredCap, rawRequireCapHeader); + addRequirement(requirements, requiredCap, rawRequireCapHeader); } iu.setRequirements(requirements.toArray(new IRequirement[requirements.size()])); @@ -223,7 +227,7 @@ public class BundlesAction extends AbstractPublisherAction { int capNo = 0; for (GenericDescription genericCap : bd.getGenericCapabilities()) { - addGenericCapability(providedCapabilities, genericCap, iu, capNo); + addCapability(providedCapabilities, genericCap, iu, capNo); capNo++; } @@ -301,36 +305,45 @@ public class BundlesAction extends AbstractPublisherAction { reqsDeps.add(MetadataFactory.createRequirement(PublisherHelper.CAPABILITY_NS_JAVA_PACKAGE, importSpec.getName(), versionRange, null, optional, false, greedy)); } - // TODO Handle all attributes and directives somehow? Especially the "effective" directive. - protected void addGenericRequirement(List reqsDeps, GenericSpecification requireCapSpec, ManifestElement[] rawRequiresPackageHeader) { - String ns = requireCapSpec.getType(); - String ldap = requireCapSpec.getMatchingFilter(); - String matcher = "providedCapabilities.exists(pc | pc.namespace == '" + ns + "' && pc.attributes ~= filter('" + ldap + "'))"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - - IExpression expr = ExpressionUtil.parse(matcher); - IMatchExpression matchExpr = ExpressionUtil.getFactory().matchExpression(expr); - - // Optional and greedy in order to be backward compatible. - IRequirement requireCap = MetadataFactory.createRequirement(matchExpr, null, 0, 1, true); - - reqsDeps.add(requireCap); - } - protected void addRequireBundleRequirement(List reqsDeps, BundleSpecification requiredBundle, ManifestElement[] rawRequireBundleHeader) { final boolean optional = requiredBundle.isOptional(); final boolean greedy; - if (optional) + if (optional) { greedy = INSTALLATION_GREEDY.equals(getInstallationDirective(requiredBundle.getName(), rawRequireBundleHeader)); - else + } else { greedy = true; + } reqsDeps.add(MetadataFactory.createRequirement(CAPABILITY_NS_OSGI_BUNDLE, requiredBundle.getName(), PublisherHelper.fromOSGiVersionRange(requiredBundle.getVersionRange()), null, optional ? 0 : 1, 1, greedy)); } - protected void addGenericCapability(List caps, GenericDescription provideCapDesc, InstallableUnitDescription iu, int capNo) { + // TODO Handle the "effective:=" directive somehow? + protected void addRequirement(List reqsDeps, GenericSpecification requireCapSpec, ManifestElement[] rawRequireCapabilities) { + BundleRequirement req = requireCapSpec.getRequirement(); + + String namespace = req.getNamespace(); + Map directives = req.getDirectives(); + + String capFilter = directives.get(Namespace.REQUIREMENT_FILTER_DIRECTIVE); + boolean optional = directives.get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE) == Namespace.RESOLUTION_OPTIONAL; + boolean greedy = optional + ? INSTALLATION_GREEDY.equals(directives.get(INSTALLATION_DIRECTIVE)) + : true; + + IRequirement requireCap = MetadataFactory.createRequirement(namespace, capFilter, null, optional ? 0 : 1, 1, greedy); + reqsDeps.add(requireCap); + } + + protected void addCapability(List caps, GenericDescription provideCapDesc, InstallableUnitDescription iu, int capNo) { + // Convert the values to String, Version, List of String or Version + Map capAttrs = provideCapDesc.getDeclaredAttributes() + .entrySet() + .stream() + .collect(toMap(Entry::getKey, e -> convertAttribute(e.getValue()))); + + // Resolve the namespace String capNs = provideCapDesc.getType(); - Map capAttrs = new HashMap<>(provideCapDesc.getDeclaredAttributes()); - // Resolve the p2 name + // Resolve the mandatory p2 name // By convention OSGi capabilities have an attribute named like the capability namespace. // If this is not the case synthesize a unique name (e.g. "osgi.service" has an "objectClass" attribute instead). // TODO If present but not a String log a warning somehow that it is ignored? Or fail the publication? @@ -338,28 +351,32 @@ public class BundlesAction extends AbstractPublisherAction { capNs, (k, v) -> (v instanceof String) ? v : String.format("%s_%s-%s", iu.getId(), iu.getVersion(), capNo)); //$NON-NLS-1$ - // Convert all OSGi versions to P2 versions - for (String key : new HashSet<>(capAttrs.keySet())) { - Object val = capAttrs.get(key); - if (!(val instanceof org.osgi.framework.Version)) { - continue; - } - org.osgi.framework.Version osgiVer = (org.osgi.framework.Version) val; - Version p2Ver = Version.createOSGi(osgiVer.getMajor(), osgiVer.getMinor(), osgiVer.getMicro(), osgiVer.getQualifier()); - capAttrs.put(key, p2Ver); - } - - // Resolve the version + // Resolve the mandatory p2 version // By convention versioned OSGi capabilities have a "version" attribute containing the OSGi Version object // If this is not the case use an empty version (e.g. "osgi.ee" has a list of versions). // TODO If present but not a Version log a warning somehow that it is ignored? Or fail the publication? capAttrs.compute( - CAPABILITY_ATTR_VERSION, + IProvidedCapability.PROPERTY_VERSION, (k, v) -> (v instanceof Version) ? v : Version.emptyVersion); caps.add(MetadataFactory.createProvidedCapability(capNs, capAttrs)); } + private Object convertAttribute(Object attr) { + if (attr instanceof Collection) { + return ((Collection) attr).stream().map(this::convertScalarAttribute).collect(toList()); + } + return convertScalarAttribute(attr); + } + + private Object convertScalarAttribute(Object attr) { + if (attr instanceof org.osgi.framework.Version) { + org.osgi.framework.Version osgiVer = (org.osgi.framework.Version) attr; + return Version.createOSGi(osgiVer.getMajor(), osgiVer.getMinor(), osgiVer.getMicro(), osgiVer.getQualifier()); + } + return attr.toString(); + } + static VersionRange computeUpdateRange(org.osgi.framework.Version base) { VersionRange updateRange = null; if (!base.equals(org.osgi.framework.Version.emptyVersion)) { diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/p2/publisher/AbstractPublisherAction.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/p2/publisher/AbstractPublisherAction.java index 71288480a..33822347f 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/p2/publisher/AbstractPublisherAction.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/p2/publisher/AbstractPublisherAction.java @@ -395,7 +395,7 @@ public abstract class AbstractPublisherAction implements IPublisherAction { } IRequiredCapability requiredCapability = (IRequiredCapability) requirement; - if (!RequiredCapability.isSimpleRequirement(requiredCapability.getMatches())) { + if (!RequiredCapability.isVersionRangeRequirement(requiredCapability.getMatches())) { return null; } diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/persistence/XMLConstants.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/persistence/XMLConstants.java index f1af9f142..de1601d90 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/persistence/XMLConstants.java +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/persistence/XMLConstants.java @@ -11,6 +11,9 @@ *******************************************************************************/ package org.eclipse.equinox.internal.p2.persistence; +import java.util.List; +import org.eclipse.equinox.p2.metadata.Version; + public interface XMLConstants { // Constants used in defining a default processing instruction @@ -29,7 +32,20 @@ public interface XMLConstants { public static final String PROPERTY_ELEMENT = "property"; //$NON-NLS-1$ public static final String PROPERTY_NAME_ATTRIBUTE = "name"; //$NON-NLS-1$ public static final String PROPERTY_VALUE_ATTRIBUTE = "value"; //$NON-NLS-1$ + public static final String PROPERTY_TYPE_ATTRIBUTE = "type"; //$NON-NLS-1$ public static final String[] PROPERTY_ATTRIBUTES = new String[] {PROPERTY_NAME_ATTRIBUTE, PROPERTY_VALUE_ATTRIBUTE}; + public static final String[] PROPERTY_OPTIONAL_ATTRIBUTES = new String[] {PROPERTY_TYPE_ATTRIBUTE}; + public static final String PROPERTY_TYPE_LIST = List.class.getSimpleName(); + public static final String PROPERTY_TYPE_STRING = String.class.getSimpleName(); + public static final String PROPERTY_TYPE_INTEGER = Integer.class.getSimpleName(); + public static final String PROPERTY_TYPE_LONG = Long.class.getSimpleName(); + public static final String PROPERTY_TYPE_FLOAT = Float.class.getSimpleName(); + public static final String PROPERTY_TYPE_DOUBLE = Double.class.getSimpleName(); + public static final String PROPERTY_TYPE_BYTE = Byte.class.getSimpleName(); + public static final String PROPERTY_TYPE_SHORT = Short.class.getSimpleName(); + public static final String PROPERTY_TYPE_CHARACTER = Character.class.getSimpleName(); + public static final String PROPERTY_TYPE_BOOLEAN = Boolean.class.getSimpleName(); + public static final String PROPERTY_TYPE_VERSION = Version.class.getSimpleName(); // Constants for the names of common general attributes public static final String ID_ATTRIBUTE = "id"; //$NON-NLS-1$ diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/persistence/XMLWriter.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/persistence/XMLWriter.java index 6973a2f06..3a532f6ad 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/persistence/XMLWriter.java +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/persistence/XMLWriter.java @@ -10,11 +10,11 @@ *******************************************************************************/ package org.eclipse.equinox.internal.p2.persistence; +import static java.util.stream.Collectors.joining; + import java.io.*; import java.nio.charset.StandardCharsets; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Stack; +import java.util.*; import org.eclipse.equinox.p2.metadata.Version; public class XMLWriter implements XMLConstants { @@ -220,28 +220,82 @@ public class XMLWriter implements XMLConstants { this.pw.flush(); } - public void writeProperties(Map properties) { + public void writeProperties(Map properties) { writeProperties(PROPERTIES_ELEMENT, properties); } - public void writeProperties(String propertiesElement, Map properties) { - if (properties != null && properties.size() > 0) { - start(propertiesElement); - attribute(COLLECTION_SIZE_ATTRIBUTE, properties.size()); - for (Entry entry : properties.entrySet()) { - writeProperty(entry.getKey(), entry.getValue()); - } - end(propertiesElement); + public void writeProperties(String propertiesElement, Map properties) { + if (properties == null || properties.isEmpty()) { + return; } + + start(propertiesElement); + attribute(COLLECTION_SIZE_ATTRIBUTE, properties.size()); + properties.forEach(this::writeProperty); + end(); } - public void writeProperty(String name, String value) { + public void writeProperty(String name, Object value) { + String type; + String valueStr; + + if (Collection.class.isAssignableFrom(value.getClass())) { + Collection coll = (Collection) value; + + type = PROPERTY_TYPE_LIST; + String elType = resolvePropertyType(coll.iterator().next()); + if (elType != null) { + type += String.format("<%s>", elType); //$NON-NLS-1$ + } + + valueStr = coll.stream().map(Object::toString).collect(joining(",")); //$NON-NLS-1$ + } else { + type = resolvePropertyType(value); + valueStr = value.toString(); + } + start(PROPERTY_ELEMENT); attribute(PROPERTY_NAME_ATTRIBUTE, name); - attribute(PROPERTY_VALUE_ATTRIBUTE, value); + attribute(PROPERTY_VALUE_ATTRIBUTE, valueStr); + attributeOptional(PROPERTY_TYPE_ATTRIBUTE, type); end(); } + private String resolvePropertyType(Object value) { + if (value instanceof Integer) { + return PROPERTY_TYPE_INTEGER; + } + if (value instanceof Long) { + return PROPERTY_TYPE_LONG; + } + if (value instanceof Float) { + return PROPERTY_TYPE_FLOAT; + } + if (value instanceof Double) { + return PROPERTY_TYPE_DOUBLE; + } + if (value instanceof Byte) { + return PROPERTY_TYPE_BYTE; + } + if (value instanceof Short) { + return PROPERTY_TYPE_SHORT; + } + if (value instanceof Character) { + return PROPERTY_TYPE_CHARACTER; + } + if (value instanceof Boolean) { + return PROPERTY_TYPE_BOOLEAN; + } + if (value instanceof Version) { + return PROPERTY_TYPE_VERSION; + } + + // Null is read back as String + // NOTE: Using string as default is needed for backward compatibility with properties that are always String like + // the IU properties + return null; + } + protected static String attributeImage(String name, String value) { if (value == null) { return ""; // optional attribute with no value //$NON-NLS-1$ diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/director/AutomatedDirectorTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/director/AutomatedDirectorTest.java index f229b4e66..d726d6a5b 100644 --- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/director/AutomatedDirectorTest.java +++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/director/AutomatedDirectorTest.java @@ -10,14 +10,16 @@ package org.eclipse.equinox.p2.tests.director; import java.util.HashMap; import java.util.Map; -import junit.framework.Test; -import junit.framework.TestSuite; import org.eclipse.core.runtime.IStatus; import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; import org.eclipse.equinox.internal.p2.director.ProfileChangeRequest; import org.eclipse.equinox.internal.provisional.p2.director.IDirector; import org.eclipse.equinox.p2.engine.IProfile; -import org.eclipse.equinox.p2.metadata.*; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.metadata.IProvidedCapability; +import org.eclipse.equinox.p2.metadata.IRequirement; +import org.eclipse.equinox.p2.metadata.MetadataFactory; +import org.eclipse.equinox.p2.metadata.Version; import org.eclipse.equinox.p2.metadata.expression.IMatchExpression; import org.eclipse.equinox.p2.tests.AbstractProvisioningTest; @@ -25,20 +27,6 @@ import org.eclipse.equinox.p2.tests.AbstractProvisioningTest; * Various automated tests of the {@link IDirector} API. */ public class AutomatedDirectorTest extends AbstractProvisioningTest { - //private static Version version = Version.createOSGi(1, 0, 0); - - public static Test suite() { - return new TestSuite(AutomatedDirectorTest.class); - } - - public AutomatedDirectorTest() { - super(""); - } - - public AutomatedDirectorTest(String name) { - super(name); - } - /** * Tests installing an IU that has a filtered dependency on another IU. When * the filter is satisfied, the dependency is active and the required IU should @@ -46,26 +34,39 @@ public class AutomatedDirectorTest extends AbstractProvisioningTest { * and the second IU should not be installed. */ public void testInstallFilteredCapability() { - //The IU that is required + final String envKey = "filterKey"; + final String envVal = "true"; + + // The IU that is required IInstallableUnit requiredIU = createIU("required." + getName()); // The IU to be installed - IMatchExpression filter = createFilter("FilterKey", "true"); - IRequirement capability = MetadataFactory.createRequirement(IInstallableUnit.NAMESPACE_IU_ID, requiredIU.getId(), ANY_VERSION, filter, false, false); - IInstallableUnit toInstallIU = createIU("toInstall." + getName(), new IRequirement[] {capability}); + IMatchExpression requirementFilter = createFilter(envKey, envVal); + IRequirement[] requires = new IRequirement[] { + MetadataFactory.createRequirement(IInstallableUnit.NAMESPACE_IU_ID, requiredIU.getId(), ANY_VERSION, requirementFilter, false, false) + }; + IInstallableUnit toInstallIU = createIU("toInstall." + getName(), requires); + // Metadata repository IInstallableUnit[] allUnits = new IInstallableUnit[] {requiredIU, toInstallIU}; createTestMetdataRepository(allUnits); - IDirector director = createDirector(); - //Install into a profile in which the filter is satisfied + // Install into a profile in which the requirement filter is satisfied Map properties = new HashMap<>(); - properties.put(IProfile.PROP_ENVIRONMENTS, "FilterKey=true"); + properties.put(IProfile.PROP_ENVIRONMENTS, envKey + "=" + envVal); + + // Profile IProfile satisfied = createProfile("Satisfied." + getName(), properties); + + // Profile change request ProfileChangeRequest request = new ProfileChangeRequest(satisfied); request.add(toInstallIU); + + // Provision + IDirector director = createDirector(); IStatus result = director.provision(request, null, null); - assertTrue("1.0", result.isOK()); + + assertTrue(result.isOK()); assertProfileContains("1.1", satisfied, allUnits); } @@ -73,25 +74,41 @@ public class AutomatedDirectorTest extends AbstractProvisioningTest { * Tests installing an IU that has an optional prerequisite that is available. */ public void testInstallOptionalAvailable() { - String capabilityId = "test." + getName(); + final String capabilityNamespace = "test.capability"; + final String capabilityName = "test." + getName(); + final Version capabilityVersion = DEFAULT_VERSION; + //The IU that exports the capability - IInstallableUnit requiredIU = createIU("required." + getName(), new IProvidedCapability[] {MetadataFactory.createProvidedCapability("test.capability", capabilityId, DEFAULT_VERSION)}); + IProvidedCapability[] provides = new IProvidedCapability[] { + MetadataFactory.createProvidedCapability(capabilityNamespace, capabilityName, capabilityVersion) + }; + IInstallableUnit requiredIU = createIU("required." + getName(), provides); //The IU that optionally requires the capability - IRequirement required = MetadataFactory.createRequirement("test.capability", capabilityId, ANY_VERSION, null, /* optional=> */true, /* multiple=> */false, /* greedy=>*/false); - IInstallableUnit toInstallIU = createIU("toInstall." + getName(), new IRequirement[] {required}); + IRequirement[] requires = new IRequirement[] { + MetadataFactory.createRequirement(capabilityNamespace, capabilityName, ANY_VERSION, null, /* optional=> */true, /* multiple=> */false, /* greedy=>*/false) + }; + IInstallableUnit toInstallIU = createIU("toInstall." + getName(), requires); + // Metadata repository IInstallableUnit[] allUnits = new IInstallableUnit[] {toInstallIU, requiredIU}; - IInstallableUnit[] toInstallArray = new IInstallableUnit[] {toInstallIU}; createTestMetdataRepository(allUnits); IProfile profile = createProfile("TestProfile." + getName()); - IDirector director = createDirector(); + + // Change request ProfileChangeRequest request = new ProfileChangeRequest(profile); + IInstallableUnit[] toInstallArray = new IInstallableUnit[] {toInstallIU}; request.addInstallableUnits(toInstallArray); + + // Provision + IDirector director = createDirector(); IStatus result = director.provision(request, null, null); - if (!result.isOK()) + if (!result.isOK()) { LogHelper.log(result); + } + + // The requiredIu is not installed, because the optional requirement is not greedy assertTrue("1.0", result.isOK()); assertProfileContains("1.1", profile, toInstallArray); } @@ -100,21 +117,34 @@ public class AutomatedDirectorTest extends AbstractProvisioningTest { * Tests installing an IU that has an optional prerequisite that is not available. */ public void testInstallOptionalUnavailable() { - String capabilityId = "test." + getName(); - //no IU will be available that exports this capability - IRequirement required = MetadataFactory.createRequirement("test.capability", capabilityId, ANY_VERSION, null, true, false); - IInstallableUnit toInstallIU = createIU("toInstall." + getName(), new IRequirement[] {required}); + final String capabilityNamespace = "test.capability"; + final String capabilityName = "test." + getName(); + + // no IU will be available that exports this capability + IRequirement[] requires = new IRequirement[] { + MetadataFactory.createRequirement(capabilityNamespace, capabilityName, ANY_VERSION, null, /* optional=> */true, /* multiple=> */false) + }; + IInstallableUnit toInstallIU = createIU("toInstall." + getName(), requires); + // Metadata repo IInstallableUnit[] allUnits = new IInstallableUnit[] {toInstallIU}; createTestMetdataRepository(allUnits); + // Profile IProfile profile = createProfile("TestProfile." + getName()); - IDirector director = createDirector(); + + // Change request ProfileChangeRequest request = new ProfileChangeRequest(profile); request.addInstallableUnits(allUnits); + + // Provision + IDirector director = createDirector(); IStatus result = director.provision(request, null, null); - if (!result.isOK()) + if (!result.isOK()) { LogHelper.log(result); + } + + // The UI is installed because the requirement is optional assertTrue("1.0", result.isOK()); assertProfileContains("1.1", profile, allUnits); } @@ -124,30 +154,50 @@ public class AutomatedDirectorTest extends AbstractProvisioningTest { * the capability has a platform filter that is not satisfied. */ public void testInstallPlatformFilter() { - //The IU that exports the capability - String capabilityId = "test." + getName(); - IProvidedCapability[] provides = new IProvidedCapability[] {MetadataFactory.createProvidedCapability("test.capability", capabilityId, DEFAULT_VERSION)}; - IInstallableUnit requiredIU = createIU("required." + getName(), createFilter("osgi.os", "blort"), provides); + // Profile environment + final String osKey = "osgi.os"; + final String osVal = "blort"; + + // Test capability + final String capabilityNamespace = "test.capability"; + final String capabilityName = "test." + getName(); + final Version capabilityVersion = DEFAULT_VERSION; - IInstallableUnit toInstallIU = createIU("toInstall." + getName(), createRequiredCapabilities("test.capability", capabilityId, ANY_VERSION, (IMatchExpression) null)); + // The IU that exports the capability + IProvidedCapability[] provides = new IProvidedCapability[] {MetadataFactory.createProvidedCapability(capabilityNamespace, capabilityName, capabilityVersion)}; + IInstallableUnit requiredIU = createIU("required." + getName(), createFilter(osKey, osVal), provides); + // Installed IU + IInstallableUnit toInstallIU = createIU("toInstall." + getName(), createRequiredCapabilities(capabilityNamespace, capabilityName, ANY_VERSION, (IMatchExpression) null)); + + // Metadata repo IInstallableUnit[] allUnits = new IInstallableUnit[] {requiredIU, toInstallIU}; - IInstallableUnit[] toInstallArray = new IInstallableUnit[] {toInstallIU}; createTestMetdataRepository(allUnits); - IProfile profile = createProfile("TestProfile." + getName()); IDirector director = createDirector(); + + // Profile that does not satisfy the OS requirement + IProfile profile = createProfile("TestProfile." + getName()); + + // Request ProfileChangeRequest request = new ProfileChangeRequest(profile); + IInstallableUnit[] toInstallArray = new IInstallableUnit[] {toInstallIU}; request.addInstallableUnits(toInstallArray); + + // Provision - should fail since requireIU can't be installed on the current environment IStatus result = director.provision(request, null, null); assertTrue("1.0", !result.isOK()); - //try again with the filter satisfied + // New profile that satisfies the OS requirement Map properties = new HashMap<>(); - properties.put(IProfile.PROP_ENVIRONMENTS, "osgi.os=blort"); - IProfile profile2 = createProfile("TestProfile2." + getName(), properties); - request = new ProfileChangeRequest(profile2); + properties.put(IProfile.PROP_ENVIRONMENTS, osKey + "=" + osVal); + profile = createProfile("TestProfile2." + getName(), properties); + + // New request + request = new ProfileChangeRequest(profile); request.addInstallableUnits(toInstallArray); + + // New provisioning - should succeed result = director.provision(request, null, null); assertTrue("2.0", result.isOK()); } @@ -156,27 +206,42 @@ public class AutomatedDirectorTest extends AbstractProvisioningTest { * Tests installing an IU that has an unsatisfied platform filter */ public void testInstallPlatformFilterUnsatisfied() { - //The IU to install - IInstallableUnit toInstallIU = createIU("toInstall." + getName(), createFilter("osgi.os", "blort"), NO_PROVIDES); + // Profile environment + final String osKey = "osgi.os"; + final String osVal = "blort"; + + // The IU to install that needs a concrete environment + IInstallableUnit toInstallIU = createIU("toInstall." + getName(), createFilter(osKey, osVal), NO_PROVIDES); + + // Metadata repo IInstallableUnit[] allUnits = new IInstallableUnit[] {toInstallIU}; - IInstallableUnit[] toInstallArray = new IInstallableUnit[] {toInstallIU}; createTestMetdataRepository(allUnits); + // Profile without a matching environment IProfile profile = createProfile("TestProfile." + getName()); - IDirector director = createDirector(); + + // Change request ProfileChangeRequest request = new ProfileChangeRequest(profile); + IInstallableUnit[] toInstallArray = new IInstallableUnit[] {toInstallIU}; request.addInstallableUnits(toInstallArray); + + // Provisioning failure: incompatible environment + IDirector director = createDirector(); IStatus result = director.provision(request, null, null); - assertTrue("1.0", !result.isOK()); + assertTrue(!result.isOK()); - //try again with the filter satisfied + // Profile with matching environment Map properties = new HashMap<>(); - properties.put(IProfile.PROP_ENVIRONMENTS, "osgi.os=blort"); - IProfile profile2 = createProfile("TestProfile2." + getName(), properties); - request = new ProfileChangeRequest(profile2); + properties.put(IProfile.PROP_ENVIRONMENTS, osKey + "=" + osVal); + profile = createProfile("TestProfile2." + getName(), properties); + + // New change request + request = new ProfileChangeRequest(profile); request.addInstallableUnits(toInstallArray); + + // Provisioning success result = director.provision(request, null, null); - assertTrue("2.0", result.isOK()); + assertTrue(result.isOK()); } /** @@ -184,24 +249,39 @@ public class AutomatedDirectorTest extends AbstractProvisioningTest { * that the IU providing the capability is installed. */ public void testSimpleInstallRequired() { - String capabilityId = "test." + getName(); + // Test capability + String capabilityNamespace = "test.capability"; + String capabilityName = "test." + getName(); + //The IU that exports the capability - IInstallableUnit requiredIU = createIU("required." + getName(), new IProvidedCapability[] {MetadataFactory.createProvidedCapability("test.capability", capabilityId, DEFAULT_VERSION)}); + IProvidedCapability[] provides = new IProvidedCapability[] { + MetadataFactory.createProvidedCapability(capabilityNamespace, capabilityName, DEFAULT_VERSION) + }; + IInstallableUnit requiredIU = createIU("required." + getName(), provides); - IInstallableUnit toInstallIU = createIU("toInstall." + getName(), createRequiredCapabilities("test.capability", capabilityId, ANY_VERSION, (IMatchExpression) null)); + // The IU that requires the capability + IRequirement[] requires = createRequiredCapabilities(capabilityNamespace, capabilityName, ANY_VERSION, (IMatchExpression) null); + IInstallableUnit toInstallIU = createIU("toInstall." + getName(), requires); + // Crate the metadata repo IInstallableUnit[] allUnits = new IInstallableUnit[] {requiredIU, toInstallIU}; - IInstallableUnit[] toInstallArray = new IInstallableUnit[] {toInstallIU}; createTestMetdataRepository(allUnits); + // Provision IProfile profile = createProfile("TestProfile." + getName()); - IDirector director = createDirector(); + // Create the profile change request ProfileChangeRequest request = new ProfileChangeRequest(profile); + IInstallableUnit[] toInstallArray = new IInstallableUnit[] {toInstallIU}; request.addInstallableUnits(toInstallArray); + + // Provision + IDirector director = createDirector(); IStatus result = director.provision(request, null, null); - if (!result.isOK()) + if (!result.isOK()) { LogHelper.log(result); + } + assertTrue("1.0", result.isOK()); assertProfileContains("1.1", profile, allUnits); } @@ -211,24 +291,34 @@ public class AutomatedDirectorTest extends AbstractProvisioningTest { * specifying a version range (any version will do). */ public void testInstallRequiredNoVersion() { - //The IU that is needed + // The IU that is needed IInstallableUnit requiredIU = createIU("required." + getName()); - IRequirement capability = MetadataFactory.createRequirement(IInstallableUnit.NAMESPACE_IU_ID, requiredIU.getId(), null, null, false, false); - IInstallableUnit toInstallIU = createIU("toInstall." + getName(), new IRequirement[] {capability}); + // The IU that is installed + IRequirement[] requires = new IRequirement[] { + MetadataFactory.createRequirement(IInstallableUnit.NAMESPACE_IU_ID, requiredIU.getId(), null, null, false, false) + }; + IInstallableUnit toInstallIU = createIU("toInstall." + getName(), requires); + // Metadata repo IInstallableUnit[] allUnits = new IInstallableUnit[] {requiredIU, toInstallIU}; - IInstallableUnit[] toInstallArray = new IInstallableUnit[] {toInstallIU}; createTestMetdataRepository(allUnits); + // Profile IProfile profile = createProfile("TestProfile." + getName()); - IDirector director = createDirector(); + // Profile change request ProfileChangeRequest request = new ProfileChangeRequest(profile); + IInstallableUnit[] toInstallArray = new IInstallableUnit[] {toInstallIU}; request.addInstallableUnits(toInstallArray); + + // Provision + IDirector director = createDirector(); IStatus result = director.provision(request, null, null); - if (!result.isOK()) + if (!result.isOK()) { LogHelper.log(result); + } + assertTrue("1.0", result.isOK()); assertProfileContains("1.1", profile, allUnits); } @@ -239,25 +329,34 @@ public class AutomatedDirectorTest extends AbstractProvisioningTest { * capability on the IU namespace. */ public void testSimpleInstallRequiredIU() { - //The IU that exports the capability + // The IU that is required. It exports it's identity capability by default. IInstallableUnit requiredIU = createIU("required." + getName()); - IRequirement capability = MetadataFactory.createRequirement(IInstallableUnit.NAMESPACE_IU_ID, requiredIU.getId(), ANY_VERSION, null, false, false); - IInstallableUnit toInstallIU = createIU("toInstall." + getName(), new IRequirement[] {capability}); + // The IU that is installed + IRequirement[] requires = new IRequirement[] { + MetadataFactory.createRequirement(IInstallableUnit.NAMESPACE_IU_ID, requiredIU.getId(), ANY_VERSION, null, false, false) + }; + IInstallableUnit toInstallIU = createIU("toInstall." + getName(), requires); + // Create the metadata repo IInstallableUnit[] allUnits = new IInstallableUnit[] {requiredIU, toInstallIU}; - IInstallableUnit[] toInstallArray = new IInstallableUnit[] {toInstallIU}; createTestMetdataRepository(allUnits); IProfile profile = createProfile("TestProfile." + getName()); - IDirector director = createDirector(); + // Create the profile change request ProfileChangeRequest request = new ProfileChangeRequest(profile); + IInstallableUnit[] toInstallArray = new IInstallableUnit[] {toInstallIU}; request.addInstallableUnits(toInstallArray); + + // Provision + IDirector director = createDirector(); IStatus result = director.provision(request, null, null); - if (!result.isOK()) + if (!result.isOK()) { LogHelper.log(result); - assertTrue("1.0", result.isOK()); + } + + assertTrue(result.isOK()); assertProfileContains("1.1", profile, allUnits); } diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/engine/PhaseSetTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/engine/PhaseSetTest.java index f085f9415..609d1d69a 100644 --- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/engine/PhaseSetTest.java +++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/engine/PhaseSetTest.java @@ -12,21 +12,42 @@ package org.eclipse.equinox.p2.tests.engine; import java.io.File; import java.net.URI; -import java.util.*; +import java.util.EventObject; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.equinox.internal.p2.director.ProfileChangeRequest; -import org.eclipse.equinox.internal.p2.engine.*; +import org.eclipse.equinox.internal.p2.engine.BeginOperationEvent; +import org.eclipse.equinox.internal.p2.engine.CommitOperationEvent; +import org.eclipse.equinox.internal.p2.engine.EngineSession; +import org.eclipse.equinox.internal.p2.engine.InstallableUnitOperand; +import org.eclipse.equinox.internal.p2.engine.Phase; +import org.eclipse.equinox.internal.p2.engine.PhaseEvent; +import org.eclipse.equinox.internal.p2.engine.PhaseSet; +import org.eclipse.equinox.internal.p2.engine.ProfileEvent; +import org.eclipse.equinox.internal.p2.engine.RollbackOperationEvent; import org.eclipse.equinox.internal.p2.repository.DownloadProgressEvent; import org.eclipse.equinox.internal.p2.touchpoint.natives.Util; import org.eclipse.equinox.internal.provisional.p2.core.eventbus.ProvisioningListener; import org.eclipse.equinox.internal.provisional.p2.repository.RepositoryEvent; import org.eclipse.equinox.p2.core.ProvisionException; -import org.eclipse.equinox.p2.engine.*; +import org.eclipse.equinox.p2.engine.IEngine; +import org.eclipse.equinox.p2.engine.IPhaseSet; +import org.eclipse.equinox.p2.engine.IProfile; +import org.eclipse.equinox.p2.engine.IProvisioningPlan; +import org.eclipse.equinox.p2.engine.PhaseSetFactory; +import org.eclipse.equinox.p2.engine.ProvisioningContext; import org.eclipse.equinox.p2.metadata.IInstallableUnit; -import org.eclipse.equinox.p2.query.*; +import org.eclipse.equinox.p2.query.IQuery; +import org.eclipse.equinox.p2.query.IQueryResult; +import org.eclipse.equinox.p2.query.QueryUtil; import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository; import org.eclipse.equinox.p2.tests.AbstractProvisioningTest; import org.eclipse.equinox.p2.tests.TestActivator; @@ -176,7 +197,7 @@ public class PhaseSetTest extends AbstractProvisioningTest { getArtifactRepositoryManager().loadRepository(repoURI, null); doProvisioning(repoURI, phaseSet, query, expectedCode, monitor); // make sure the listener handles all event already that are dispatched asynchronously - listener.latch.await(10, TimeUnit.SECONDS); + listener.latch.await(15, TimeUnit.SECONDS); assertFalse("Engine still do provisioning after pausing.", listener.hasProvisioningEventAfterPaused); pauseJob.join(); } finally { diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/RequirementToString.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/RequirementToString.java index 1ccc188bf..476d0daed 100644 --- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/RequirementToString.java +++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/RequirementToString.java @@ -23,18 +23,23 @@ import org.eclipse.equinox.p2.metadata.expression.IMatchExpression; public class RequirementToString extends TestCase { public void testRequirementWithEmptyRange() { IRequirement req = MetadataFactory.createRequirement("expectedNameSpace", "expectedName", VersionRange.emptyRange, null, false, false); - assertEquals("expectedNameSpace expectedName 0.0.0", req.toString()); + assertEquals("expectedNameSpace; expectedName 0.0.0", req.toString()); } public void testStandardRequirement() { IRequirement req = MetadataFactory.createRequirement("expectedNameSpace", "expectedName", new VersionRange("[1.0.0, 2.0.0)"), null, false, false); - assertEquals("expectedNameSpace expectedName [1.0.0,2.0.0)", req.toString()); + assertEquals("expectedNameSpace; expectedName [1.0.0,2.0.0)", req.toString()); + } + + public void testPropertiesRequirement() { + IRequirement req = MetadataFactory.createRequirement("expectedNameSpace", "(key=val)", null, 1, 1, true); + assertEquals("expectedNameSpace; (key=val)", req.toString()); } public void testFancyRequirement() { - Object[] expressionParameters = new Object[] {"expectedId1, expectedVersion1", "expectedId2, expectedVersion2"}; + Object[] expressionParameters = new Object[] {"expectedId1", "expectedVersion1", "expectedId2", "expectedVersion2"}; IMatchExpression iuMatcher = ExpressionUtil.getFactory(). matchExpression(ExpressionUtil.parse("(id == $0 && version == $1) || (id == $2 && version == $3)"), expressionParameters); IRequirement req = MetadataFactory.createRequirement(iuMatcher, null, 1, 1, true); - assertEquals("id == $0 && version == $1 || id == $2 && version == $3 expectedId1, expectedVersion1 expectedId2, expectedVersion2", req.toString().trim()); + assertEquals("id == $0 && version == $1 || id == $2 && version == $3 (expectedId1, expectedVersion1, expectedId2, expectedVersion2)", req.toString().trim()); } } diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/expression/AllTests.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/expression/AllTests.java index 27ebc00b8..8e5be56d7 100644 --- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/expression/AllTests.java +++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/expression/AllTests.java @@ -4,13 +4,16 @@ * 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.tests.metadata.expression; -import junit.framework.*; +import junit.framework.JUnit4TestAdapter; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; /** * Performs all automated director tests. @@ -20,7 +23,7 @@ public class AllTests extends TestCase { public static Test suite() { TestSuite suite = new TestSuite(AllTests.class.getName()); suite.addTestSuite(ExpressionTest.class); - suite.addTestSuite(FilterTest.class); + suite.addTest(new JUnit4TestAdapter(FilterTest.class)); return suite; } diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/expression/FilterTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/expression/FilterTest.java index 0d6895ac2..4a51833fb 100644 --- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/expression/FilterTest.java +++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/expression/FilterTest.java @@ -10,19 +10,225 @@ *******************************************************************************/ package org.eclipse.equinox.p2.tests.metadata.expression; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.math.BigDecimal; import java.math.BigInteger; -import java.util.*; -import junit.framework.*; -import org.eclipse.equinox.p2.metadata.expression.*; -import org.osgi.framework.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import org.eclipse.equinox.p2.metadata.Version; +import org.eclipse.equinox.p2.metadata.expression.ExpressionParseException; +import org.eclipse.equinox.p2.metadata.expression.ExpressionUtil; +import org.eclipse.equinox.p2.metadata.expression.IFilterExpression; +import org.junit.Test; +import org.osgi.framework.Bundle; +import org.osgi.framework.Filter; +import org.osgi.framework.ServiceReference; + +public class FilterTest { + @Test + public void testComparable() throws Exception { + IFilterExpression f1 = ExpressionUtil.parseLDAP("(comparable=42)"); + Object comp; + Map hash = new HashMap<>(); + + comp = new SampleComparable("42"); + hash.put("comparable", comp); + assertTrue("does not match filter", f1.match(hash)); + assertTrue("does not match filter", f1.match(new DictionaryServiceReference(hash))); + + comp = Long.valueOf(42); + hash.put("comparable", comp); + assertTrue("does not match filter", f1.match(hash)); + assertTrue("does not match filter", f1.match(new DictionaryServiceReference(hash))); + + IFilterExpression f2 = ExpressionUtil.parseLDAP("(comparable=42)"); + hash = new Hashtable<>(); + + comp = new SampleComparable("42"); + hash.put("comparable", comp); + assertTrue("does not match filter", f2.match(hash)); + assertTrue("does not match filter", f2.match(new DictionaryServiceReference(hash))); + + comp = Long.valueOf(42); + hash.put("comparable", comp); + assertTrue("does not match filter", f2.match(hash)); + assertTrue("does not match filter", f2.match(new DictionaryServiceReference(hash))); + + assertEquals("not equal", f1, f2); + } + + @Test + public void testFilterEquality() { + Filter f1 = ExpressionUtil.parseLDAP("( a = bedroom )"); + Filter f2 = ExpressionUtil.parseLDAP(" (a= bedroom ) "); + assertEquals("not equal", "(a= bedroom )", f1.toString()); + assertEquals("not equal", "(a= bedroom )", f2.toString()); + assertEquals("not equal", f1, f2); + assertEquals("not equal", f2, f1); + assertEquals("not equal", f1.hashCode(), f2.hashCode()); + + f1 = ExpressionUtil.parseLDAP("(status =\\28o*\\5c\\29\\2a)"); + assertEquals("not equal", "(status=\\28o*\\5c\\29\\2a)", f1.toString()); + + f1 = ExpressionUtil.parseLDAP("(|(a=1)(&(a=1)(b=1)))"); + f2 = ExpressionUtil.parseLDAP("(a=1)"); + + f1 = ExpressionUtil.parseLDAP("(|(&(os=macos)(ws=cocoa)(arch=x86))(&(ws=cocoa)(os=macos)(arch=ppc)))"); + f2 = ExpressionUtil.parseLDAP("(&(os=macos)(ws=cocoa)(|(arch=x86)(arch=ppc)))"); + assertEquals("not equal: f1:" + f1.toString() + ", f2:" + f1.toString(), f1, f2); + + f1 = ExpressionUtil.parseLDAP("(&(|(x=a)(y=b)(z=a))(|(x=a)(y=b)(z=b)))"); + f2 = ExpressionUtil.parseLDAP("(|(x=a)(y=b)(&(z=a)(z=b)))"); + assertEquals("not equal: f1:" + f1.toString() + ", f2:" + f1.toString(), f1, f2); + + f1 = ExpressionUtil.parseLDAP("(&(a=1)(|(a=1)(b=1)))"); + f2 = ExpressionUtil.parseLDAP("(a=1)"); + + f1 = ExpressionUtil.parseLDAP("(|(a=1)(&(a=1)(b=1)))"); + f2 = ExpressionUtil.parseLDAP("(a=1)"); + assertEquals("not equal: f1:" + f1.toString() + ", f2:" + f1.toString(), f1, f2); + } + + @Test + public void testFilterMatching() { + Dictionary props = new Hashtable<>(); + props.put("room", "bedroom"); + props.put("channel", Integer.valueOf(34)); + props.put("status", "(on\\)*"); + props.put("max record time", Long.valueOf(150)); + props.put("canrecord", "true(x)"); + props.put("shortvalue", Short.valueOf((short) 1000)); + props.put("bytevalue", Byte.valueOf((byte) 10)); + props.put("floatvalue", Float.valueOf(1.01f)); + props.put("doublevalue", Double.valueOf(2.01)); + props.put("charvalue", Character.valueOf('A')); + props.put("booleanvalue", Boolean.FALSE); + props.put("listvalue", Arrays.asList(1, 2, 3)); + props.put("versionlistvalue", Arrays.asList(Version.create("1"), Version.create("2"), Version.create("3"))); + props.put("weirdvalue", new Hashtable<>()); + props.put("bigintvalue", new BigInteger("4123456")); + props.put("bigdecvalue", new BigDecimal("4.123456")); + + assertMatch("(room=*)", props); + assertNoMatch("(rooom=*)", props); + assertMatch("(room=bedroom)", props); + assertMatch("(room~= B E D R O O M )", props); + assertNoMatch("(room=abc)", props); + assertMatch(" ( room >=aaaa)", props); + assertNoMatch("(room <=aaaa)", props); + assertMatch(" ( room =b*) ", props); + assertMatch(" ( room =*m) ", props); + assertMatch("(room=bed*room)", props); + assertMatch(" ( room =b*oo*m) ", props); + assertMatch(" ( room =*b*oo*m*) ", props); + assertNoMatch(" ( room =b*b* *m*) ", props); + assertMatch(" (& (room =bedroom) (channel = 34))", props); + assertNoMatch(" (& (room =b*) (room =*x) (channel=34))", props); + assertMatch("(| (room =bed*)(channel=222)) ", props); + assertMatch("(| (room =boom*)(channel=34)) ", props); + assertMatch(" (! (room =ab*b*oo*m*) ) ", props); + assertMatch(" (status =\\(o*\\\\\\)\\*) ", props); + assertMatch(" (status =\\28o*\\5c\\29\\2a) ", props); + assertMatch(" (status =\\28o*\\5C\\29\\2A) ", props); + assertMatch(" (canRecord =true\\(x\\)) ", props); + assertMatch("(max Record Time <=150) ", props); + assertMatch("(shortValue >= 100) ", props); + assertMatch(" ( & ( byteValue <= 100 ) ( byteValue >= 10 ) ) ", props); + assertMatch("(bigIntValue = 4123456) ", props); + assertMatch("(bigDecValue = 4.123456) ", props); + assertMatch("(floatValue >= 1.0) ", props); + assertMatch("(doubleValue <= 2.011) ", props); + assertMatch("(charValue ~= a) ", props); + assertMatch("(booleanValue = false) ", props); + assertMatch("(listvalue>=0)", props); + assertMatch("(listvalue=3)", props); + assertMatch("(!(listvalue>=4))", props); + assertMatch("(versionlistvalue>=0)", props); + assertMatch("(versionlistvalue=3)", props); + assertMatch("(!(versionlistvalue>=4))", props); + assertMatch("(& (| (room =d*m) (room =bed*) (room=abc)) (! (channel=999)))", props); + assertNoMatch("(room=bedroom)", null); + assertNoMatch("(weirdValue = 100) ", props); + } + + @Test + public void testFilterParserErrors() { + assertParseError("()"); + assertParseError("(=foo)"); + assertParseError("("); + assertParseError("(abc = ))"); + assertParseError("(& (abc = xyz) (& (345))"); + assertParseError(" (room = b**oo!*m*) ) "); + assertParseError(" (room = b**oo)*m*) ) "); + assertParseError(" (room = *=b**oo*m*) ) "); + assertParseError(" (room = =b**oo*m*) ) "); + } + + private void assertMatch(String query, Dictionary props) { + expectMatch(query, props, true); + } + + private void assertNoMatch(String query, Dictionary props) { + expectMatch(query, props, false); + } + + private void expectMatch(String query, Dictionary props, boolean match) { + Filter f = ExpressionUtil.parseLDAP(query); + + // TODO Doing raw conversion here for simplicity; could convert to Dictionary + // but the filter impl must still handle cases where non String keys are used. + assertEquals(match, f.match(props)); + + ServiceReference ref = new DictionaryServiceReference((Map) props); + assertEquals(match, f.match(ref)); + } + + private void assertParseError(String query) { + try { + ExpressionUtil.parseLDAP(query); + fail("expected exception"); + } catch (ExpressionParseException e) { + // Pass + } + } + + private static class SampleComparable implements Comparable { + private int value = -1; + + public SampleComparable(String value) { + this.value = Integer.parseInt(value); + } + + @Override + public boolean equals(Object o) { + return o instanceof SampleComparable && value == ((SampleComparable) o).value; + } + + @Override + public int compareTo(SampleComparable o) { + return value - o.value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + } -public class FilterTest extends TestCase { private static class DictionaryServiceReference implements ServiceReference { private final Map dictionary; - + private final String[] keys; - + DictionaryServiceReference(Map dictionary) { if (dictionary == null) { this.dictionary = null; @@ -42,17 +248,17 @@ public class FilterTest extends TestCase { } this.keys = keyList.toArray(new String[keyList.size()]); } - + @Override public int compareTo(Object reference) { throw new UnsupportedOperationException(); } - + @Override public Bundle getBundle() { return null; } - + @Override public Object getProperty(String k) { for (int i = 0, length = keys.length; i < length; i++) { @@ -63,230 +269,25 @@ public class FilterTest extends TestCase { } return null; } - + @Override public String[] getPropertyKeys() { return keys.clone(); } - + @Override public Bundle[] getUsingBundles() { throw new UnsupportedOperationException(); } - + @Override public boolean isAssignableTo(Bundle bundle, String className) { throw new UnsupportedOperationException(); } - + @Override public Dictionary getProperties() { return new Hashtable<>(dictionary); } } - - private static class SampleComparable implements Comparable { - private int value = -1; - - public SampleComparable(String value) { - this.value = Integer.parseInt(value); - } - - @Override - public boolean equals(Object o) { - return o instanceof SampleComparable && value == ((SampleComparable) o).value; - } - - @Override - public int compareTo(SampleComparable o) { - return value - o.value; - } - - @Override - public String toString() { - return String.valueOf(value); - } - } - - static final int ISTRUE = 1; - - static final int ISFALSE = 2; - - static final int ISILLEGAL = 3; - - public static Test suite() { - return new TestSuite(FilterTest.class); - } - - public void testComparable() throws Exception { - IFilterExpression f1 = ExpressionUtil.parseLDAP("(comparable=42)"); //$NON-NLS-1$ - Object comp; - Map hash = new HashMap<>(); - - comp = new SampleComparable("42"); //$NON-NLS-1$ - hash.put("comparable", comp); //$NON-NLS-1$ - assertTrue("does not match filter", f1.match(hash)); //$NON-NLS-1$ - assertTrue("does not match filter", f1.match(new DictionaryServiceReference(hash))); //$NON-NLS-1$ - - comp = Long.valueOf(42); - hash.put("comparable", comp); //$NON-NLS-1$ - assertTrue("does not match filter", f1.match(hash)); //$NON-NLS-1$ - assertTrue("does not match filter", f1.match(new DictionaryServiceReference(hash))); //$NON-NLS-1$ - - IFilterExpression f2 = ExpressionUtil.parseLDAP("(comparable=42)"); //$NON-NLS-1$ - hash = new Hashtable<>(); - - comp = new SampleComparable("42"); //$NON-NLS-1$ - hash.put("comparable", comp); //$NON-NLS-1$ - assertTrue("does not match filter", f2.match(hash)); //$NON-NLS-1$ - assertTrue("does not match filter", f2.match(new DictionaryServiceReference(hash))); //$NON-NLS-1$ - - comp = Long.valueOf(42); - hash.put("comparable", comp); //$NON-NLS-1$ - assertTrue("does not match filter", f2.match(hash)); //$NON-NLS-1$ - assertTrue("does not match filter", f2.match(new DictionaryServiceReference(hash))); //$NON-NLS-1$ - - assertEquals("not equal", f1, f2); //$NON-NLS-1$ - } - - public void testFilter() { - Dictionary props = new Hashtable<>(); - props.put("room", "bedroom"); //$NON-NLS-1$ //$NON-NLS-2$ - props.put("channel", Integer.valueOf(34)); //$NON-NLS-1$ - props.put("status", "(on\\)*"); //$NON-NLS-1$//$NON-NLS-2$ - props.put("max record time", Long.valueOf(150)); //$NON-NLS-1$ - props.put("canrecord", "true(x)"); //$NON-NLS-1$ //$NON-NLS-2$ - props.put("shortvalue", Short.valueOf((short) 1000)); //$NON-NLS-1$ - props.put("bytevalue", Byte.valueOf((byte) 10)); //$NON-NLS-1$ - props.put("floatvalue", Float.valueOf(1.01f)); //$NON-NLS-1$ - props.put("doublevalue", Double.valueOf(2.01)); //$NON-NLS-1$ - props.put("charvalue", Character.valueOf('A')); //$NON-NLS-1$ - props.put("booleanvalue", Boolean.FALSE); //$NON-NLS-1$ - props.put("weirdvalue", new Hashtable<>()); //$NON-NLS-1$ - try { - props.put("bigintvalue", new BigInteger("4123456")); //$NON-NLS-1$ //$NON-NLS-2$ - } catch (NoClassDefFoundError e) { - // ignore - } - try { - props.put("bigdecvalue", new BigDecimal("4.123456")); //$NON-NLS-1$ //$NON-NLS-2$ - } catch (NoClassDefFoundError e) { - // ignore - } - - testFilter("(room=*)", props, ISTRUE); //$NON-NLS-1$ - testFilter("(rooom=*)", props, ISFALSE); //$NON-NLS-1$ - testFilter("(room=bedroom)", props, ISTRUE); //$NON-NLS-1$ - testFilter("(room~= B E D R O O M )", props, ISTRUE); //$NON-NLS-1$ - testFilter("(room=abc)", props, ISFALSE); //$NON-NLS-1$ - testFilter(" ( room >=aaaa)", props, ISTRUE); //$NON-NLS-1$ - testFilter("(room <=aaaa)", props, ISFALSE); //$NON-NLS-1$ - testFilter(" ( room =b*) ", props, ISTRUE); //$NON-NLS-1$ - testFilter(" ( room =*m) ", props, ISTRUE); //$NON-NLS-1$ - testFilter("(room=bed*room)", props, ISTRUE); //$NON-NLS-1$ - testFilter(" ( room =b*oo*m) ", props, ISTRUE); //$NON-NLS-1$ - testFilter(" ( room =*b*oo*m*) ", props, ISTRUE); //$NON-NLS-1$ - testFilter(" ( room =b*b* *m*) ", props, ISFALSE); //$NON-NLS-1$ - testFilter(" (& (room =bedroom) (channel = 34))", props, ISTRUE); //$NON-NLS-1$ - testFilter(" (& (room =b*) (room =*x) (channel=34))", props, ISFALSE); //$NON-NLS-1$ - testFilter("(| (room =bed*)(channel=222)) ", props, ISTRUE); //$NON-NLS-1$ - testFilter("(| (room =boom*)(channel=34)) ", props, ISTRUE); //$NON-NLS-1$ - testFilter(" (! (room =ab*b*oo*m*) ) ", props, ISTRUE); //$NON-NLS-1$ - testFilter(" (status =\\(o*\\\\\\)\\*) ", props, ISTRUE); //$NON-NLS-1$ - testFilter(" (status =\\28o*\\5c\\29\\2a) ", props, ISTRUE); //$NON-NLS-1$ - testFilter(" (status =\\28o*\\5C\\29\\2A) ", props, ISTRUE); //$NON-NLS-1$ - testFilter(" (canRecord =true\\(x\\)) ", props, ISTRUE); //$NON-NLS-1$ - testFilter("(max Record Time <=150) ", props, ISTRUE); //$NON-NLS-1$ - testFilter("(shortValue >= 100) ", props, ISTRUE); //$NON-NLS-1$ - testFilter(" ( & ( byteValue <= 100 ) ( byteValue >= 10 ) ) ", props, ISTRUE); //$NON-NLS-1$ - testFilter("(bigIntValue = 4123456) ", props, ISTRUE); //$NON-NLS-1$ - testFilter("(bigDecValue = 4.123456) ", props, ISTRUE); //$NON-NLS-1$ - testFilter("(floatValue >= 1.0) ", props, ISTRUE); //$NON-NLS-1$ - testFilter("(doubleValue <= 2.011) ", props, ISTRUE); //$NON-NLS-1$ - testFilter("(charValue ~= a) ", props, ISTRUE); //$NON-NLS-1$ - testFilter("(booleanValue = false) ", props, ISTRUE); //$NON-NLS-1$ - testFilter("(& (| (room =d*m) (room =bed*) (room=abc)) (! (channel=999)))", props, ISTRUE); //$NON-NLS-1$ - testFilter("(room=bedroom)", null, ISFALSE); //$NON-NLS-1$ - - testFilter("()", props, ISILLEGAL); //$NON-NLS-1$ - testFilter("(=foo)", props, ISILLEGAL); //$NON-NLS-1$ - testFilter("(", props, ISILLEGAL); //$NON-NLS-1$ - testFilter("(abc = ))", props, ISILLEGAL); //$NON-NLS-1$ - testFilter("(& (abc = xyz) (& (345))", props, ISILLEGAL); //$NON-NLS-1$ - testFilter(" (room = b**oo!*m*) ) ", props, ISILLEGAL); //$NON-NLS-1$ - testFilter(" (room = b**oo)*m*) ) ", props, ISILLEGAL); //$NON-NLS-1$ - testFilter(" (room = *=b**oo*m*) ) ", props, ISILLEGAL); //$NON-NLS-1$ - testFilter(" (room = =b**oo*m*) ) ", props, ISILLEGAL); //$NON-NLS-1$ - - try { - Filter f1 = ExpressionUtil.parseLDAP("( a = bedroom )"); //$NON-NLS-1$ - Filter f2 = ExpressionUtil.parseLDAP(" (a= bedroom ) "); //$NON-NLS-1$ - assertEquals("not equal", "(a= bedroom )", f1.toString()); //$NON-NLS-1$ //$NON-NLS-2$ - assertEquals("not equal", "(a= bedroom )", f2.toString()); //$NON-NLS-1$ //$NON-NLS-2$ - assertEquals("not equal", f1, f2); //$NON-NLS-1$ - assertEquals("not equal", f2, f1); //$NON-NLS-1$ - assertEquals("not equal", f1.hashCode(), f2.hashCode()); //$NON-NLS-1$ - - f1 = ExpressionUtil.parseLDAP("(status =\\28o*\\5c\\29\\2a)"); - assertEquals("not equal", "(status=\\28o*\\5c\\29\\2a)", f1.toString()); //$NON-NLS-1$ //$NON-NLS-2$ - - f1 = ExpressionUtil.parseLDAP("(|(a=1)(&(a=1)(b=1)))"); //$NON-NLS-1$ - f2 = ExpressionUtil.parseLDAP("(a=1)"); //$NON-NLS-1$ - System.out.println(f2.toString()); - System.out.println(f1.toString()); - - f1 = ExpressionUtil.parseLDAP("(|(&(os=macos)(ws=cocoa)(arch=x86))(&(ws=cocoa)(os=macos)(arch=ppc)))"); //$NON-NLS-1$ - f2 = ExpressionUtil.parseLDAP("(&(os=macos)(ws=cocoa)(|(arch=x86)(arch=ppc)))"); //$NON-NLS-1$ - System.out.println(f2.toString()); - System.out.println(f1.toString()); - assertEquals("not equal", f1, f2); //$NON-NLS-1$ - - f1 = ExpressionUtil.parseLDAP("(&(|(x=a)(y=b)(z=a))(|(x=a)(y=b)(z=b)))"); //$NON-NLS-1$ - f2 = ExpressionUtil.parseLDAP("(|(x=a)(y=b)(&(z=a)(z=b)))"); //$NON-NLS-1$ - System.out.println(f2.toString()); - System.out.println(f1.toString()); - assertEquals("not equal", f1, f2); //$NON-NLS-1$ - - f1 = ExpressionUtil.parseLDAP("(&(a=1)(|(a=1)(b=1)))"); //$NON-NLS-1$ - f2 = ExpressionUtil.parseLDAP("(a=1)"); //$NON-NLS-1$ - System.out.println(f2.toString()); - System.out.println(f1.toString()); - - f1 = ExpressionUtil.parseLDAP("(|(a=1)(&(a=1)(b=1)))"); //$NON-NLS-1$ - f2 = ExpressionUtil.parseLDAP("(a=1)"); //$NON-NLS-1$ - System.out.println(f2.toString()); - System.out.println(f1.toString()); - assertEquals("not equal", f1, f2); //$NON-NLS-1$ - } catch (IllegalArgumentException e) { - fail("unexpected invalid syntax: " + e); //$NON-NLS-1$ - } - testFilter("(weirdValue = 100) ", props, ISFALSE); //$NON-NLS-1$ - - } - - private void testFilter(String query, Dictionary props, int expect) { - final ServiceReference ref = new DictionaryServiceReference((Map) props); - Filter f1; - try { - f1 = ExpressionUtil.parseLDAP(query); - - if (expect == ISILLEGAL) { - fail("expected exception"); //$NON-NLS-1$ - } - } catch (ExpressionParseException e) { - System.out.println(e.toString()); - if (expect != ISILLEGAL) { - fail("exception: " + e.toString()); //$NON-NLS-1$ - } - return; - } - - // TODO Doing raw conversion here for simplicity; could convert to Dictionary - // but the filter impl must still handle cases where non String keys are used. - boolean val = f1.match(props); - assertEquals("wrong result", expect == ISTRUE, val); //$NON-NLS-1$ - - val = f1.match(ref); - assertEquals("wrong result", expect == ISTRUE, val); //$NON-NLS-1$ - } } diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/SPIMetadataRepositoryTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/SPIMetadataRepositoryTest.java index 022b6549f..605143491 100644 --- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/SPIMetadataRepositoryTest.java +++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/SPIMetadataRepositoryTest.java @@ -179,14 +179,14 @@ public class SPIMetadataRepositoryTest extends AbstractProvisioningTest { class SPIProvidedCapability implements IProvidedCapability { private final String namespace; - private final Map attributes; + private final Map properties; public SPIProvidedCapability(String namespace, String name, Version version) { this.namespace = namespace; - this.attributes = new HashMap<>(); - attributes.put(namespace, name); - attributes.put(ProvidedCapability.ATTRIBUTE_VERSION, version); + this.properties = new HashMap<>(); + properties.put(namespace, name); + properties.put(IProvidedCapability.PROPERTY_VERSION, version); } @Override @@ -199,7 +199,7 @@ public class SPIMetadataRepositoryTest extends AbstractProvisioningTest { if (!(namespace.equals(otherCapability.getNamespace()))) { return false; } - if (!(attributes.equals(otherCapability.getAttributes()))) { + if (!(properties.equals(otherCapability.getProperties()))) { return false; } return true; @@ -207,12 +207,12 @@ public class SPIMetadataRepositoryTest extends AbstractProvisioningTest { @Override public String toString() { - return namespace + "; " + attributes; + return namespace + "; " + properties; } @Override public String getName() { - return (String) attributes.get(namespace); + return (String) properties.get(namespace); } @Override @@ -222,12 +222,12 @@ public class SPIMetadataRepositoryTest extends AbstractProvisioningTest { @Override public Version getVersion() { - return (Version) attributes.get(ProvidedCapability.ATTRIBUTE_VERSION); + return (Version) properties.get(IProvidedCapability.PROPERTY_VERSION); } @Override - public Map getAttributes() { - return Collections.unmodifiableMap(attributes); + public Map getProperties() { + return Collections.unmodifiableMap(properties); } } diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/planner/PropertyMatchRequirement.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/planner/PropertyMatchRequirement.java new file mode 100644 index 000000000..4d709c93a --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/planner/PropertyMatchRequirement.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2008, 2017 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.tests.planner; + +import static org.eclipse.core.runtime.IStatus.ERROR; +import static org.eclipse.core.runtime.IStatus.OK; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.eclipse.equinox.internal.p2.director.Explanation.MissingIU; +import org.eclipse.equinox.internal.p2.director.ProfileChangeRequest; +import org.eclipse.equinox.internal.provisional.p2.director.PlannerStatus; +import org.eclipse.equinox.internal.provisional.p2.director.RequestStatus; +import org.eclipse.equinox.p2.engine.IProfile; +import org.eclipse.equinox.p2.engine.IProvisioningPlan; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.metadata.IProvidedCapability; +import org.eclipse.equinox.p2.metadata.IRequirement; +import org.eclipse.equinox.p2.metadata.MetadataFactory; +import org.eclipse.equinox.p2.planner.IPlanner; +import org.eclipse.equinox.p2.tests.AbstractProvisioningTest; + +public class PropertyMatchRequirement extends AbstractProvisioningTest { + private IInstallableUnit providerIu; + private IInstallableUnit consumerIu; + + private IProfile profile; + private IPlanner planner; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // A standard OSGi service representation + String osgiService = "osgi.service"; + String objectClass = "objectClass"; + List objectClassList = Arrays.asList("org.example.A", "org.example.B", "org.example.C"); + + // Provider. + // TODO Check if p2 really needs a name. IProvidedCapability.equals() can differentiate by properties only. + Map capability = new HashMap<>(); + capability.put(osgiService, "ignored-1"); + capability.put(objectClass, objectClassList); + + IProvidedCapability[] provides = new IProvidedCapability[] { + MetadataFactory.createProvidedCapability(osgiService, capability) + }; + providerIu = createIU("provider", DEFAULT_VERSION, provides); + + // Consumer + String requirement = String.format("(%s=%s)", objectClass, objectClassList.get(0)); + IRequirement[] requires = new IRequirement[] { + MetadataFactory.createRequirement(osgiService, requirement, null, 1, 1, true) + }; + consumerIu = createIU("consumer", DEFAULT_VERSION, requires); + + // Planner + profile = createProfile("test." + getName()); + planner = createPlanner(); + } + + public void testMandatoryPresent() { + createTestMetdataRepository(new IInstallableUnit[] {providerIu, consumerIu}); + + ProfileChangeRequest req = new ProfileChangeRequest(profile); + req.add(consumerIu); + + // Must pass + IProvisioningPlan plan = planner.getProvisioningPlan(req, null, null); + assertEquals(OK, plan.getStatus().getSeverity()); + + // And both consumer and provider must be installed + assertInstallOperand(plan, consumerIu); + assertInstallOperand(plan, providerIu); + } + + public void testMandatoryAbsent() { + createTestMetdataRepository(new IInstallableUnit[] {consumerIu}); + + ProfileChangeRequest req = new ProfileChangeRequest(profile); + req.add(consumerIu); + + // Must fail + IProvisioningPlan plan = planner.getProvisioningPlan(req, null, null); + assertEquals(ERROR, plan.getStatus().getSeverity()); + + // With a good explanation + RequestStatus requestStatus = ((PlannerStatus) plan.getStatus()).getRequestStatus(); + IRequirement consumerReq = consumerIu.getRequirements().iterator().next(); + requestStatus.getExplanations() + .stream() + .filter(e -> e instanceof MissingIU) + .filter(e -> (((MissingIU) e).req.toString()).equals(consumerReq.toString())) + .findFirst() + .orElseGet(() -> { + fail("Did not find explanation for missing requirement: " + consumerReq); + return null; + }); + + // And the consumer must not be installed + assertNoOperand(plan, consumerIu); + } +} diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/ActionTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/ActionTest.java index 058cca76c..faf51598b 100644 --- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/ActionTest.java +++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/ActionTest.java @@ -27,15 +27,13 @@ import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.equinox.internal.p2.core.helpers.FileUtils; +import org.eclipse.equinox.internal.p2.metadata.InstallableUnit; import org.eclipse.equinox.p2.metadata.IInstallableUnit; import org.eclipse.equinox.p2.metadata.IProvidedCapability; import org.eclipse.equinox.p2.metadata.IRequirement; import org.eclipse.equinox.p2.metadata.MetadataFactory; import org.eclipse.equinox.p2.metadata.Version; import org.eclipse.equinox.p2.metadata.VersionRange; -import org.eclipse.equinox.p2.metadata.expression.ExpressionUtil; -import org.eclipse.equinox.p2.metadata.expression.IExpression; -import org.eclipse.equinox.p2.metadata.expression.IMatchExpression; import org.eclipse.equinox.p2.publisher.AbstractPublisherAction; import org.eclipse.equinox.p2.publisher.IPublisherInfo; import org.eclipse.equinox.p2.publisher.IPublisherResult; @@ -92,18 +90,25 @@ public abstract class ActionTest extends AbstractProvisioningTest { verifyRequirement(actual, namespace, name, range, null, 1, 1, true); } - protected void verifyRequirement(Collection actual, String namespace, String name, VersionRange range, String filterStr, int minCard, int maxCard, boolean greedy) { - IRequirement expected = MetadataFactory.createRequirement(namespace, name, range, null, minCard, maxCard, greedy); + protected void verifyRequirement(Collection actual, String namespace, String name, VersionRange range, String envFilter, int minCard, int maxCard, boolean greedy) { + IRequirement expected = MetadataFactory.createRequirement(namespace, name, range, InstallableUnit.parseFilter(envFilter), minCard, maxCard, greedy); verifyRequirement(actual, expected); } - protected void verifyRequirement(Collection actual, String matchExpr, int minCard, int maxCard, boolean greedy) { - IExpression expr = ExpressionUtil.parse(matchExpr); - IMatchExpression matcher = ExpressionUtil.getFactory().matchExpression(expr); - IRequirement expected = MetadataFactory.createRequirement(matcher, null, minCard, maxCard, greedy); + protected void verifyRequirement(Collection actual, String namespace, String propsFilter, String envFilter, int minCard, int maxCard, boolean greedy) { + IRequirement expected = MetadataFactory.createRequirement(namespace, propsFilter, InstallableUnit.parseFilter(envFilter), minCard, maxCard, greedy); verifyRequirement(actual, expected); } + /** + * Safe to use only if actual and expected were created by the same method of {@link MetadataFactory} + * because match expressions are not safe to compare for equality. + * + * This must be guaranteed by all sub-class test cases + * + * @param actual + * @param expected + */ protected void verifyRequirement(Collection actual, IRequirement expected) { for (IRequirement act : actual) { if (expected.getMatches().equals(act.getMatches())) { diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/BundlesActionTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/BundlesActionTest.java index af99bfe42..7e3ab0344 100644 --- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/BundlesActionTest.java +++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/publisher/actions/BundlesActionTest.java @@ -77,18 +77,19 @@ import org.eclipse.equinox.spi.p2.publisher.PublisherHelper; public class BundlesActionTest extends ActionTest { private static final String OSGI = PublisherHelper.OSGI_BUNDLE_CLASSIFIER; private static final String OSGI_IDENTITY = "osgi.identity"; + private static final String OSGI_EE = "osgi.ee"; private static final String JAVA_PACKAGE = "java.package";//$NON-NLS-1$ - private static final String JAVA_EE_1_4_REQ = "providedCapabilities.exists(pc | pc.namespace == 'osgi.ee' && pc.attributes ~= filter('(|(&(osgi.ee=JavaSE)(version=1.4))(&(osgi.ee=CDC/Foundation)(version=1.1)))'))"; - private static final String JAVA_EE_1_6_REQ = "providedCapabilities.exists(pc | pc.namespace == 'osgi.ee' && pc.attributes ~= filter('(&(osgi.ee=JavaSE)(version=1.6))'))"; + private static final String JAVA_EE_1_4 = "(|(&(osgi.ee=JavaSE)(version=1.4))(&(osgi.ee=CDC/Foundation)(version=1.1)))"; + private static final String JAVA_EE_1_6 = "(&(osgi.ee=JavaSE)(version=1.6))"; private static final String TEST1_IUD_NAME = "iud";//$NON-NLS-1$ private static final String TEST1_PROVZ_NAME = "iuz";//$NON-NLS-1$ private static final String TEST1_PROVBUNDLE_NAME = "test1";//$NON-NLS-1$ - private static final String TEST1_REQ_EE_FILTER = JAVA_EE_1_4_REQ; + private static final String TEST1_REQ_EE = JAVA_EE_1_4; private static final String TEST2_REQ_A_NAME = "iua";//$NON-NLS-1$ private static final String TEST2_REQ_B_NAME = "iub";//$NON-NLS-1$ private static final String TEST2_REQ_C_NAME = "iuc";//$NON-NLS-1$ - private static final String TEST2_REQ_EE_FILTER = JAVA_EE_1_4_REQ; + private static final String TEST2_REQ_EE = JAVA_EE_1_4; private static final String TEST2_PROV_Z_NAME = "iuz";//$NON-NLS-1$ private static final String TEST2_PROV_Y_NAME = "iuy";//$NON-NLS-1$ private static final String TEST2_PROV_X_NAME = "iux";//$NON-NLS-1$ @@ -99,9 +100,9 @@ public class BundlesActionTest extends ActionTest { private static final String TEST4_REQ_PACKAGE_OPTGREEDY_NAME = "iuf";//$NON-NLS-1$ private static final String TEST4_REQ_BUNDLE_OPTIONAL_NAME = "iug";//$NON-NLS-1$ private static final String TEST4_REQ_BUNDLE_OPTGREEDY_NAME = "iuh";//$NON-NLS-1$ - private static final String TEST5_REQ_EE_FILTER = JAVA_EE_1_4_REQ; + private static final String TEST5_REQ_EE = JAVA_EE_1_4; private static final String TEST5_PROV_BUNDLE_NAME = "test5";//$NON-NLS-1$ - private static final String TESTDYN_REQ_EE_FILTER = JAVA_EE_1_6_REQ; + private static final String TESTDYN_REQ_EE = JAVA_EE_1_6; private static final File TEST_BASE = new File(TestActivator.getTestDataFolder(), "BundlesActionTest");//$NON-NLS-1$ private static final File TEST_FILE1 = new File(TEST_BASE, TEST1_PROVBUNDLE_NAME); @@ -173,32 +174,32 @@ public class BundlesActionTest extends ActionTest { bundlesAction.perform(info, results, new NullProgressMonitor()); Collection ius = results.getIUs(null, null); - assertEquals("1.0", 1, ius.size()); + assertEquals(1, ius.size()); info = new PublisherInfo(); results = new PublisherResult(); bundlesAction = new BundlesAction(new File[] {foo}); bundlesAction.perform(info, results, new NullProgressMonitor()); ius = results.getIUs(null, null); - assertEquals("2.0", 1, ius.size()); + assertEquals(1, ius.size()); QueryableArray queryableArray = new QueryableArray(ius.toArray(new IInstallableUnit[ius.size()])); IQueryResult result = queryableArray.query(QueryUtil.createIUQuery("foo"), null); - assertEquals("3.1", 1, queryResultSize(result)); + assertEquals(1, queryResultSize(result)); IInstallableUnit iu = result.iterator().next(); TranslationSupport utils = new TranslationSupport(); utils.setTranslationSource(queryableArray); - assertEquals("3.2", "English Foo", utils.getIUProperty(iu, IInstallableUnit.PROP_NAME)); + assertEquals("English Foo", utils.getIUProperty(iu, IInstallableUnit.PROP_NAME)); bundlesAction = new BundlesAction(new File[] {foo_fragment}); bundlesAction.perform(info, results, new NullProgressMonitor()); ius = results.getIUs(null, null); - assertEquals("2.0", 3, ius.size()); + assertEquals(3, ius.size()); queryableArray = new QueryableArray(ius.toArray(new IInstallableUnit[ius.size()])); result = queryableArray.query(QueryUtil.createIUQuery("foo"), null); - assertEquals("2.1", 1, queryResultSize(result)); + assertEquals(1, queryResultSize(result)); iu = result.iterator().next(); utils.setTranslationSource(queryableArray); - assertEquals("2.2", "German Foo", utils.getIUProperty(iu, IInstallableUnit.PROP_NAME, Locale.GERMAN.toString())); + assertEquals("German Foo", utils.getIUProperty(iu, IInstallableUnit.PROP_NAME, Locale.GERMAN.toString())); } private void verifyBundlesAction() throws Exception { @@ -217,7 +218,7 @@ public class BundlesActionTest extends ActionTest { IArtifactDescriptor[] descriptors = artifactRepository.getArtifactDescriptors(key2); // Should have one canonical and one packed - assertTrue("1.0", descriptors.length == 2); + assertEquals(2, descriptors.length); int packedIdx; int canonicalIdx; @@ -244,18 +245,18 @@ public class BundlesActionTest extends ActionTest { } private void verifyBundle1() { - ArrayList ius = new ArrayList<>(publisherResult.getIUs(TEST1_PROVBUNDLE_NAME, IPublisherResult.ROOT)); - assertTrue(ius.size() == 1); + List ius = new ArrayList<>(publisherResult.getIUs(TEST1_PROVBUNDLE_NAME, IPublisherResult.ROOT)); + assertEquals(1, ius.size()); IInstallableUnit bundle1IU = ius.get(0); - assertNotNull("1.0", bundle1IU); - assertEquals("1.1", bundle1IU.getVersion(), BUNDLE1_VERSION); + assertNotNull(bundle1IU); + assertEquals(bundle1IU.getVersion(), BUNDLE1_VERSION); // check required capabilities - Collection requiredCapability = bundle1IU.getRequirements(); - verifyRequirement(requiredCapability, TEST1_IU_D_NAMESPACE, TEST1_IUD_NAME, TEST1_IU_D_VERSION_RANGE); - verifyRequirement(requiredCapability, TEST1_REQ_EE_FILTER, 0, 1, true); - assertEquals("2.0", 2, requiredCapability.size()); + Collection requirements = bundle1IU.getRequirements(); + verifyRequirement(requirements, TEST1_IU_D_NAMESPACE, TEST1_IUD_NAME, TEST1_IU_D_VERSION_RANGE); + verifyRequirement(requirements, OSGI_EE, TEST1_REQ_EE, null, 1, 1, true); + assertEquals(2, requirements.size()); // check provided capabilities Collection providedCapabilities = bundle1IU.getProvidedCapabilities(); @@ -264,7 +265,7 @@ public class BundlesActionTest extends ActionTest { verifyProvidedCapability(providedCapabilities, OSGI, TEST1_PROVBUNDLE_NAME, BUNDLE1_VERSION); verifyProvidedCapability(providedCapabilities, TEST1_PROV_Z_NAMESPACE, TEST1_PROVZ_NAME, TEST2_PROVZ_VERSION); verifyProvidedCapability(providedCapabilities, PublisherHelper.NAMESPACE_ECLIPSE_TYPE, "source", Version.create("1.0.0"));//$NON-NLS-1$//$NON-NLS-2$ - assertEquals("2.1", 5, providedCapabilities.size()); + assertEquals(5, providedCapabilities.size()); Collection data = bundle1IU.getTouchpointData(); boolean found = false; @@ -277,27 +278,27 @@ public class BundlesActionTest extends ActionTest { found = true; } } - assertTrue("3.0", found); + assertTrue(found); } private void verifyBundle2() { - ArrayList ius = new ArrayList<>(publisherResult.getIUs(TEST2_PROV_BUNDLE_NAME, IPublisherResult.ROOT)); - assertTrue(ius.size() == 1); - IInstallableUnit bundle2IU = ius.get(0); + List ius = new ArrayList<>(publisherResult.getIUs(TEST2_PROV_BUNDLE_NAME, IPublisherResult.ROOT)); + assertEquals(1, ius.size()); - assertNotNull(bundle2IU); - assertEquals(bundle2IU.getVersion(), BUNDLE2_VERSION); + IInstallableUnit bundleIu = ius.get(0); + assertNotNull(bundleIu); + assertEquals(bundleIu.getVersion(), BUNDLE2_VERSION); // check required capabilities - Collection requirements = bundle2IU.getRequirements(); + Collection requirements = bundleIu.getRequirements(); verifyRequirement(requirements, TEST2_IU_A_NAMESPACE, TEST2_REQ_A_NAME, TEST2_IU_A_VERSION_RANGE); verifyRequirement(requirements, TEST2_IU_B_NAMESPACE, TEST2_REQ_B_NAME, TEST2_IU_B_VERSION_RANGE); verifyRequirement(requirements, TEST2_IU_C_NAMESPACE, TEST2_REQ_C_NAME, TEST2_IU_C_VERSION_RANGE); - verifyRequirement(requirements, TEST2_REQ_EE_FILTER, 0, 1, true); - assertTrue(requirements.size() == 4 /*number of tested elements*/); + verifyRequirement(requirements, OSGI_EE, TEST2_REQ_EE, null, 1, 1, true); + assertEquals(4, requirements.size()); // check provided capabilities - Collection providedCapabilities = bundle2IU.getProvidedCapabilities(); + Collection providedCapabilities = bundleIu.getProvidedCapabilities(); verifyProvidedCapability(providedCapabilities, PROVBUNDLE_NAMESPACE, TEST2_PROV_BUNDLE_NAME, PROVBUNDLE2_VERSION); verifyProvidedCapability(providedCapabilities, OSGI, TEST2_PROV_BUNDLE_NAME, BUNDLE2_VERSION); verifyProvidedCapability(providedCapabilities, OSGI_IDENTITY, TEST2_PROV_BUNDLE_NAME, BUNDLE2_VERSION); @@ -308,11 +309,11 @@ public class BundlesActionTest extends ActionTest { assertEquals(7, providedCapabilities.size()); /*number of tested elements*/ // check %bundle name is correct - Map prop = bundle2IU.getProperties(); + Map prop = bundleIu.getProperties(); assertTrue(prop.get("org.eclipse.equinox.p2.name").toString().equalsIgnoreCase("%bundleName"));//$NON-NLS-1$//$NON-NLS-2$ assertTrue(prop.get("org.eclipse.equinox.p2.provider").toString().equalsIgnoreCase("%providerName"));//$NON-NLS-1$//$NON-NLS-2$ - Collection data = bundle2IU.getTouchpointData(); + Collection data = bundleIu.getTouchpointData(); boolean found = false; for (ITouchpointData td : data) { ITouchpointInstruction configure = td.getInstruction("configure"); @@ -323,18 +324,17 @@ public class BundlesActionTest extends ActionTest { found = true; } } - assertFalse("3.0", found); - + assertFalse(found); } private void verifyBundle3() { // also a regression test for bug 393051: manifest headers use uncommon (but valid) capitalization ArrayList ius = new ArrayList<>(publisherResult.getIUs(TEST3_PROV_BUNDLE_NAME, IPublisherResult.ROOT)); + assertEquals(1, ius.size()); - assertTrue(ius.size() == 1); - IInstallableUnit bundle3IU = ius.get(0); + IInstallableUnit bundleIu = ius.get(0); - IUpdateDescriptor updateDescriptor = bundle3IU.getUpdateDescriptor(); + IUpdateDescriptor updateDescriptor = bundleIu.getUpdateDescriptor(); String name = RequiredCapability.extractName(updateDescriptor.getIUsBeingUpdated().iterator().next()); VersionRange range = RequiredCapability.extractRange(updateDescriptor.getIUsBeingUpdated().iterator().next()); String description = updateDescriptor.getDescription(); @@ -349,36 +349,31 @@ public class BundlesActionTest extends ActionTest { private void verifyBundle4() { ArrayList ius = new ArrayList<>(publisherResult.getIUs(TEST4_PROV_BUNDLE_NAME, IPublisherResult.ROOT)); - assertTrue(ius.size() == 1); - IInstallableUnit bundle4IU = ius.get(0); + assertEquals(1, ius.size()); - assertNotNull("1.0", bundle4IU); - assertEquals("1.1", bundle4IU.getVersion(), BUNDLE4_VERSION); + IInstallableUnit bundleIu = ius.get(0); + assertNotNull(bundleIu); + assertEquals(bundleIu.getVersion(), BUNDLE4_VERSION); // check required capabilities - Collection requirements = bundle4IU.getRequirements(); + Collection requirements = bundleIu.getRequirements(); verifyRequirement(requirements, JAVA_PACKAGE, TEST4_REQ_PACKAGE_OPTIONAL_NAME, DEFAULT_VERSION_RANGE, null, 0, 1, false); verifyRequirement(requirements, JAVA_PACKAGE, TEST4_REQ_PACKAGE_OPTGREEDY_NAME, DEFAULT_VERSION_RANGE, null, 0, 1, true); verifyRequirement(requirements, OSGI, TEST4_REQ_BUNDLE_OPTIONAL_NAME, DEFAULT_VERSION_RANGE, null, 0, 1, false); verifyRequirement(requirements, OSGI, TEST4_REQ_BUNDLE_OPTGREEDY_NAME, DEFAULT_VERSION_RANGE, null, 0, 1, true); - assertEquals("2.0", 4, requirements.size()); + assertEquals(4, requirements.size()); } private void verifyBundle5() { ArrayList ius = new ArrayList<>(publisherResult.getIUs(TEST5_PROV_BUNDLE_NAME, IPublisherResult.ROOT)); - assertTrue(ius.size() == 1); + assertEquals(1, ius.size()); + IInstallableUnit bundle5IU = ius.get(0); Collection requirements = bundle5IU.getRequirements(); - verifyRequirement(requirements, TEST5_REQ_EE_FILTER, 0, 1, true); - assertTrue(requirements.size() == 2); - IRequirement requirement = requirements.iterator().next(); - - int min = requirement.getMin(); - int max = requirement.getMax(); - - assertTrue(min == 6); - assertTrue(max == 7); + verifyRequirement(requirements, OSGI_EE, TEST5_REQ_EE, null, 1, 1, true); + verifyRequirement(requirements, "bar", "foo", VersionRange.emptyRange, null, 6, 7, true); + assertEquals(2, requirements.size()); } @Override @@ -491,7 +486,7 @@ public class BundlesActionTest extends ActionTest { IInstallableUnit iu = BundlesAction.createBundleIU(BundlesAction.createBundleDescription(testData), null, new PublisherInfo()); Collection requirements = iu.getRequirements(); - verifyRequirement(requirements, TESTDYN_REQ_EE_FILTER, 0, 1, true); + verifyRequirement(requirements, OSGI_EE, TESTDYN_REQ_EE, null, 1, 1, true); assertEquals(1, requirements.size()); } -- cgit v1.2.3