/******************************************************************************* * Copyright (c) 2004, 2006 * Thomas Hallgren, Kenneth Olwing, Mitch Sonies * Pontus Rydin, Nils Unden, Peer Torngren * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the individual * copyright holders listed above, as Initial Contributors under such license. * The text of such license is available at www.eclipse.org. *******************************************************************************/ package org.eclipse.buckminster.core.resolver; import java.net.URL; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.buckminster.core.CorePlugin; import org.eclipse.buckminster.core.Messages; import org.eclipse.buckminster.core.RMContext; import org.eclipse.buckminster.core.common.model.ExpandingProperties; import org.eclipse.buckminster.core.cspec.IAttribute; import org.eclipse.buckminster.core.cspec.IComponentRequest; import org.eclipse.buckminster.core.cspec.QualifiedDependency; import org.eclipse.buckminster.core.cspec.model.CSpec; import org.eclipse.buckminster.core.cspec.model.ComponentRequest; import org.eclipse.buckminster.core.ctype.IComponentType; import org.eclipse.buckminster.core.query.IAdvisorNode; import org.eclipse.buckminster.core.query.IComponentQuery; import org.eclipse.buckminster.core.query.model.ComponentQuery; import org.eclipse.buckminster.core.rmap.model.ProviderScore; import org.eclipse.buckminster.core.version.VersionHelper; import org.eclipse.buckminster.core.version.VersionMatch; import org.eclipse.buckminster.core.version.VersionSelector; import org.eclipse.buckminster.core.version.VersionType; import org.eclipse.core.runtime.CoreException; import org.eclipse.equinox.p2.metadata.Version; import org.eclipse.equinox.p2.metadata.VersionRange; import org.eclipse.osgi.util.NLS; /** * The <code>NodeQuery</code> combines the {@link IComponentQuery} with one * specific {@link IComponentRequest} during recursive resolution of a * dependency tree. * * @author Thomas Hallgren */ public class NodeQuery implements Comparator<VersionMatch>, IResolverBackchannel { private final RMContext context; private final Map<String, ? extends Object> properties; private final QualifiedDependency qDep; private transient IComponentType componentType; public NodeQuery(NodeQuery query, Map<String, ? extends Object> additionalProperties) { this(query, additionalProperties, true); } public NodeQuery(NodeQuery query, Map<String, ? extends Object> additionalProperties, boolean additionalPrioritized) { context = query.getContext(); qDep = query.getQualifiedDependency(); Map<String, ? extends Object> qprops = query.getProperties(); if (additionalProperties.isEmpty()) properties = qprops; else { ExpandingProperties<Object> propUnion = new ExpandingProperties<Object>(qprops.size() + additionalProperties.size()); if (additionalPrioritized) { propUnion.putAll(qprops, true); propUnion.putAll(additionalProperties); } else { propUnion.putAll(additionalProperties, true); propUnion.putAll(qprops); } properties = propUnion; } } public NodeQuery(RMContext context, ComponentRequest request, Set<String> attributes) { this(context, new QualifiedDependency(request, attributes)); } public NodeQuery(RMContext context, QualifiedDependency qDep) { this.context = context; this.qDep = qDep; this.properties = context.getProperties(qDep.getRequest()); } /** * Merges the version designator and the attributes of the new dependency * with the current one. The method will return this instance if the merge * is a no-op. * * @param newQDep * the new qualified depenency * @return This instance or a new instance if modifications where necessary. * @throws CoreException * if the qualification is in conflict with the previously * defined dependency with respect to its version designator */ public NodeQuery addDependencyQualification(QualifiedDependency newQDep) throws CoreException { QualifiedDependency mqDep = qDep.mergeDependency(newQDep); return qDep == mqDep ? this : context.getNodeQuery(mqDep); } /** * Checks if the advice for this node indicates that a circular dependency * is allowed. * * @return <code>true</code> only if an advice indicates that a circular * dependency is allowed. */ public boolean allowCircularDependency() throws CoreException { return getComponentQuery().allowCircularDependency(getComponentRequest(), context); } @Override public int compare(VersionMatch vm1, VersionMatch vm2) { if (vm1 == vm2) return 0; int cmp = 0; // Compare the revision. If it is present, all revisions higher // than it are invalid // // If only one match matches the revision, it will take precedence // regardless of everything else. // String revision = getRevision(); if (vm1.satisfiesRevision(revision)) { if (!vm2.satisfiesRevision(revision)) cmp = -1; } else if (vm2.satisfiesRevision(revision)) cmp = 1; else // Both revisions are invalid. No use continuing the // comparison // return 0; if (cmp != 0) return cmp; // Compare the timestamp. If it is present, all timestamps younger // than it are invalid // // If only one match matches the timestamp, it will take precedence // regardless of everything else. // Date timestamp = getTimestamp(); Date vm1Ts = vm1.getTimestamp(); Date vm2Ts = vm2.getTimestamp(); if (timestamp != null) { if (vm1Ts != null && timestamp.compareTo(vm1Ts) >= 0) { if (vm2Ts == null || timestamp.compareTo(vm2Ts) < 0) cmp = 1; // vm1 is greater since vm2 is invalid // Both revisions are valid so the revision doesn't // rule anything out. We compare the revisions further // down. } else { if (vm2Ts != null && timestamp.compareTo(vm2Ts) >= 0) cmp = -1; // vm2 is greater since vm1 is invalid else // Both timestamps are invalid. No use continuing the // comparison // return 0; } } if (cmp != 0) return cmp; int[] prio = getResolutionPrio(); for (int idx = 0; idx < prio.length; ++idx) { switch (prio[idx]) { case IAdvisorNode.PRIO_BRANCHTAG_PATH_INDEX: cmp = compareSelectors(vm1, vm2); break; default: cmp = compareVersions(vm1, vm2); } if (cmp != 0) return cmp; } String vm1Str = vm1.getRevision(); String vm2Str = vm2.getRevision(); if (vm1Str != null && vm2Str != null && !vm1Str.equals(vm2Str)) { // Not same revision. The higher revision wins if they are numeric // try { return Long.parseLong(vm1Str) < Long.parseLong(vm2Str) ? -1 : 1; } catch (NumberFormatException e) { // } } if (vm1Ts != null && vm2Ts != null) cmp = vm1Ts.compareTo(vm2Ts); return cmp; } /** * Returns the attributes designated by this query. * * @param cspec * The cspec containing the needed attributes. * @return An array of attributes, possibly empty but never * <code>null</code>. * @throws CoreException * when this query declares an attribute that cannot be found in * <code>cspec</code>. */ public IAttribute[] getAttributes(CSpec cspec) throws CoreException { return cspec.getAttributes(getRequiredAttributes()); } /** * Returns the path consisting of branches and/or tags * * @return A path that might be empty but never <code>null</code>. */ public VersionSelector[] getBranchTagPath() { return getComponentQuery().getBranchTagPath(getComponentRequest(), context); } public final ComponentQuery getComponentQuery() { return context.getComponentQuery(); } public final ComponentRequest getComponentRequest() { return qDep.getRequest(); } public synchronized IComponentType getComponentType() { if (componentType == null) { try { componentType = getComponentRequest().getComponentType(); } catch (CoreException e) { throw new IllegalStateException(Messages.Unable_to_obtain_component_type, e); } } return componentType; } public RMContext getContext() { return context; } public long getNumericRevision() { String revision = getRevision(); return revision == null ? -1 : Long.parseLong(revision); } public final URL getOverlayFolder() { return getComponentQuery().getOverlayFolder(getComponentRequest(), context); } /** * Returns properties to use in the resolvement process. * * @param request * The component request. * @return the properties that the resolver should use. */ public Map<String, ? extends Object> getProperties() { return properties; } public Object getProperty(String mapName) { return getProperties().get(mapName); } /** * When the resolver finds a provider, that provider will state that it can * produce mutable or immutable artifacts and that those artifacts are in * source or binary form. The resolver will use this method to find out how * good the provider is based on those facts. * * @param mutable * <code>true</code> if the provider will provide a mutable * artifact. * @param source * <code>true</code> if the provider will provide source. * @return A score that the resolver will use when it compares the provider * to other providers. */ public ProviderScore getProviderScore(boolean mutable, boolean source) { return getComponentQuery().getProviderScore(getComponentRequest(), mutable, source, context); } /** * Returns the qualified dependency (i.e. the request plus required * attributes). * * @return The qualified dependency. */ public QualifiedDependency getQualifiedDependency() { return qDep; } /** * Checks if there is a matching advice that declares specific actions * * @return The names of the adviced actions or an empty array. */ public List<String> getRequiredAttributes() { return getComponentQuery().getAttributes(getComponentRequest(), context); } public ResolutionContext getResolutionContext() { if (context instanceof ResolutionContext) return (ResolutionContext) context; throw new IllegalStateException(Messages.ResolutionContext_requested_during_Materialization); } public int[] getResolutionPrio() { return getComponentQuery().getResolutionPrio(getComponentRequest(), context); } /** * Returns the revision number or -1 if not applicable * * @return The revision number to search for */ public String getRevision() { return getComponentQuery().getRevision(getComponentRequest(), context); } /** * Returns the timestamp to search for or <code>null</code> if not * applicable * * @return The timestamp to search for */ public Date getTimestamp() { return getComponentQuery().getTimestamp(getComponentRequest(), context); } /** * Returns the, possibly overriden, version designator of the component * request. * * @return A version selector or <code>null</code>. */ public VersionRange getVersionRange() { ComponentRequest request = getComponentRequest(); VersionRange vds = getComponentQuery().getVersionOverride(request, context); if (vds == null) vds = request.getVersionRange(); if (vds == null) return vds; IComponentType ctype = getComponentType(); if (ctype == null && VersionHelper.getVersionType(vds.getMinimum()).equals(VersionType.TRIPLET)) { try { ctype = CorePlugin.getDefault().getComponentType("maven"); //$NON-NLS-1$ } catch (CoreException e) { return vds; } } return ctype == null ? vds : ctype.getTypeSpecificDesignator(vds); } /** * Returns true if the given version will match this query with respect to * the current version designator, branchTag path, and space path. * * @param version * The version to match or <code>null</code> if not applicable * @param branchOrTag * The branch or tag to match or <code>null</code> if not * applicable * @param space * The space to match or <code>null</code> if not applicable * @param spacePathResolver * the space path resolver to use when expanding the space path. * @return true if the given values matches this query */ public boolean isMatch(Version version, VersionSelector branchOrTag) { if (!isMatch(branchOrTag)) return false; VersionRange versionRange = getVersionRange(); if (versionRange != null && !versionRange.isIncluded(version)) { logDecision(ResolverDecisionType.VERSION_REJECTED, version, NLS.bind(Messages.Not_designated_by_0, versionRange)); return false; } return true; } /** * Returns true if the given <code>versionMatch</code> will match this query * with respect to the current version designator, branchTag path, and space * path. * * @param versionMatch * The version match to match * @param spacePathResolver * the space path resolver to use when expanding the space path. * @return true if the given values matches this query */ public boolean isMatch(VersionMatch versionMatch) { if (versionMatch == null) versionMatch = VersionMatch.DEFAULT; return isMatch(versionMatch.getVersion(), versionMatch.getBranchOrTag()); } /** * Returns true if the given version will match this query with respect to * the current version designator, branchTag path, and space path. * * @param version * The version to match or <code>null</code> if not applicable * @param branchOrTag * The branch or tag to match or <code>null</code> if not * applicable * @param space * The space to match or <code>null</code> if not applicable * @param spacePathResolver * the space path resolver to use when expanding the space path. * @return true if the given values matches this query */ public boolean isMatch(VersionSelector branchOrTag) { VersionSelector[] branchTagPath = getBranchTagPath(); if (branchTagPath.length > 0) { if (branchOrTag == null) branchOrTag = VersionSelector.branch(VersionSelector.DEFAULT_BRANCH); if (VersionSelector.indexOf(branchTagPath, branchOrTag) < 0) { logDecision(branchOrTag == null || branchOrTag.getType() == VersionSelector.BRANCH ? ResolverDecisionType.BRANCH_REJECTED : ResolverDecisionType.TAG_REJECTED, branchOrTag, NLS.bind(Messages.Not_in_path_0, VersionSelector.toString(branchTagPath))); return false; } } return true; } /** * Checks if there is an advice to prune the external dependencies from the * requested component. Pruning means that all dependencies that has no * declared purposes will be omitted. The default is to not prune the * dependencies. * * @return <code>true</code> if the external dependencies of the requested * component should be pruned. */ public boolean isPrune() { return getComponentQuery().isPrune(getComponentRequest(), context); } @Override public ResolverDecision logDecision(ComponentRequest request, ResolverDecisionType decisionType, Object... args) { return getResolutionContext().logDecision(request, decisionType, args); } @Override public ResolverDecision logDecision(ResolverDecisionType decisionType, Object... args) { return getResolutionContext().logDecision(getComponentRequest(), decisionType, args); } /** * Checks if there is an advice to skip the requested component and not * include it in the resolvment. The default is to include the component. * * @return <code>true</code> if the requested component should be skipped. */ public boolean skipComponent() { return getComponentQuery().skipComponent(getComponentRequest(), context); } @Override public String toString() { return "Query for: " + qDep; //$NON-NLS-1$ } /** * When the resolver finds a materialized component that is not bound to the * workspace it will call this method to decide wether to use that * materialization or if it should be overwritten. * * @return <code>true</code> if an existing materialization can be used. */ public boolean useMaterialization() { return getComponentQuery().useMaterialization(getComponentRequest(), context); } /** * Use remote resolution such as an rmap or a service. * * @return <code>true</code> if remote resolution can be used. */ public boolean useResolutionService() { return getComponentQuery().useResolutionService(getComponentRequest(), context); } /** * When the resolver finds an installed feature, plugin, or fragment that * represents the component it tries to resolve, it will ask if it can be * used to fulfill the request. * * @return <code>true</code> if installed feature or plugin can be used. */ public boolean useTargetPlatform() { return getComponentQuery().useTargetPlatform(getComponentRequest(), context); } /** * When the resolver finds a project that represents the component it tries * to resolve, it will ask if it can be used to fulfill the request. * * @param props * The properties that contains the component request. * @return <code>true</code> if an existing project can be used. */ public boolean useWorkspace() { return getComponentQuery().useWorkspace(getComponentRequest(), context); } private int compareSelectors(VersionMatch vm1, VersionMatch vm2) { int cmp = 0; VersionSelector[] branchTagPath = getBranchTagPath(); if (branchTagPath.length > 0) { // The match with the lower index is considered greater. A match // with no index (-1) will always loose // int v1idx = VersionSelector.indexOf(branchTagPath, vm1.getBranchOrTag()); int v2idx = VersionSelector.indexOf(branchTagPath, vm2.getBranchOrTag()); if (v1idx >= 0) { if (v2idx >= 0) cmp = (v1idx < v2idx) ? 1 : ((v1idx == v2idx) ? 0 : -1); else cmp = 1; } else { if (v2idx >= 0) cmp = -1; } } return cmp; } private int compareVersions(VersionMatch vm1, VersionMatch vm2) { // Compare the versions. // Version v1 = vm1.getVersion(); Version v2 = vm2.getVersion(); VersionRange vd = getVersionRange(); if (vd != null) { // Only consider designated versions // if (v1 != null && !vd.isIncluded(v1)) v1 = null; if (v2 != null && !vd.isIncluded(v2)) v2 = null; } int cmp = 0; if (v1 == null) { if (v2 != null) cmp = -1; // Consider v1 to be less } else { if (v2 == null) cmp = 1; // Consider v2 to be less else cmp = v1.compareTo(v2); } return cmp; } }