/*****************************************************************************
* 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.
*
* Contributors:
* - Cloudsmith Inc - initial API and implementation.
* - Carsten Reckord, Yatta Solutions GmbH - Synthetic source bundle
*****************************************************************************/
package org.eclipse.buckminster.core.cspec.builder;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.buckminster.core.P2Constants;
import org.eclipse.buckminster.core.RMContext;
import org.eclipse.buckminster.core.TargetPlatform;
import org.eclipse.buckminster.core.common.model.Documentation;
import org.eclipse.buckminster.core.cspec.IAttribute;
import org.eclipse.buckminster.core.cspec.ICSpecData;
import org.eclipse.buckminster.core.cspec.IComponentIdentifier;
import org.eclipse.buckminster.core.cspec.IComponentRequest;
import org.eclipse.buckminster.core.cspec.IGenerator;
import org.eclipse.buckminster.core.cspec.model.AttributeAlreadyDefinedException;
import org.eclipse.buckminster.core.cspec.model.CSpec;
import org.eclipse.buckminster.core.cspec.model.ComponentIdentifier;
import org.eclipse.buckminster.core.cspec.model.ComponentRequest;
import org.eclipse.buckminster.core.cspec.model.GeneratorAlreadyDefinedException;
import org.eclipse.buckminster.core.cspec.model.MissingAttributeException;
import org.eclipse.buckminster.core.cspec.model.MissingDependencyException;
import org.eclipse.buckminster.core.ctype.IComponentType;
import org.eclipse.buckminster.core.helpers.FilterUtils;
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.BuckminsterException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Platform;
import org.eclipse.equinox.internal.p2.metadata.RequiredCapability;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.IRequirement;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.metadata.VersionRange;
import org.eclipse.equinox.p2.metadata.expression.IMatchExpression;
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.metadata.IMetadataRepository;
import org.osgi.framework.InvalidSyntaxException;
/**
* @author Thomas Hallgren
*/
@SuppressWarnings("restriction")
public class CSpecBuilder implements ICSpecData {
private HashMap<String, AttributeBuilder> attributes;
private String componentType;
private List<ComponentRequestBuilder> dependencies;
private Documentation documentation;
private HashMap<IComponentIdentifier, GeneratorBuilder> generators;
private String name;
private URL projectInfo;
private String shortDesc;
private Version version;
private Filter filter;
public CSpecBuilder() {
}
@Deprecated
public CSpecBuilder(IMetadataRepository mdr, IInstallableUnit iu) throws CoreException {
this(RMContext.getGlobalPropertyAdditions(), mdr, iu);
}
public CSpecBuilder(Map<String, ? extends Object> properties, IMetadataRepository mdr, IInstallableUnit iu) throws CoreException {
String id = iu.getId();
boolean isFeature = id.endsWith(P2Constants.FEATURE_GROUP);
if (isFeature) {
id = id.substring(0, id.length() - P2Constants.FEATURE_GROUP.length());
setComponentTypeID(IComponentType.ECLIPSE_FEATURE);
} else
setComponentTypeID(IComponentType.OSGI_BUNDLE);
setName(id);
setVersion(iu.getVersion());
IMatchExpression<IInstallableUnit> filterExpr = iu.getFilter();
if (filterExpr != null) {
// TODO: Rewrite to accept non-osgi type filters
boolean filterOK = false;
Object[] parameters = filterExpr.getParameters();
if (parameters.length == 1) {
Object param = parameters[0];
if (param instanceof org.osgi.framework.Filter) {
try {
Filter flt = FilterFactory.newInstance(param.toString());
flt = FilterUtils.replaceAttributeNames(flt, "osgi", TargetPlatform.TARGET_PREFIX); //$NON-NLS-1$
setFilter(flt);
filterOK = true;
} catch (InvalidSyntaxException e) {
throw BuckminsterException.wrap(e);
}
}
}
if (!filterOK)
throw BuckminsterException.fromMessage("Unable to convert requirement filter %s into an LDAP filter", filterExpr); //$NON-NLS-1$
}
boolean hasBogusFragments = false;
if (isFeature) {
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=213437
Object tmp = properties.get("buckminster.handle.incomplete.platform.features"); //$NON-NLS-1$
if (tmp instanceof String && "true".equalsIgnoreCase((String) tmp)) { //$NON-NLS-1$
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=213437
hasBogusFragments = "org.eclipse.platform".equals(id) //$NON-NLS-1$
|| "org.eclipse.equinox.executable".equals(id) //$NON-NLS-1$
|| "org.eclipse.rcp".equals(id); //$NON-NLS-1$
} else {
// We still need this here due to
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=319345
hasBogusFragments = "org.eclipse.equinox.executable".equals(id); //$NON-NLS-1$
}
}
for (IRequirement cap : iu.getRequirements()) {
// We only bother with direct dependencies to other IU's here
// since package imports etc. are not yet supported
//
IMatchExpression<IInstallableUnit> matches = cap.getMatches();
String namespace = RequiredCapability.extractNamespace(matches);
if (namespace == null)
continue;
id = RequiredCapability.extractName(matches);
if (id == null)
continue;
if (id.endsWith("_root") || id.contains("_root.")) //$NON-NLS-1$ //$NON-NLS-2$
// TODO: Handle binary feature contribution.
continue;
String ctype;
if (IInstallableUnit.NAMESPACE_IU_ID.equals(namespace)) {
if (id.endsWith(P2Constants.FEATURE_GROUP)) {
id = id.substring(0, id.length() - P2Constants.FEATURE_GROUP.length());
ctype = IComponentType.ECLIPSE_FEATURE;
} else if (isFeature)
ctype = IComponentType.OSGI_BUNDLE;
else
continue;
} else if (IComponentType.OSGI_BUNDLE.equals(namespace))
ctype = namespace;
else
// Package or something else that we don't care about here
continue;
filterExpr = cap.getFilter();
String filterStr = null;
if (filterExpr != null) {
// TODO: Rewrite to accept non-osgi type filters
boolean filterOK = false;
Object[] parameters = filterExpr.getParameters();
if (parameters.length == 1) {
Object param = parameters[0];
if (param instanceof org.osgi.framework.Filter) {
filterStr = param.toString();
filterOK = true;
}
}
if (!filterOK)
throw BuckminsterException.fromMessage("Unable to convert requirement filter %s into an LDAP filter", filterExpr); //$NON-NLS-1$
}
if (cap.getMin() == 0) {
if (filterStr == null)
filterStr = ComponentRequest.FILTER_ECLIPSE_P2_OPTIONAL;
else {
filterStr = "(&" + ComponentRequest.FILTER_ECLIPSE_P2_OPTIONAL + filterStr + ')'; //$NON-NLS-1$
}
} else if (hasBogusFragments && ctype == IComponentType.OSGI_BUNDLE && filterStr != null) {
// Don't add unless this requirement can be satisfied within the
// same mdr
IQuery<IInstallableUnit> query = QueryUtil.createMatchQuery(matches);
IQueryResult<IInstallableUnit> result = mdr.query(query, null);
if (result.isEmpty())
continue;
}
ComponentRequestBuilder crb = new ComponentRequestBuilder();
crb.setName(id);
crb.setComponentTypeID(ctype);
crb.setVersionRange(RequiredCapability.extractRange(matches));
if (filterStr != null) {
try {
Filter flt = FilterFactory.newInstance(filterStr);
flt = FilterUtils.replaceAttributeNames(flt, "osgi", TargetPlatform.TARGET_PREFIX); //$NON-NLS-1$
crb.setFilter(flt);
} catch (InvalidSyntaxException e) {
throw BuckminsterException.wrap(e);
}
}
addDependency(crb);
}
addSourceDependency();
}
public ActionBuilder addAction(String actionName, boolean publ, String actorName, boolean always) throws AttributeAlreadyDefinedException {
ActionBuilder bld = createActionBuilder();
bld.setName(actionName);
bld.setPublic(publ);
bld.setActorName(actorName);
bld.setAlways(always);
addAttribute(bld);
return bld;
}
public ArtifactBuilder addArtifact(String n, boolean publ, IPath base) throws AttributeAlreadyDefinedException {
ArtifactBuilder bld = createArtifactBuilder();
bld.setName(n);
bld.setPublic(publ);
bld.setBase(base);
addAttribute(bld);
return bld;
}
public void addAttribute(IAttribute attribute) throws AttributeAlreadyDefinedException {
String attrName = attribute.getName();
if (attributes == null)
attributes = new HashMap<String, AttributeBuilder>();
else if (attributes.containsKey(attrName))
throw new AttributeAlreadyDefinedException(name, attrName);
attributes.put(attrName, attribute.getAttributeBuilder(this));
}
public boolean addDependency(IComponentRequest dependency) throws CoreException {
ComponentRequestBuilder bld;
if (dependency instanceof ComponentRequestBuilder)
bld = (ComponentRequestBuilder) dependency;
else {
bld = createDependencyBuilder();
bld.initFrom(dependency);
}
if (dependencies == null) {
dependencies = new ArrayList<ComponentRequestBuilder>();
dependencies.add(bld);
return true;
}
String depName = dependency.getName();
String depType = dependency.getComponentTypeID();
VersionRange depRange = dependency.getVersionRange();
Filter depFilter = dependency.getFilter();
int idx = dependencies.size();
while (--idx >= 0) {
ComponentRequestBuilder old = dependencies.get(idx);
if (!old.getName().equals(depName))
// Name differ
continue;
String oldType = old.getComponentTypeID();
if (oldType != null && depType != null && !oldType.equals(depType))
// Type differ
continue;
VersionRange oldRange = old.getVersionRange();
if (oldRange != null && depRange != null && oldRange.intersect(depRange) == null)
// No version range intersect
continue;
// Duplicate or merge
boolean change = false;
if (depType == null) {
if (oldType != null) {
depType = oldType;
change = true;
}
}
if (depRange == null) {
if (oldRange != null) {
depRange = oldRange;
change = true;
}
} else if (oldRange != null) {
if (!depRange.equals(oldRange)) {
change = true;
depRange = oldRange.intersect(depRange);
}
}
Filter oldFilter = old.getFilter();
if (depFilter == null) {
if (oldFilter != null) {
depFilter = oldFilter;
change = true;
}
} else if (oldFilter != null) {
if (!depFilter.equals(oldFilter)) {
try {
depFilter = FilterFactory.newInstance("(|" + depFilter + oldFilter + ')'); //$NON-NLS-1$
change = true;
} catch (InvalidSyntaxException e) {
throw BuckminsterException.wrap(e);
}
}
}
if (!change)
// This was a duplicate
return false;
bld.setComponentTypeID(depType);
bld.setFilter(depFilter);
bld.setName(depName);
bld.setVersionRange(depRange);
dependencies.remove(idx);
dependencies.add(bld);
return false;
}
// No duplicate or mergeable entry found. Just add the new entry.
dependencies.add(bld);
return true;
}
public void addGenerator(IGenerator generator) throws GeneratorAlreadyDefinedException {
IComponentIdentifier ci = generator.getGeneratedIdentifier();
if (generators == null)
generators = new HashMap<IComponentIdentifier, GeneratorBuilder>();
else if (generators.containsKey(ci))
throw new GeneratorAlreadyDefinedException(name, ci);
GeneratorBuilder bld = createGeneratorBuilder();
bld.initFrom(generator);
generators.put(ci, bld);
}
public GroupBuilder addGroup(String groupName, boolean publ) throws AttributeAlreadyDefinedException {
GroupBuilder bld = createGroupBuilder();
bld.setName(groupName);
bld.setPublic(publ);
addAttribute(bld);
return bld;
}
public ActionBuilder addInternalAction(String actionName, boolean publ) throws AttributeAlreadyDefinedException {
return addAction(actionName, publ, null, true);
}
public void addSourceDependency() throws CoreException {
if (componentType != null && name != null && version != null && componentType.equals(IComponentType.OSGI_BUNDLE) && !name.endsWith(".source")) { //$NON-NLS-1$
ComponentRequestBuilder srcDep = createDependencyBuilder();
srcDep.setName(name + ".source"); //$NON-NLS-1$
srcDep.setComponentTypeID(IComponentType.OSGI_BUNDLE);
srcDep.setVersionRange(VersionHelper.exactRange(version));
try {
srcDep.setFilter(FilterFactory.newInstance(ComponentRequest.FILTER_OPTIONAL_SOURCE_BUNDLE));
} catch (InvalidSyntaxException e) {
// This won't happen on that particular filter
}
addDependency(srcDep);
}
}
public void clear() {
name = null;
componentType = null;
version = null;
filter = null;
projectInfo = null;
documentation = null;
shortDesc = null;
dependencies = null;
attributes = null;
generators = null;
}
public ActionArtifactBuilder createActionArtifactBuilder() {
return new ActionArtifactBuilder(this);
}
public ActionBuilder createActionBuilder() {
return new ActionBuilder(this);
}
public ArtifactBuilder createArtifactBuilder() {
return new ArtifactBuilder(this);
}
public AttributeBuilder createAttributeBuilder() {
return new AttributeBuilder(this);
}
public CSpec createCSpec() {
return new CSpec(this);
}
public ComponentRequestBuilder createDependencyBuilder() {
return new ComponentRequestBuilder();
}
public GeneratorBuilder createGeneratorBuilder() {
return new GeneratorBuilder(this);
}
public GroupBuilder createGroupBuilder() {
return new GroupBuilder(this);
}
public void finalWrapUp() {
if (attributes != null && dependencies != null) {
for (AttributeBuilder attr : attributes.values()) {
if (attr instanceof GroupBuilder)
((GroupBuilder) attr).finalWrapUp(dependencies);
else if (attr instanceof ActionBuilder)
((ActionBuilder) attr).getPrerequisitesBuilder().finalWrapUp(dependencies);
}
}
}
public ActionBuilder getActionBuilder(String actionName) {
if (attributes != null) {
AttributeBuilder attr = attributes.get(actionName);
if (attr instanceof ActionBuilder)
return (ActionBuilder) attr;
}
return null;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(Class<T> adapterType) {
if (CSpecBuilder.class.isAssignableFrom(adapterType))
return (T) this;
if (CSpec.class.isAssignableFrom(adapterType))
return (T) createCSpec();
return Platform.getAdapterManager().getAdapter(this, adapterType);
}
public ArtifactBuilder getArtifactBuilder(String artifactName) {
AttributeBuilder attr = attributes.get(artifactName);
return attr instanceof ArtifactBuilder ? (ArtifactBuilder) attr : null;
}
@Override
public AttributeBuilder getAttribute(String attrName) {
return attributes == null ? null : attributes.get(attrName);
}
@Override
public Map<String, AttributeBuilder> getAttributes() {
return attributes;
}
@Override
public ComponentIdentifier getComponentIdentifier() {
return new ComponentIdentifier(name, componentType, version);
}
@Override
public String getComponentTypeID() {
return componentType;
}
@Override
public Collection<ComponentRequestBuilder> getDependencies() {
return dependencies == null ? Collections.<ComponentRequestBuilder> emptyList() : dependencies;
}
@SuppressWarnings("deprecation")
@Deprecated
@Override
public ComponentRequestBuilder getDependency(String depName, String depType) throws MissingDependencyException {
return getDependency(depName, depType, null);
}
@Override
public ComponentRequestBuilder getDependency(String depName, String depType, VersionRange depRange) throws MissingDependencyException {
if (dependencies != null) {
int idx = dependencies.size();
while (--idx >= 0) {
ComponentRequestBuilder dependency = dependencies.get(idx);
if (!depName.equals(dependency.getName()))
continue;
if (depType != null && dependency.getComponentTypeID() != null && !depType.equals(dependency.getComponentTypeID()))
continue;
if (depRange != null && dependency.getVersionRange() != null && dependency.getVersionRange().intersect(depRange) == null)
continue;
return dependency;
}
}
return null;
}
public List<ComponentRequestBuilder> getDependencyBuilders() {
return dependencies;
}
@Override
public Documentation getDocumentation() {
return documentation;
}
@Override
public Filter getFilter() {
return filter;
}
@Override
public Collection<GeneratorBuilder> getGeneratorList() {
return generators == null ? Collections.<GeneratorBuilder> emptySet() : generators.values();
}
public GroupBuilder getGroup(String groupName) {
AttributeBuilder attr = attributes.get(groupName);
return attr instanceof GroupBuilder ? (GroupBuilder) attr : null;
}
@Override
public String getName() {
return name;
}
@Override
public URL getProjectInfo() {
return projectInfo;
}
public ActionBuilder getRequiredAction(String actionName) throws MissingAttributeException {
AttributeBuilder attr = attributes.get(actionName);
if (attr instanceof ActionBuilder)
return (ActionBuilder) attr;
throw new MissingAttributeException(name, actionName);
}
public ArtifactBuilder getRequiredArtifact(String artifactName) throws MissingAttributeException {
if (attributes != null) {
AttributeBuilder attr = attributes.get(artifactName);
if (attr instanceof ArtifactBuilder)
return (ArtifactBuilder) attr;
}
throw new MissingAttributeException(name, artifactName);
}
public AttributeBuilder getRequiredAttribute(String attrName) throws MissingAttributeException {
if (attributes != null) {
AttributeBuilder attr = attributes.get(attrName);
if (attr != null)
return attr;
}
throw new MissingAttributeException(name, attrName);
}
public ComponentRequestBuilder getRequiredDependency(IComponentRequest dep) throws MissingDependencyException {
ComponentRequestBuilder dependency = getDependency(dep.getName(), dep.getComponentTypeID(), dep.getVersionRange());
if (dependency == null)
throw new MissingDependencyException(name, dep.toString());
return dependency;
}
/**
* @deprecated Use
* {@link #getRequiredDependency(String, String, VersionRange)}
*/
@Deprecated
public ComponentRequestBuilder getRequiredDependency(String dependencyName, String componentTypeID) throws MissingDependencyException {
ComponentRequestBuilder dependency = getDependency(dependencyName, componentTypeID, null);
if (dependency == null)
throw new MissingDependencyException(name, dependencyName);
return dependency;
}
public GroupBuilder getRequiredGroup(String groupName) throws MissingAttributeException {
AttributeBuilder attr = attributes.get(groupName);
if (attr instanceof GroupBuilder)
return (GroupBuilder) attr;
throw new MissingAttributeException(name, groupName);
}
@Override
public String getShortDesc() {
return shortDesc;
}
public String getTagInfo(String parentInfo) {
return CSpec.getTagInfo(getComponentIdentifier(), projectInfo, parentInfo);
}
@Override
public Version getVersion() {
return version;
}
public void initFrom(ICSpecData cspec) throws CoreException {
name = cspec.getName();
componentType = cspec.getComponentTypeID();
version = cspec.getVersion();
filter = cspec.getFilter();
projectInfo = cspec.getProjectInfo();
documentation = cspec.getDocumentation();
shortDesc = cspec.getShortDesc();
Map<String, ? extends IAttribute> attrs = cspec.getAttributes();
if (attrs.size() > 0) {
attributes = new HashMap<String, AttributeBuilder>(attrs.size());
for (IAttribute attr : attrs.values())
attributes.put(attr.getName(), attr.getAttributeBuilder(this));
} else
attributes = null;
Collection<? extends IComponentRequest> deps = cspec.getDependencies();
if (deps.size() > 0) {
dependencies = new ArrayList<ComponentRequestBuilder>(deps.size());
for (IComponentRequest dep : deps)
addDependency(dep);
} else
dependencies = null;
Collection<? extends IGenerator> gens = cspec.getGeneratorList();
if (gens.size() > 0) {
generators = new HashMap<IComponentIdentifier, GeneratorBuilder>(gens.size());
for (IGenerator gen : gens) {
GeneratorBuilder gb = createGeneratorBuilder();
gb.initFrom(gen);
generators.put(gen.getGeneratedIdentifier(), gb);
}
} else
generators = null;
}
public void removeAttribute(String attributeName) {
if (attributes != null)
attributes.remove(attributeName);
}
public void removeDependency(String dependencyName) {
if (dependencies != null)
dependencies.remove(dependencyName);
}
public void removeGenerator(String generatorName) {
if (generators != null)
generators.remove(generatorName);
}
public void setComponentTypeID(String componentType) {
this.componentType = componentType;
}
public void setDocumentation(Documentation documentation) {
this.documentation = documentation;
}
public void setFilter(Filter filter) {
this.filter = filter;
}
public void setName(String name) {
this.name = name;
}
public void setProjectInfo(URL projectInfo) {
this.projectInfo = projectInfo;
}
public void setShortDesc(String shortDesc) {
this.shortDesc = shortDesc;
}
public void setVersion(Version version) {
this.version = version;
}
}