/*****************************************************************************
* 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.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import org.eclipse.buckminster.core.CorePlugin;
import org.eclipse.buckminster.core.KeyConstants;
import org.eclipse.buckminster.core.common.model.ExpandingProperties;
import org.eclipse.buckminster.core.common.model.SAXEmitter;
import org.eclipse.buckminster.core.cspec.IAction;
import org.eclipse.buckminster.core.cspec.IAttribute;
import org.eclipse.buckminster.core.cspec.IAttributeFilter;
import org.eclipse.buckminster.core.cspec.PathGroup;
import org.eclipse.buckminster.core.cspec.SaxablePath;
import org.eclipse.buckminster.core.cspec.builder.ActionBuilder;
import org.eclipse.buckminster.core.cspec.builder.AttributeBuilder;
import org.eclipse.buckminster.core.cspec.builder.CSpecBuilder;
import org.eclipse.buckminster.core.internal.actor.ActorFactory;
import org.eclipse.buckminster.core.internal.actor.PerformManager;
import org.eclipse.buckminster.core.metadata.model.IModelCache;
import org.eclipse.buckminster.runtime.Logger;
import org.eclipse.buckminster.sax.ISaxableElement;
import org.eclipse.buckminster.sax.Utils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
/**
* @author Thomas Hallgren
*/
public class Action extends TopLevelAttribute implements IAction {
public static final String ATTR_ACTOR = "actor"; //$NON-NLS-1$
public static final String ATTR_ALWAYS = "always"; //$NON-NLS-1$
public static final String ATTR_ASSIGN_CONSOLE_SUPPORT = "assignConsoleSupport"; //$NON-NLS-1$
public static final String ATTR_PRODUCT_FILE_COUNT = "fileCount"; //$NON-NLS-1$
public static final String ATTR_UP_TO_DATE_POLICY = "upToDatePolicy"; //$NON-NLS-1$
public static final String ELEM_ACTOR_PROPERTIES = "actorProperties"; //$NON-NLS-1$
public static final String ELEM_PROPERTIES = "properties"; //$NON-NLS-1$
public static final String ELEM_PRODUCTS = "products"; //$NON-NLS-1$
public static final boolean ALWAYS_DEFAULT = false;
public static final boolean ASSIGN_CONSOLE_SUPPORT_DEFAULT = true;
private final Set<IPath> products;
private final String productAlias;
private final IPath productBase;
private final String actorName;
private final boolean always;
private final int productFileCount;
private final Map<String, String> actorProperties;
private final Map<String, String> properties;
private final boolean assignConsoleSupport;
private final UpToDatePolicy upToDatePolicy;
private Prerequisites prerequisites;
public static final String BINDING_NAME = "binding.name"; //$NON-NLS-1$
public Action(ActionBuilder builder) {
super(builder);
actorName = builder.getActorName();
prerequisites = new Prerequisites(this, builder.getPrerequisitesBuilder());
always = builder.isAlways();
assignConsoleSupport = builder.isAssignConsoleSupport();
productAlias = builder.getProductAlias();
productBase = builder.getProductBase();
productFileCount = builder.getProductFileCount();
products = CSpec.createUnmodifiablePaths(builder.getProductPaths());
actorProperties = ExpandingProperties.createUnmodifiableProperties(builder.getActorProperties());
properties = ExpandingProperties.createUnmodifiableProperties(builder.getProperties());
upToDatePolicy = builder.getUpToDatePolicy();
}
@Override
public IAttribute copy() {
Action copy = (Action) super.copy();
copy.prerequisites = (Prerequisites) copy.prerequisites.copy();
return copy;
}
@Override
public String getActorName() {
try {
return isInternal() ? ActorFactory.getInstance().findInternalActionActorName(getName()) : actorName;
} catch (CoreException ce) {
throw new RuntimeException(ce);
}
}
@Override
public Map<String, String> getActorProperties() {
return actorProperties;
}
public String getBindingName(Map<String, ? extends Object> globalProps) {
Map<String, String> actionProps = getProperties();
if (actionProps.containsKey(BINDING_NAME)) {
ExpandingProperties<Object> allProps = new ExpandingProperties<Object>(globalProps);
allProps.putAll(actionProps);
return (String) allProps.get(BINDING_NAME);
}
return null;
}
public IPath getExpandedBase(IPath base, Map<String, ? extends Object> local) {
if (base == null)
return getExpandedDefaultBase(local);
base = PerformManager.expandPath(local, base);
if (!base.isAbsolute())
base = getExpandedDefaultBase(local).append(base);
return base;
}
public IPath getExpandedDefaultBase(Map<String, ? extends Object> local) {
return PerformManager.expandPath(local, Path.fromPortableString(KeyConstants.ACTION_OUTPUT_REF));
}
@Override
public Group getPrerequisiteGroup() {
return prerequisites;
}
public IPath getPrerequisiteRebase() {
return prerequisites.getPrerequisiteRebase();
}
@Override
public List<Prerequisite> getPrerequisites(Stack<IAttributeFilter> filters) {
return prerequisites.getPrerequisites(filters);
}
public String getPrerequisitesAlias() {
return prerequisites.getName();
}
@Override
public String getProductAlias() {
return productAlias;
}
public List<ActionArtifact> getProductArtifacts() {
return getCSpec().getActionArtifacts(this);
}
@Override
public IPath getProductBase() {
return productBase;
}
@Override
public int getProductFileCount() {
return productFileCount;
}
@Override
public Set<IPath> getProductPaths() {
return products;
}
@Override
public Map<String, String> getProperties() {
return properties;
}
@Override
public UpToDatePolicy getUpToDatePolicy() {
return upToDatePolicy;
}
@Override
public final boolean isAlways() {
return always;
}
@Override
public boolean isAssignConsoleSupport() {
return assignConsoleSupport;
}
@Override
public final boolean isInternal() {
return actorName == null;
}
@Override
public boolean isProducedByActions(IModelCache ctx) {
return true;
}
public boolean isUpToDate(IModelCache ctx) throws CoreException {
Logger logger = CorePlugin.getLogger();
String failLeadIn = ""; //$NON-NLS-1$
boolean isDebug = logger.isDebugEnabled();
if (isDebug)
failLeadIn = String.format("Action %s using 'up to date' policy %s: Rebuild needed: ", this, //$NON-NLS-1$
upToDatePolicy);
int expectedFileCount;
if (upToDatePolicy == UpToDatePolicy.MAPPER) {
Map<String, Long> prereqFiles = getPrerequisiteRelativeFiles(ctx);
Map<String, Long> productFiles = getProductRelativeFiles(ctx);
expectedFileCount = prereqFiles.size();
if (productFileCount > 0)
expectedFileCount += productFileCount;
if (productFiles.size() < expectedFileCount) {
// Not enough files
//
if (isDebug)
logger.debug("%sFile count(%d) < expected(%d)", failLeadIn, Integer.valueOf(productFiles.size()), //$NON-NLS-1$
Integer.valueOf(expectedFileCount));
return false;
}
// Don't consider products that we don't need since their timestamp
// might effect the outcome negatively
//
for (Map.Entry<String, Long> entry : prereqFiles.entrySet()) {
Long tsObj = productFiles.get(entry.getKey());
if (tsObj == null) {
// Oops, missing product
//
if (isDebug)
logger.debug(String.format("%sNo product is matching requirement %s", failLeadIn, entry //$NON-NLS-1$
.getKey()));
return false;
}
long productTs = tsObj.longValue();
long prereqTs = entry.getValue().longValue();
if (prereqTs > productTs) {
// Prerequisite is newer
//
if (isDebug)
logger.debug(String.format("%sThe product for %s of age %s is older then its matching requirement with age %s", //$NON-NLS-1$
failLeadIn, entry.getKey(), new Date(productTs), new Date(prereqTs)));
return false;
}
}
if (isDebug)
logger.debug(String.format("Action %s using 'up to date' policy %s: Product is up to date", this, //$NON-NLS-1$
upToDatePolicy));
return true;
}
int[] fileCountBin = new int[] { 0 };
switch (upToDatePolicy) {
case COUNT:
expectedFileCount = productFileCount;
break;
case ACTOR:
case NOT_EMPTY:
expectedFileCount = 0;
break;
default:
expectedFileCount = -1;
}
long oldest = getFirstModified(ctx, expectedFileCount, fileCountBin);
if (upToDatePolicy == UpToDatePolicy.ACTOR) {
fileCountBin[0] = 0;
long prereqAge = getPrerequisiteGroup().getLastModified(ctx, oldest, fileCountBin);
if (ActorFactory.getInstance().getActor(this).isUpToDate(this, ctx, prereqAge, oldest)) {
if (isDebug)
logger.debug(String.format("Action %s using 'up to date' policy %s: Product is up to date", this, //$NON-NLS-1$
upToDatePolicy));
return true;
}
if (isDebug)
logger.debug("%sActor decision", failLeadIn); //$NON-NLS-1$
return false;
}
int fileCount = fileCountBin[0];
if (oldest == 0L || (expectedFileCount > 0 && expectedFileCount > fileCount)) {
if (isDebug) {
switch (upToDatePolicy) {
case DEFAULT:
logger.debug(String.format("%sProduct has folders", failLeadIn)); //$NON-NLS-1$
break;
case NOT_EMPTY:
logger.debug(String.format("%sProduct is empty", failLeadIn)); //$NON-NLS-1$
break;
default:
logger.debug(String.format("%sFile count(%d) < expected(%d)", failLeadIn, Integer //$NON-NLS-1$
.valueOf(fileCountBin[0]), Integer.valueOf(expectedFileCount)));
break;
}
}
return false;
}
fileCountBin[0] = 0;
long prereqAge = getPrerequisiteGroup().getLastModified(ctx, oldest, fileCountBin);
if (oldest >= prereqAge) {
if (isDebug)
logger.debug(String.format("Action %s using 'up to date' policy %s: Product is up to date", this, //$NON-NLS-1$
upToDatePolicy));
return true;
}
if (isDebug)
logger.debug(String.format("%s: Product of age %s is older then prerequisite of age %s", failLeadIn, //$NON-NLS-1$
new Date(oldest), new Date(prereqAge)));
return false;
}
@Override
protected void addAttributes(AttributesImpl attrs) {
super.addAttributes(attrs);
if (actorName != null)
Utils.addAttribute(attrs, ATTR_ACTOR, actorName);
if (always != ALWAYS_DEFAULT)
Utils.addAttribute(attrs, ATTR_ALWAYS, Boolean.toString(always));
if (assignConsoleSupport != ASSIGN_CONSOLE_SUPPORT_DEFAULT)
Utils.addAttribute(attrs, ATTR_ASSIGN_CONSOLE_SUPPORT, Boolean.toString(assignConsoleSupport));
}
@Override
protected AttributeBuilder createAttributeBuilder(CSpecBuilder cspecBuilder) {
return cspecBuilder.createActionBuilder();
}
@Override
protected void emitElements(ContentHandler handler, String namespace, String prefix) throws SAXException {
super.emitElements(handler, namespace, prefix);
if (!actorProperties.isEmpty()) {
String qName = Utils.makeQualifiedName(prefix, ELEM_ACTOR_PROPERTIES);
handler.startElement(namespace, ELEM_ACTOR_PROPERTIES, qName, ISaxableElement.EMPTY_ATTRIBUTES);
SAXEmitter.emitProperties(handler, actorProperties, namespace, prefix, true, false);
handler.endElement(namespace, ELEM_ACTOR_PROPERTIES, qName);
}
if (!properties.isEmpty()) {
String qName = Utils.makeQualifiedName(prefix, ELEM_PROPERTIES);
handler.startElement(namespace, ELEM_PROPERTIES, qName, ISaxableElement.EMPTY_ATTRIBUTES);
SAXEmitter.emitProperties(handler, properties, namespace, prefix, true, false);
handler.endElement(namespace, ELEM_PROPERTIES, qName);
}
if (prerequisites.getPrerequisites().size() > 0)
prerequisites.toSax(handler, namespace, prefix, prerequisites.getDefaultTag());
AttributesImpl attrs = new AttributesImpl();
if (productAlias != null)
Utils.addAttribute(attrs, Prerequisite.ATTR_ALIAS, productAlias);
if (productBase != null)
Utils.addAttribute(attrs, Artifact.ATTR_BASE, productBase.toPortableString());
if (productFileCount >= 0)
Utils.addAttribute(attrs, ATTR_PRODUCT_FILE_COUNT, Integer.toString(productFileCount));
if (upToDatePolicy != UpToDatePolicy.DEFAULT)
Utils.addAttribute(attrs, ATTR_UP_TO_DATE_POLICY, upToDatePolicy.name());
ArrayList<ISaxableElement> allProds = new ArrayList<ISaxableElement>();
for (IPath path : products)
allProds.add((SaxablePath) path);
allProds.addAll(getCSpec().getActionArtifacts(this));
Utils.emitCollection(namespace, prefix, ELEM_PRODUCTS, null, attrs, allProds, handler);
}
@Override
protected PathGroup[] internalGetPathGroups(IModelCache ctx, Map<String, ? extends Object> local, Stack<IAttributeFilter> filters)
throws CoreException {
CSpec cspec = getCSpec();
ArrayList<PathGroup> pathGroups = new ArrayList<PathGroup>();
int numProducts = products.size();
if (productBase != null || numProducts > 0) {
// Add the anonymous group
//
IPath base = getExpandedBase(productBase, local);
IPath[] pathArr = products.toArray(new IPath[numProducts]);
while (--numProducts >= 0)
pathArr[numProducts] = PerformManager.expandPath(local, pathArr[numProducts]);
pathGroups.add(new PathGroup(base, pathArr));
}
// Add produced artifacts
//
for (Artifact a : cspec.getActionArtifacts(this))
for (PathGroup pathGroup : a.getPathGroups(ctx, filters))
pathGroups.add(pathGroup);
return pathGroups.toArray(new PathGroup[pathGroups.size()]);
}
@Override
void setCSPec(CSpec cspec) {
super.setCSPec(cspec);
prerequisites.setCSPec(cspec);
}
private Map<String, Long> getPrerequisiteRelativeFiles(IModelCache ctx) throws CoreException {
HashMap<String, Long> filesAndDates = new HashMap<String, Long>();
CSpec cspec = getCSpec();
for (Prerequisite pq : getPrerequisites(null)) {
if (!pq.isContributor())
continue;
IAttribute ag = pq.getReferencedAttribute(cspec, ctx);
if (ag instanceof TopLevelAttribute)
((TopLevelAttribute) ag).appendRelativeFiles(ctx, filesAndDates);
}
return filesAndDates;
}
private Map<String, Long> getProductRelativeFiles(IModelCache ctx) throws CoreException {
HashMap<String, Long> filesAndDates = new HashMap<String, Long>();
appendRelativeFiles(ctx, filesAndDates);
return filesAndDates;
}
}