/*****************************************************************************
* 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.internal.actor;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
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.actor.IActor;
import org.eclipse.buckminster.core.actor.IGlobalContext;
import org.eclipse.buckminster.core.actor.IPerformManager;
import org.eclipse.buckminster.core.common.model.ExpandingProperties;
import org.eclipse.buckminster.core.cspec.IActionArtifact;
import org.eclipse.buckminster.core.cspec.IAttribute;
import org.eclipse.buckminster.core.cspec.ICSpecData;
import org.eclipse.buckminster.core.cspec.PathGroup;
import org.eclipse.buckminster.core.cspec.model.Action;
import org.eclipse.buckminster.core.cspec.model.ActionArtifact;
import org.eclipse.buckminster.core.cspec.model.Attribute;
import org.eclipse.buckminster.core.cspec.model.CSpec;
import org.eclipse.buckminster.core.cspec.model.ComponentRequest;
import org.eclipse.buckminster.core.cspec.model.Generator;
import org.eclipse.buckminster.core.cspec.model.Prerequisite;
import org.eclipse.buckminster.core.helpers.NullOutputStream;
import org.eclipse.buckminster.core.metadata.AmbigousComponentException;
import org.eclipse.buckminster.core.metadata.MissingComponentException;
import org.eclipse.buckminster.core.metadata.WorkspaceInfo;
import org.eclipse.buckminster.osgi.filter.Filter;
import org.eclipse.buckminster.runtime.Logger;
import org.eclipse.buckminster.runtime.MonitorUtils;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
/**
* @author kolwing
* @author Thomas Hallgren
*/
public class PerformManager implements IPerformManager {
abstract class ActionInvocation {
abstract IStatus execute(GlobalContext globalCtx, IProgressMonitor monitor) throws CoreException;
abstract Action getAction();
abstract void toString(StringBuilder bld);
}
class DeferredActionInvocation extends ActionInvocation {
private final CSpec requestor;
private final Prerequisite prerequisite;
public DeferredActionInvocation(CSpec requestor, Prerequisite prerequisite) {
this.requestor = requestor;
this.prerequisite = prerequisite;
}
@Override
IStatus execute(GlobalContext globalCtx, IProgressMonitor monitor) throws CoreException {
CSpec referenced = prerequisite.getReferencedCSpec(requestor, globalCtx);
if (referenced == null)
// Dependency not enabled
return Status.OK_STATUS;
Attribute attr = referenced.getRequiredAttribute(prerequisite.getName());
Filter filter = attr.getFilter();
if (filter != null && !filter.matches(globalCtx.getProperties()))
// Attribute is not enabled
return Status.OK_STATUS;
return internalPerform(Collections.singletonList(attr), globalCtx, monitor);
}
@Override
Action getAction() {
return null;
}
@Override
void toString(StringBuilder bld) {
bld.append("Deferred invocation of "); //$NON-NLS-1$
bld.append(prerequisite);
}
}
class DirectActionInvocation extends ActionInvocation {
private final Action action;
private PerformContext performContext;
DirectActionInvocation(Action action) {
this.action = action;
}
@Override
IStatus execute(GlobalContext globalCtx, IProgressMonitor monitor) throws CoreException {
PrintStream out;
PrintStream err;
if (action.isAssignConsoleSupport()) {
out = Logger.getOutStream();
err = Logger.getErrStream();
} else {
out = nullPrintStream;
err = nullPrintStream;
}
MonitorUtils.begin(monitor, 100);
performContext = new PerformContext(globalCtx, action, out, err, monitor);
if (globalCtx.hasPerformedAction(action)) {
MonitorUtils.done(monitor);
return Status.OK_STATUS;
}
globalCtx.addPerformedAction(action);
if (!performContext.isForced() && action.isUpToDate(performContext)) {
MonitorUtils.done(monitor);
return Status.OK_STATUS;
}
IActor actor = ActorFactory.getInstance().getActor(action);
IStatus status = actor.perform(performContext, new SubProgressMonitor(monitor, 90, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK));
if (status.getSeverity() == IStatus.CANCEL)
throw new OperationCanceledException();
if (status.getSeverity() != IStatus.ERROR)
makeWorkspaceAwareOfProducts(MonitorUtils.subMonitor(monitor, 10));
MonitorUtils.done(monitor);
return status;
}
@Override
final Action getAction() {
return action;
}
final PerformContext getPerformContext() {
return performContext;
}
private void makeWorkspaceAwareOfProducts(IProgressMonitor monitor) throws CoreException {
PathGroup[] pathGroups = action.getPathGroups(performContext, null);
int ticks = 100 * pathGroups.length;
monitor.beginTask(null, ticks);
try {
HashSet<IPath> alreadyRefreshed = new HashSet<IPath>();
for (PathGroup pathGroup : pathGroups) {
IProgressMonitor groupMonitor = MonitorUtils.subMonitor(monitor, 100);
IPath[] paths = pathGroup.getPaths();
groupMonitor.beginTask(null, 10 * paths.length);
IPath base = pathGroup.getBase();
for (IPath path : paths)
refreshAndSetDerivedPath(path.isAbsolute() ? path : base.append(path), alreadyRefreshed,
MonitorUtils.subMonitor(groupMonitor, 10));
groupMonitor.done();
}
} finally {
MonitorUtils.done(monitor);
}
}
@Override
void toString(StringBuilder bld) {
action.toString(bld);
}
}
class GeneratorInvocation extends DirectActionInvocation {
private final Generator generator;
public GeneratorInvocation(Generator generator, Action generatorAction) {
super(generatorAction);
this.generator = generator;
}
@Override
IStatus execute(GlobalContext globalCtx, IProgressMonitor monitor) throws CoreException {
IStatus status = super.execute(globalCtx, monitor);
if (status.getSeverity() == IStatus.ERROR)
return status;
generator.registerGeneratedResolution(getAction().getPathGroups(getPerformContext(), null), globalCtx, monitor);
return status;
}
}
private static final PerformManager INSTANCE = new PerformManager();
private static final PrintStream nullPrintStream = new PrintStream(NullOutputStream.INSTANCE);
public static IPath expandPath(Map<String, ? extends Object> properties, IPath path) {
if (path != null)
path = new Path(ExpandingProperties.expand(properties, path.toPortableString(), 0));
return path;
}
public static PerformManager getInstance() {
return INSTANCE;
}
private static void refreshAndSetDerivedPath(IPath path, HashSet<IPath> alreadyRefreshed, IProgressMonitor monitor) throws CoreException {
monitor.beginTask(null, 100);
try {
String leaf = null;
if (!path.hasTrailingSeparator()) {
// Path denotes a file, refresh its directory
//
leaf = path.lastSegment();
path = path.removeLastSegments(1).addTrailingSeparator();
}
if (alreadyRefreshed.contains(path))
return;
alreadyRefreshed.add(path);
IContainer container = ResourcesPlugin.getWorkspace().getRoot().getContainerForLocation(path);
if (container == null)
return;
if (!(container instanceof IProject))
refreshParents(container.getParent(), MonitorUtils.subMonitor(monitor, 50));
else
MonitorUtils.worked(monitor, 50);
container.refreshLocal(IResource.DEPTH_INFINITE, MonitorUtils.subMonitor(monitor, 40));
if (leaf != null) {
IFile file = container.getFile(new Path(leaf));
if (file.exists())
file.setDerived(true, MonitorUtils.subMonitor(monitor, 10));
else
MonitorUtils.worked(monitor, 10);
} else {
if (!(container instanceof IProgressMonitor))
container.setDerived(true, MonitorUtils.subMonitor(monitor, 10));
else
MonitorUtils.worked(monitor, 10);
}
} finally {
monitor.done();
}
}
private static void refreshParents(IContainer container, IProgressMonitor monitor) throws CoreException {
monitor.beginTask("", IProgressMonitor.UNKNOWN); //$NON-NLS-1$
try {
if (container != null) {
// Don't refresh the parent of a project since that would be
// refreshing the
// whole workspace.
//
if (!(container instanceof IProject))
refreshParents(container.getParent(), MonitorUtils.subMonitor(monitor, 1));
container.refreshLocal(IResource.DEPTH_ZERO, MonitorUtils.subMonitor(monitor, 1));
}
} finally {
monitor.done();
}
}
private void addAttributeChildren(GlobalContext ctx, Attribute attribute, Set<String> seen, List<ActionInvocation> ordered,
Generator attrGenerator) throws CoreException {
if (attribute instanceof ActionArtifact)
attribute = ((ActionArtifact) attribute).getAction();
String attrId = attribute.toString();
if (!seen.add(attrId))
return;
CSpec cspec = attribute.getCSpec();
for (Prerequisite preq : attribute.getPrerequisites()) {
try {
Attribute ag = preq.getReferencedAttribute(cspec, ctx);
if (ag != null)
addAttributeChildren(ctx, ag, seen, ordered, null);
} catch (MissingComponentException e) {
// The component might need to be generated in which case it's
// generator must be built.
//
String cName;
String cType;
if (preq.getComponentName() == null) {
cName = cspec.getName();
cType = cspec.getComponentTypeID();
} else {
ComponentRequest rq = cspec.getDependency(preq.getComponentName(), preq.getComponentType(), preq.getVersionRange());
cName = rq.getName();
cType = rq.getComponentTypeID();
}
List<Generator> generators = WorkspaceInfo.getGenerators(new ComponentRequest(cName, cType, null));
if (generators.size() == 0)
//
// There is no known generator for the component
//
throw e;
// If several candidates are found, we prefer the cspec that
// requested the
// attribute if it is also a generator.
//
CSpec generatorCSpec = null;
Generator actionGenerator = null;
for (Generator generator : generators) {
String component = generator.getComponent();
CSpec candidate = component == null ? generator.getCSpec() : WorkspaceInfo.getResolution(
new ComponentRequest(component, null, null), false).getCSpec();
if (candidate.equals(cspec)) {
actionGenerator = generator;
generatorCSpec = candidate;
break;
}
if (!(generatorCSpec == null || generatorCSpec.equals(candidate)))
//
// We find a generator for the desired component in more
// then
// one other component. This is an ambiguity that we
// cannot resolve.
//
throw new AmbigousComponentException(generator.getGenerates());
actionGenerator = generator;
generatorCSpec = candidate;
}
// Add the attribute that represents the generated component
//
Attribute ag = generatorCSpec.getAttribute(actionGenerator.getAttribute());
if (seen.contains(ag.toString())) {
// Make sure it was added as a generator
//
int top = ordered.size();
for (int idx = 0; idx < top; ++idx) {
ActionInvocation ai = ordered.get(idx);
if (ai.getAction() == ag && !(ai instanceof GeneratorInvocation)) {
ordered.set(idx, new GeneratorInvocation(actionGenerator, ai.getAction()));
ordered.add(new DeferredActionInvocation(cspec, preq));
break;
}
}
} else {
addAttributeChildren(ctx, ag, seen, ordered, actionGenerator);
ordered.add(new DeferredActionInvocation(cspec, preq));
}
}
}
if (attribute instanceof Action) {
Action action = (Action) attribute;
ordered.add(attrGenerator == null ? new DirectActionInvocation(action) : new GeneratorInvocation(attrGenerator, action));
}
}
private List<ActionInvocation> getOrderedActionList(GlobalContext ctx, List<? extends IAttribute> attributes) throws CoreException {
Set<String> seen = new HashSet<String>();
List<ActionInvocation> ordered = new ArrayList<ActionInvocation>();
for (IAttribute attribute : attributes)
addAttributeChildren(ctx, (Attribute) attribute, seen, ordered, null);
for (IAttribute attribute : attributes) {
if (attribute instanceof IActionArtifact)
attribute = ((ActionArtifact) attribute).getAction();
String attrId = attribute.toString();
if (!seen.contains(attrId)) {
seen.add(attrId);
if (attribute instanceof Action)
ordered.add(new DirectActionInvocation((Action) attribute));
}
}
return ordered;
}
private IStatus internalPerform(List<? extends IAttribute> attributes, IGlobalContext global, IProgressMonitor monitor) throws CoreException {
// calculate a flat dependency list of actions to be done
// adjust as needed when it's a build integration
//
GlobalContext globalCtx = (GlobalContext) global;
List<ActionInvocation> actionList = getOrderedActionList(globalCtx, attributes);
monitor.beginTask(null, 100 * actionList.size());
Logger logger = CorePlugin.getLogger();
if (logger.isDebugEnabled()) {
StringBuilder bld = new StringBuilder(40 + actionList.size() * 40);
bld.append(Messages.Actions_to_perform_in_order);
for (ActionInvocation action : actionList) {
bld.append("\n "); //$NON-NLS-1$
action.toString(bld);
}
logger.debug(bld.toString());
}
MultiStatus retStatus = new MultiStatus(CorePlugin.getID(), IStatus.OK, "", null); //$NON-NLS-1$
for (ActionInvocation action : actionList) {
// Check that the action hasn't been executed already. This may
// happen when actions
// explicitly call on other actions (such as the fragment actor)
//
IStatus status = action.execute(globalCtx, MonitorUtils.subMonitor(monitor, 100));
switch (status.getSeverity()) {
case IStatus.WARNING:
case IStatus.INFO:
retStatus.add(status);
// fall through
case IStatus.OK:
continue;
case IStatus.ERROR:
retStatus.add(status);
break;
}
break;
}
IStatus[] children = retStatus.getChildren();
IStatus status = children.length == 1 ? children[0] : retStatus;
if (status.getSeverity() == IStatus.ERROR)
throw new CoreException(status);
return status;
}
@Override
public IGlobalContext perform(ICSpecData cspec, String attributeName, Map<String, ? extends Object> props, boolean forced, boolean quiet,
IProgressMonitor monitor) throws CoreException {
return perform(Collections.singletonList(cspec.getAdapter(CSpec.class).getRequiredAttribute(attributeName)), props, forced, quiet, monitor);
}
@Override
public IStatus perform(List<? extends IAttribute> attributes, IGlobalContext global, IProgressMonitor monitor) throws CoreException {
WorkspaceInfo.pushPerformContext(global);
try {
return internalPerform(attributes, global, monitor);
} finally {
WorkspaceInfo.popPerformContext();
}
}
@Override
public IGlobalContext perform(List<? extends IAttribute> attributes, Map<String, ? extends Object> userProps, boolean forced, boolean quiet,
IProgressMonitor monitor) throws CoreException {
GlobalContext globalCtx = new GlobalContext(userProps, forced, quiet);
monitor.beginTask(null, 1000);
try {
globalCtx.setStatus(perform(attributes, globalCtx, MonitorUtils.subMonitor(monitor, 900)));
return globalCtx;
} finally {
globalCtx.removeScheduled(MonitorUtils.subMonitor(monitor, 50));
if (globalCtx.isWorkspaceRefreshPending())
ResourcesPlugin.getWorkspace().getRoot().refreshLocal(IResource.DEPTH_INFINITE, MonitorUtils.subMonitor(monitor, 50));
monitor.done();
}
}
}