/*******************************************************************************
* Copyright (c) 2004, 2006
* Thomas Hallgren, Kenneth Olwing, Mitch Sonies
* Pontus Rydin, Nils Unden, Peer Torngren
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the individual
* copyright holders listed above, as Initial Contributors under such license.
* The text of such license is available at www.eclipse.org.
*******************************************************************************/
package org.eclipse.buckminster.core.resolver;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import org.eclipse.buckminster.core.Messages;
import org.eclipse.buckminster.core.RMContext;
import org.eclipse.buckminster.core.cspec.QualifiedDependency;
import org.eclipse.buckminster.core.cspec.model.ComponentRequest;
import org.eclipse.buckminster.core.helpers.FibonacciMonitorWrapper;
import org.eclipse.buckminster.core.helpers.JobBlocker;
import org.eclipse.buckminster.core.metadata.model.BOMNode;
import org.eclipse.buckminster.core.metadata.model.BillOfMaterials;
import org.eclipse.buckminster.core.query.model.ComponentQuery;
import org.eclipse.buckminster.core.rmap.model.Provider;
import org.eclipse.buckminster.core.rmap.model.ResourceMap;
import org.eclipse.buckminster.runtime.MonitorUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.IJobChangeListener;
import org.eclipse.core.runtime.jobs.Job;
/**
* @author Thomas Hallgren
*/
@SuppressWarnings("serial")
public class ResourceMapResolver extends LocalResolver implements IJobChangeListener, IResolver {
private boolean singleThreaded = false;
private boolean holdQueue = false;
private static int jobCounter = 0;
private final ArrayList<IProgressMonitor> jobMonitors = new ArrayList<IProgressMonitor>();
private final IResourceMapResolverFactory factory;
private IProgressMonitor topMonitor;
private final LinkedList<ResolverNodeWithJob> waitQueue = new LinkedList<ResolverNodeWithJob>();
public ResourceMapResolver(IResourceMapResolverFactory factory, ResolutionContext context, boolean singleThreaded) throws CoreException {
super(context);
this.factory = factory;
this.singleThreaded = singleThreaded;
}
@Override
public void aboutToRun(IJobChangeEvent event) {
}
@Override
public void awake(IJobChangeEvent event) {
}
@Override
public void done(IJobChangeEvent event) {
ResolverNodeWithJob.NodeResolutionJob job = (ResolverNodeWithJob.NodeResolutionJob) event.getJob();
job.removeJobChangeListener(this);
ResolverNodeWithJob node = job.getNode();
synchronized (node) {
node.setScheduled(false);
if (node.isInvalidated() && !node.isForceUnresolved())
schedule(node);
}
}
@Override
public BillOfMaterials resolve(ComponentRequest request, IProgressMonitor monitor) throws CoreException {
beginTopMonitor(monitor);
try {
ResolutionContext ctx = getContext();
ComponentQuery query = ctx.getComponentQuery();
ResolverNodeWithJob topNode = (ResolverNodeWithJob) getResolverNode(ctx,
new QualifiedDependency(request, query.getAttributes(request, ctx)), null);
if (singleThreaded) {
beginTopMonitor(monitor);
schedule(topNode);
endTopMonitor();
} else {
schedule(topNode);
waitForCompletion(MonitorUtils.subMonitor(monitor, 1));
}
return createBillOfMaterials(topNode);
} finally {
endTopMonitor();
}
}
@Override
public BillOfMaterials resolveRemaining(BillOfMaterials bom, IProgressMonitor monitor) throws CoreException {
if (bom.isFullyResolved(getContext())) {
MonitorUtils.complete(monitor);
return bom;
}
beginTopMonitor(monitor);
try {
ComponentQuery cquery = bom.getQuery();
ResolutionContext context = getContext();
if (!(cquery == null || cquery.equals(context.getComponentQuery())))
context = new ResolutionContext(cquery, context);
ResolverNodeWithJob topNode = (ResolverNodeWithJob) getResolverNode(context, bom.getQualifiedDependency(), bom.getTagInfo());
holdQueue = true;
if (topNode.rebuildTree(bom)) {
holdQueue = false;
if (singleThreaded) {
IStatus status = context.getStatus();
if (status.getSeverity() == IStatus.ERROR && !context.isContinueOnError())
throw new CoreException(status);
} else {
scheduleNext();
waitForCompletion(MonitorUtils.subMonitor(monitor, 1));
}
BillOfMaterials newBom = createBillOfMaterials(topNode);
if (!newBom.contentEqual(bom))
bom = newBom;
}
return bom;
} finally {
holdQueue = false;
endTopMonitor();
}
}
@Override
public synchronized void running(IJobChangeEvent event) {
if (topMonitor != null)
MonitorUtils.worked(topMonitor, 1);
}
@Override
public void scheduled(IJobChangeEvent event) {
}
@Override
public void sleeping(IJobChangeEvent event) {
}
synchronized void addJobMonitor(IProgressMonitor monitor) {
if (singleThreaded)
return;
if (topMonitor == null || topMonitor.isCanceled()) {
monitor.setCanceled(true);
return;
}
int idx = jobMonitors.size();
while (--idx >= 0)
if (jobMonitors.get(idx) == monitor)
return;
jobMonitors.add(monitor);
}
synchronized void cancelTopMonitor() {
if (topMonitor != null)
topMonitor.setCanceled(true);
}
@Override
ResolverNode createResolverNode(ResolutionContext context, QualifiedDependency qDep, String requestorInfo) {
return new ResolverNodeWithJob(this, context, qDep, requestorInfo);
}
BOMNode innerResolve(NodeQuery query, IProgressMonitor monitor) throws CoreException {
monitor.beginTask(null, 100);
try {
BOMNode node = null;
ComponentQuery cquery = null;
URL rmapURL = null;
ResourceMap rmap = null;
boolean mayUseLocalResolver = query.useWorkspace();
if (query.useResolutionService()) {
cquery = query.getComponentQuery();
rmapURL = cquery.getResolvedResourceMapURL();
rmap = factory.getResourceMap(getContext(), rmapURL, cquery.getConnectContext());
Provider directProvider = rmap.getFirstProvider(query);
if (directProvider != null && directProvider.hasLocalCache())
// Use the given provider! Make no attempt to use the
// LocalProvider
mayUseLocalResolver = false;
}
if (mayUseLocalResolver) {
if (factory.isLocalResolve()) {
query.logDecision(ResolverDecisionType.USING_RESOLVER, "Local resolver"); //$NON-NLS-1$
node = localResolve(query, MonitorUtils.subMonitor(monitor, 5));
} else {
query.logDecision(ResolverDecisionType.RESOLVER_REJECTED, "All local resolvers"); //$NON-NLS-1$
MonitorUtils.worked(monitor, 5);
}
}
if (node == null && rmap != null) {
query.logDecision(ResolverDecisionType.USING_RESOURCE_MAP, rmapURL);
node = rmap.resolve(query, MonitorUtils.subMonitor(monitor, 95));
} else
MonitorUtils.worked(monitor, 95);
return node;
} catch (CoreException e) {
RMContext context = getContext();
if (!context.isContinueOnError())
throw e;
context.addRequestStatus(query.getComponentRequest(), e.getStatus());
return null;
} finally {
monitor.done();
}
}
synchronized void removeJobMonitor(IProgressMonitor monitor) {
if (singleThreaded)
return;
int idx = jobMonitors.size();
while (--idx >= 0) {
if (jobMonitors.get(idx) == monitor) {
jobMonitors.remove(idx);
break;
}
}
}
synchronized void resolutionPartDone() {
if (singleThreaded)
return;
// Allow another job to enter. The resolution part of the
// calling job is done.
//
--jobCounter;
scheduleNext();
}
boolean schedule(ResolverNodeWithJob node) {
synchronized (node) {
if (node.isScheduled() || node.isResolved())
return false;
node.setScheduled(true);
}
if (singleThreaded) {
node.run(MonitorUtils.subMonitor(topMonitor, 1));
node.setScheduled(false);
} else {
pushOnWaitQueue(node);
if (!holdQueue)
scheduleNext();
}
return true;
}
private synchronized void beginTopMonitor(IProgressMonitor monitor) {
monitor = new FibonacciMonitorWrapper(monitor);
monitor.beginTask(null, 50);
topMonitor = monitor;
}
private void cancelAllJobs() {
synchronized (waitQueue) {
waitQueue.clear();
}
synchronized (this) {
int idx = jobMonitors.size();
while (--idx >= 0)
jobMonitors.get(idx).setCanceled(true);
if (topMonitor != null)
topMonitor.setCanceled(true);
}
}
private synchronized void endTopMonitor() {
topMonitor.done();
topMonitor = null;
}
private ResolverNodeWithJob popWaitQueue() {
synchronized (waitQueue) {
return waitQueue.poll();
}
}
private void pushOnWaitQueue(ResolverNodeWithJob node) {
synchronized (waitQueue) {
waitQueue.add(node);
}
}
private boolean scheduleNext() {
ArrayList<ResolverNodeWithJob> nodes = null;
synchronized (getClass()) {
int jobsToSchedule = factory.getResolverThreadsMax() - jobCounter;
while (--jobsToSchedule >= 0) {
ResolverNodeWithJob node = popWaitQueue();
if (node == null)
break;
if (nodes == null)
nodes = new ArrayList<ResolverNodeWithJob>();
nodes.add(node);
++jobCounter;
}
}
if (nodes == null)
return false;
int top = nodes.size();
for (int idx = 0; idx < top; ++idx) {
ResolverNodeWithJob.NodeResolutionJob job = nodes.get(idx).getJob();
job.addJobChangeListener(this);
job.schedule();
}
return true;
}
private void waitForCompletion(IProgressMonitor monitor) throws CoreException {
JobBlocker jobBlocker = new JobBlocker();
jobBlocker.addNameBlock(Messages.Building_workspace);
jobBlocker.addNameBlock(Messages.Periodic_workspace_save);
monitor.beginTask(null, IProgressMonitor.UNKNOWN);
try {
IStatus status;
RMContext context = getContext();
try {
for (;;) {
Job.getJobManager().join(this, MonitorUtils.subMonitor(monitor, 1));
// The waitQueue is ours but the job counter is share
// between instances so we might run into situations
// where we're not yet allowed to schedule anything.
//
if (waitQueue.isEmpty())
break;
while (!scheduleNext())
//
// Sleep a while, then try again
//
Thread.sleep(100);
}
status = context.getStatus();
} catch (OperationCanceledException e) {
status = Status.CANCEL_STATUS;
} catch (InterruptedException e) {
status = Status.CANCEL_STATUS;
}
if (status.getSeverity() == IStatus.CANCEL) {
cancelAllJobs();
throw new OperationCanceledException();
}
if (status.getSeverity() == IStatus.ERROR && !context.isContinueOnError()) {
throw new CoreException(status);
}
} finally {
monitor.done();
jobBlocker.release();
}
}
}