/*****************************************************************************
* 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.metadata.model;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.eclipse.buckminster.core.Messages;
import org.eclipse.buckminster.core.RMContext;
import org.eclipse.buckminster.core.cspec.IComponentIdentifier;
import org.eclipse.buckminster.core.cspec.QualifiedDependency;
import org.eclipse.buckminster.core.cspec.model.ComponentIdentifier;
import org.eclipse.buckminster.core.cspec.model.ComponentRequest;
import org.eclipse.buckminster.core.helpers.DateAndTimeUtils;
import org.eclipse.buckminster.core.metadata.MissingComponentException;
import org.eclipse.buckminster.core.mspec.builder.MaterializationSpecBuilder;
import org.eclipse.buckminster.core.mspec.model.MaterializationSpec;
import org.eclipse.buckminster.core.query.model.ComponentQuery;
import org.eclipse.buckminster.core.resolver.IResolver;
import org.eclipse.buckminster.core.resolver.MainResolver;
import org.eclipse.buckminster.core.resolver.ResolutionContext;
import org.eclipse.buckminster.core.rmap.model.Provider;
import org.eclipse.buckminster.sax.UUIDKeyed;
import org.eclipse.buckminster.sax.Utils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
/**
* The resolution graph
*
* @author Thomas Hallgren
*/
public class BillOfMaterials extends BOMNode {
public static final String ATTR_QUERY_ID = "componentQueryId"; //$NON-NLS-1$
public static final String ATTR_TIMESTAMP = "timestamp"; //$NON-NLS-1$
public static final String ATTR_TOP_NODE_ID = "topNodeId"; //$NON-NLS-1$
@SuppressWarnings("hiding")
public static final int SEQUENCE_NUMBER = 3;
@SuppressWarnings("hiding")
public static final String TAG = "billOfMaterials"; //$NON-NLS-1$
public static BillOfMaterials create(BOMNode topNode, ComponentQuery query) throws CoreException {
return create(topNode, query, null);
}
public static BillOfMaterials create(BOMNode topNode, ComponentQuery query, Date timestamp) throws CoreException {
if (topNode == null)
throw new IllegalArgumentException(Messages.Top_node_cannot_be_null);
if (query == null)
throw new IllegalArgumentException(Messages.Component_query_cannot_be_null);
if (topNode instanceof BillOfMaterials) {
BillOfMaterials bom = (BillOfMaterials) topNode;
if (bom.getQuery().equals(query))
return bom;
}
return new BillOfMaterials(topNode, query, timestamp);
}
private static void addIfNotAdded(UUIDKeyed object, Set<UUID> unique, List<IDWrapper> wrappers) {
UUID key = object.getId();
if (unique.contains(key))
return;
unique.add(key);
wrappers.add(new IDWrapper(key, object));
}
private static void buildNodeMap(BOMNode node, HashMap<ComponentIdentifier, BOMNode> map) throws CoreException {
Resolution resolution = node.getResolution();
if (resolution == null)
return;
ComponentIdentifier id = resolution.getCSpec().getComponentIdentifier();
if (map.containsKey(id))
//
// This node is already added
//
return;
map.put(id, node);
for (BOMNode child : node.getChildren())
buildNodeMap(child, map);
}
private static void collectNodeContents(BOMNode node, Set<UUID> unique, List<IDWrapper> wrappers) throws CoreException {
UUID nodeId = node.getId();
if (unique.contains(nodeId))
//
// This node is already added
//
return;
// Add the ResolvedNode to the contents
//
unique.add(nodeId);
if (node instanceof BillOfMaterials) {
BillOfMaterials bom = (BillOfMaterials) node;
wrappers.add(new IDWrapper(bom.getQueryId(), bom.getQuery()));
collectNodeContents(bom.getTopNode(), unique, wrappers);
} else {
Resolution resolution = node.getResolution();
if (resolution != null) {
// Add the Resolution to the contents
//
addIfNotAdded(resolution.getProvider(), unique, wrappers);
addIfNotAdded(resolution.getCSpec(), unique, wrappers);
addIfNotAdded(resolution, unique, wrappers);
// Recursively add all children of this ResolvedNode. It's
// very important that we do this depth first since the leafs
// must end up first in the list of IDWrappers.
//
for (BOMNode child : node.getChildren())
collectNodeContents(child, unique, wrappers);
}
}
wrappers.add(new IDWrapper(nodeId, node));
}
// Private cache that speeds up the identifier to resolved node map. A cache
// here is quite safe since both its scope and everything it contains is
// immutable
//
private transient HashMap<ComponentIdentifier, BOMNode> nodeMap;
private final ComponentQuery query;
private final Date timestamp;
private final BOMNode topNode;
public BillOfMaterials(BOMNode topNode, ComponentQuery query, Date timestamp) {
super();
this.topNode = topNode;
this.query = query;
this.timestamp = (timestamp == null) ? new Date() : timestamp;
}
public void addMaterializationNodes(MaterializationSpecBuilder bld) throws CoreException {
for (Resolution res : findAll(null)) {
if (!res.isMaterializable())
continue;
res.getProvider().getReaderType().addMaterializationNode(bld, res);
}
}
/**
* Compares the two instances for equality without taking the creation date
* into account.
*
* @param other
* The instance that this instance is compared to
* @return true if the contents of the two instances is equal, not counting
* the creation date
*/
public boolean contentEqual(BillOfMaterials other) {
return other != null && topNode.equals(other.topNode);
}
@Override
public List<Resolution> findAll(Set<Resolution> skipThese) throws CoreException {
return getTopNode().findAll(skipThese);
}
public List<Resolution> findMaterializationCandidates(RMContext context, MaterializationSpec mspec) throws CoreException {
List<Resolution> minfos = new ArrayList<Resolution>();
addMaterializationCandidates(context, minfos, getQuery(), mspec, new HashSet<Resolution>());
return minfos;
}
public BillOfMaterials fullyResolve(IProgressMonitor monitor) throws CoreException {
return fullyResolve(new MainResolver(new ResolutionContext(getQuery())), monitor);
}
public BillOfMaterials fullyResolve(IResolver resolver, IProgressMonitor monitor) throws CoreException {
return resolver.resolveRemaining(this, monitor);
}
@Override
public List<BOMNode> getChildren() {
return getTopNode().getChildren();
}
@Override
public String getDefaultTag() {
return TAG;
}
@Override
public QualifiedDependency getQualifiedDependency() {
return getTopNode().getQualifiedDependency();
}
@Override
public synchronized ComponentQuery getQuery() {
return query;
}
@Override
public UUID getQueryId() {
return query.getId();
}
@Override
public ComponentRequest getRequest() {
return getTopNode().getRequest();
}
@Override
public Resolution getResolution() {
return getTopNode().getResolution();
}
@Override
public UUID getResolutionId() throws CoreException {
return getTopNode().getResolutionId();
}
public synchronized BOMNode getResolvedNode(IComponentIdentifier identifier) throws CoreException {
if (nodeMap == null) {
HashMap<ComponentIdentifier, BOMNode> nm = new HashMap<ComponentIdentifier, BOMNode>();
buildNodeMap(getTopNode(), nm);
nodeMap = nm;
}
BOMNode node = nodeMap.get(identifier);
if (node == null)
throw new MissingComponentException(identifier.toString());
return node;
}
public String getTagInfo() {
return getQuery().getTagInfo();
}
public Date getTimestamp() {
return timestamp;
}
public List<ComponentRequest> getUnresolvedList() {
ArrayList<ComponentRequest> unresolved = new ArrayList<ComponentRequest>();
topNode.addUnresolved(unresolved, new HashSet<Resolution>());
return unresolved;
}
@Override
public String getViewName() throws CoreException {
return getTopNode().getViewName();
}
@Override
public boolean isChild(BOMNode node) throws CoreException {
return getTopNode().isChild(node);
}
@Override
public boolean isFullyResolved(ComponentQuery cquery, Map<String, ? extends Object> properties) throws CoreException {
return getTopNode().isFullyResolved(cquery, properties);
}
public boolean isFullyResolved(Map<String, ? extends Object> properties) throws CoreException {
return getTopNode().isFullyResolved(getQuery(), properties);
}
@Override
public final boolean isReferencing(BOMNode node, boolean shallow) throws CoreException {
return equals(node) || getTopNode().isReferencing(node, shallow);
}
public BillOfMaterials replaceNode(BOMNode node) throws CoreException {
return (BillOfMaterials) replaceNode(node, new HashMap<BOMNode, BOMNode>());
}
public BillOfMaterials switchContent(BillOfMaterials other) {
if (topNode.equals(other.getTopNode()))
return this;
return new BillOfMaterials(other.getTopNode(), query, new Date());
}
@Override
public void toSax(ContentHandler receiver, String namespace, String prefix, String localName) throws SAXException {
// Note, we do not call super here since we want the top element to
// contain the collection
//
HashMap<String, String> prefixMappings = new HashMap<String, String>();
ArrayList<IDWrapper> wrappers = new ArrayList<IDWrapper>();
HashSet<UUID> unique = new HashSet<UUID>();
wrappers.add(new IDWrapper(query.getId(), query));
try {
collectNodeContents(getTopNode(), unique, wrappers);
} catch (CoreException e) {
throw new SAXException(e);
}
for (IDWrapper wrapper : wrappers) {
UUIDKeyed wrapped = wrapper.getWrapped();
if (wrapped instanceof Provider)
((Provider) wrapped).addPrefixMappings(prefixMappings);
}
Set<Map.Entry<String, String>> pfxMappings = prefixMappings.entrySet();
if (pfxMappings.size() > 0) {
for (Map.Entry<String, String> pfxMapping : pfxMappings)
receiver.startPrefixMapping(pfxMapping.getKey(), pfxMapping.getValue());
}
AttributesImpl attrs = new AttributesImpl();
addAttributes(attrs);
Utils.emitCollection(namespace, prefix, localName, IDWrapper.TAG, attrs, wrappers, receiver);
if (pfxMappings.size() > 0) {
for (Map.Entry<String, String> pfxMapping : pfxMappings)
receiver.endPrefixMapping(pfxMapping.getKey());
}
}
@Override
protected void addAttributes(AttributesImpl attrs) {
if (topNode != null)
Utils.addAttribute(attrs, ATTR_TOP_NODE_ID, topNode.getId().toString());
Utils.addAttribute(attrs, ATTR_QUERY_ID, query.getId().toString());
Utils.addAttribute(attrs, ATTR_TIMESTAMP, DateAndTimeUtils.toISOFormat(timestamp));
}
@Override
void addMaterializationCandidates(RMContext context, List<Resolution> resolutions, ComponentQuery cquery, MaterializationSpec mspec,
Set<Resolution> perused) throws CoreException {
getTopNode().addMaterializationCandidates(context, resolutions, cquery, mspec, perused);
}
@Override
void collectAll(Set<Resolution> notThese, List<Resolution> all) throws CoreException {
getTopNode().collectAll(notThese, all);
}
@Override
BOMNode replaceNode(BOMNode node, Map<BOMNode, BOMNode> visited) throws CoreException {
BOMNode self = visited.get(this);
if (self != null)
return self;
BOMNode oldTop = getTopNode();
if (node instanceof BillOfMaterials && node.getQuery().equals(getQuery())) {
BOMNode newTop = ((BillOfMaterials) node).getTopNode();
if (oldTop.getQualifiedDependency().equals(newTop.getQualifiedDependency())) {
visited.put(this, node);
return node;
}
}
BOMNode newTop = oldTop.replaceNode(node, visited);
self = (oldTop == newTop) ? this : create(newTop, getQuery());
visited.put(this, self);
return self;
}
/**
* Special sax output method that is used for BillOfMaterials inlined in
* other BillOfMaterials since the attributes are sufficient here. All
* IdWrapper instances will be emitted in the outermost BillOfMaterials
*
* @param receiver
* @param namespace
* @param prefix
* @param localName
* @throws SAXException
*/
void wrappedToSax(ContentHandler receiver, String namespace, String prefix, String localName) throws SAXException {
AttributesImpl attrs = new AttributesImpl();
addAttributes(attrs);
String qName = Utils.makeQualifiedName(prefix, localName);
receiver.startElement(namespace, localName, qName, attrs);
receiver.endElement(namespace, localName, qName);
}
private synchronized BOMNode getTopNode() {
return topNode;
}
}