/*****************************************************************************
* Copyright (c) 2006-2013, Cloudsmith Inc.
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the copyright holder
* listed above, as the Initial Contributor under such license. The text of
* such license is available at www.eclipse.org.
*****************************************************************************/
package org.eclipse.buckminster.core.cspec.model;
import java.util.Map;
import org.eclipse.buckminster.core.KeyConstants;
import org.eclipse.buckminster.core.cspec.IComponentIdentifier;
import org.eclipse.buckminster.core.cspec.IComponentRequest;
import org.eclipse.buckminster.core.cspec.builder.ComponentRequestBuilder;
import org.eclipse.buckminster.core.version.VersionHelper;
import org.eclipse.buckminster.osgi.filter.Filter;
import org.eclipse.buckminster.osgi.filter.FilterFactory;
import org.eclipse.buckminster.runtime.Trivial;
import org.eclipse.buckminster.sax.Utils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.equinox.p2.metadata.VersionRange;
import org.osgi.framework.InvalidSyntaxException;
import org.xml.sax.helpers.AttributesImpl;
/**
* A ComponentRequest is part of a requirement. All referenced components must
* be available in a workspace for a requirment to be fulfilled. A component can
* be further qualified using target references in cases when only a part of the
* component is needed. The ComponentRequest uses a singleton pattern and is
* optimized for use as key in a Map or Set.
*
* @author thhal
*/
public class ComponentRequest extends ComponentName implements IComponentRequest {
@SuppressWarnings("hiding")
public static final String TAG = "component"; //$NON-NLS-1$
static public final String ATTR_VERSION_DESIGNATOR = "versionDesignator"; //$NON-NLS-1$
static public final String ATTR_VERSION_TYPE = "versionType"; //$NON-NLS-1$
public static final String ATTR_FILTER = "filter"; //$NON-NLS-1$
private final VersionRange versionRange;
private final Filter filter;
public static final Filter P2_OPTIONAL_FILTER;
public static final Filter SOURCE_BUNDLE_FILTER;
public static final String FILTER_ECLIPSE_P2_OPTIONAL = "(!(eclipse.p2.optional=false))"; //$NON-NLS-1$
public static final String FILTER_SOURCE_BUNDLE = "(buckminster.download.source=true)"; //$NON-NLS-1$
public static final String FILTER_OPTIONAL_SOURCE_BUNDLE = "(&" + ComponentRequest.FILTER_ECLIPSE_P2_OPTIONAL + FILTER_SOURCE_BUNDLE + ')'; //$NON-NLS-1$
static {
try {
P2_OPTIONAL_FILTER = FilterFactory.newInstance(ComponentRequest.FILTER_ECLIPSE_P2_OPTIONAL);
SOURCE_BUNDLE_FILTER = FilterFactory.newInstance(ComponentRequest.FILTER_SOURCE_BUNDLE);
} catch (InvalidSyntaxException e) {
throw new ExceptionInInitializerError(e);
}
}
public ComponentRequest(ComponentRequestBuilder bld) {
super(bld.getName(), bld.getComponentTypeID());
VersionRange vr = bld.getVersionRange();
if (vr != null && vr.equals(VersionRange.emptyRange))
vr = null;
versionRange = vr;
filter = bld.getFilter();
}
public ComponentRequest(String name, String componentType, String versionRangeStr, String versionTypeId) throws CoreException {
this(name, componentType, versionRangeStr, versionTypeId, null);
}
public ComponentRequest(String name, String componentType, String versionRangeStr, String versionTypeId, Filter filter) throws CoreException {
this(name, componentType, VersionHelper.createRange(versionTypeId, versionRangeStr), filter);
}
public ComponentRequest(String name, String componentType, VersionRange versionRange) {
this(name, componentType, versionRange, null);
}
public ComponentRequest(String name, String componentType, VersionRange versionRange, Filter filter) {
super(name, componentType);
if (versionRange != null && versionRange.equals(VersionRange.emptyRange))
versionRange = null;
this.versionRange = versionRange;
this.filter = filter;
}
public void appendViewName(StringBuilder bld) {
bld.append(getName());
String componentType = getComponentTypeID();
if (componentType != null) {
bld.append(':');
bld.append(componentType);
}
if (filter != null)
bld.append(filter);
}
@Override
public boolean designates(IComponentIdentifier id) {
return Trivial.equalsAllowNull(getName(), id.getName())
&& (getComponentTypeID() == null || getComponentTypeID().equals(id.getComponentTypeID()))
&& (versionRange == null || versionRange.isIncluded(id.getVersion()));
}
/**
* Returns true if this component reference is equal to obj with respect to
* name, versionSelector, and match rule.
*/
@Override
public boolean equals(Object o) {
if (o == this)
return true;
return super.equals(o) && Trivial.equalsAllowNull(versionRange, ((ComponentRequest) o).versionRange)
&& Trivial.equalsAllowNull(filter, ((ComponentRequest) o).filter);
}
@Override
public String getDefaultTag() {
return TAG;
}
@Override
public Filter getFilter() {
return filter;
}
@Override
public Map<String, String> getProperties() {
Map<String, String> p = super.getProperties();
if (versionRange != null)
p.put(KeyConstants.VERSION_DESIGNATOR, versionRange.toString());
return p;
}
@Override
public VersionRange getVersionRange() {
return versionRange;
}
public String getViewName() {
StringBuilder bld = new StringBuilder();
appendViewName(bld);
return bld.toString();
}
/**
* Returns the hashCode for this component request.
*/
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 31 * hash + (versionRange == null ? 0 : versionRange.hashCode());
return 31 * hash + (filter == null ? 0 : filter.hashCode());
}
public boolean isEnabled(Map<String, ? extends Object> properties) {
return filter == null || filter.matchCase(properties);
}
public boolean isOptional() {
return filter != null && filter.toString().contains(FILTER_ECLIPSE_P2_OPTIONAL);
}
public boolean isSynthetic() {
return filter != null && filter.toString().contains(FILTER_ECLIPSE_P2_OPTIONAL);
}
public boolean isSyntheticSource() {
return filter != null && filter.toString().contains(FILTER_SOURCE_BUNDLE);
}
public ComponentRequest mergeDesignator(ComponentRequest that) throws CoreException {
if (!Trivial.equalsAllowNull(getName(), that.getName()))
throw new ComponentRequestConflictException(this, that);
int cmp = 0;
final VersionRange thisVD = getVersionRange();
final VersionRange thatVD = that.getVersionRange();
VersionRange mergedVD = null;
if (thisVD == null) {
if (thatVD != null) {
cmp = 1; // limited by that
mergedVD = thatVD;
}
} else {
if (thatVD == null) {
cmp = -1; // limited by this
mergedVD = thisVD;
} else {
mergedVD = thisVD.intersect(thatVD);
if (mergedVD == null)
throw new ComponentRequestConflictException(this, that);
if (mergedVD.equals(thisVD)) {
if (!mergedVD.equals(thatVD))
cmp = -1; // limited by this
} else {
if (mergedVD.equals(thatVD))
cmp = 1; // limited by that
else
cmp = 2; // Limited by both
}
}
}
final String thisCType = getComponentTypeID();
final String thatCType = that.getComponentTypeID();
String mergedCType = null;
if (thisCType == null) {
if (thatCType != null) {
if (cmp == 0)
cmp = 1;
mergedCType = thatCType;
}
} else {
if (thatCType != null) {
if (!thisCType.equals(thatCType))
throw new ComponentRequestConflictException(this, that);
} else {
if (cmp == 0)
cmp = -1;
}
mergedCType = thisCType;
}
final Filter thisFilter = getFilter();
final Filter thatFilter = that.getFilter();
boolean thisOptional = isOptional();
boolean thatOptional = that.isOptional();
Filter mergedFilter = null;
if (!Trivial.equalsAllowNull(thisFilter, thatFilter)) {
if (thisOptional != thatOptional)
// Filters can only be merged if both are required or
// both are optional or if the version ranges are
// exactly the same.
//
throw new ComponentRequestConflictException(this, that);
if (thisFilter == null)
mergedFilter = thatFilter;
else if (thatFilter == null)
mergedFilter = thisFilter;
else
mergedFilter = thisFilter.addFilterWithOr(thatFilter);
} else
mergedFilter = thisFilter;
// Never allow an optional request to qualify one that is not
// optional. The opposite is OK though.
//
if (thisOptional) {
if (!thatOptional) {
if (cmp == -1 || cmp == 2)
// Qualified by this
throw new ComponentRequestConflictException(this, that);
return that;
}
} else if (thatOptional) {
if (cmp > 0)
// Qualified by that
throw new ComponentRequestConflictException(this, that);
return this;
}
if (Trivial.equalsAllowNull(mergedVD, thisVD) && Trivial.equalsAllowNull(mergedCType, thisCType)
&& Trivial.equalsAllowNull(thisFilter, mergedFilter))
return this;
if (Trivial.equalsAllowNull(mergedVD, thatVD) && Trivial.equalsAllowNull(mergedCType, thatCType)
&& Trivial.equalsAllowNull(thatFilter, mergedFilter))
return that;
return new ComponentRequest(getName(), mergedCType, mergedVD, mergedFilter);
}
@Override
public ComponentName toPureComponentName() {
return new ComponentName(this);
}
@Override
public void toString(StringBuilder bld) {
super.toString(bld);
if (versionRange != null) {
bld.append('/');
bld.append(VersionHelper.getHumanReadable(versionRange));
}
if (filter != null)
bld.append(filter);
}
@Override
protected void addAttributes(AttributesImpl attrs) {
super.addAttributes(attrs);
if (versionRange != null)
Utils.addAttribute(attrs, ATTR_VERSION_DESIGNATOR, versionRange.toString());
if (filter != null)
Utils.addAttribute(attrs, ATTR_FILTER, filter.toString());
}
}