blob: 005481e98941f7104b3f16ffe69f2f53787bb8be [file] [log] [blame]
// BatchManager.java
package org.eclipse.stem.jobs.batch;
/*******************************************************************************
* Copyright (c) 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.stem.core.common.DublinCore;
import org.eclipse.stem.core.experiment.Experiment;
import org.eclipse.stem.jobs.Activator;
import org.eclipse.stem.jobs.execution.ExecutableManager;
import org.eclipse.swt.widgets.Display;
/**
* This class implements a centralized manager for running {@link Experiment}s
* in "batch mode". The idea of batch mode is that a {@link Scenario} referenced
* by an {@link Experiment} can be used as a base from which a collection of
* other {@link Scenario}s can be derived. These derived {@link Scenario}s
* differ from the base in some (usually) minor variation of one or more of the
* base's configuration parameters.
* <p>
* The manager controls the creation of the derived {@link Scenario}s from
* specifications provided by a user, and then, their subsequent execution as
* {@link Simulation}s.
*
* @see Batch
* @see Experiment
* @see Modifier
*/
public class BatchManager extends ExecutableManager implements IBatchListener {
/**
* The singleton instance of the {@link BatchManager}.
*/
public static BatchManager INSTANCE = new BatchManager();
/**
* This is the sequence number assigned to each successive {@link Batch}
* instance the manager creates.
*/
static private int sequenceNumber = 0;
/**
* Constant empty array of {@link Batch}s.
*/
public static final IBatch[] NONE = new Batch[] {};
/**
* The collection of {@link IBatchManagerListener}'s waiting to be told
* about {@link BatchManagerEvent}'s
*/
private final List<IBatchManagerListener> listeners = new CopyOnWriteArrayList<IBatchManagerListener>();
/**
* The collection of {@link IBatchManagerListenerSync}'s waiting to be told
* about {@link BatchManagerEvent}'s
*/
private final List<IBatchManagerListenerSync> listenersSync = new CopyOnWriteArrayList<IBatchManagerListenerSync>();
/**
* The {@link Set} of {@link Batch}s being managed.
*/
private final List<IBatch> batches = new ArrayList<IBatch>();
/**
* Private constructor so that instances of the {@link BatchManager} cannot
* be created externally.
*/
private BatchManager() {
// nothing
} // BatchManager
/**
* @return the next {@link Batch} sequence number and increment the value.
*/
synchronized private static final int getAndIncrementBatchSequenceNumber() {
return sequenceNumber++;
} // getAndIncrementBatchSequenceNumber
/**
* @return the {@link List} of {@link Batch}s being managed.
*/
public List<IBatch> getActiveBatches() {
return batches;
} // getActiveBatches
/**
* Add a new {@link Batch} to the manager's collection and notify any
* listeners of the addition.
*/
private void addActiveBatch(final IBatch batch) {
batches.add(batch);
batch.addBatchListener(this);
fireBatchManagerChanged(new IBatch[] { batch }, NONE);
} // addActiveBatch
/**
* Remove an {@link IBatch} from the collection of active {@link IBatch}s.
*
* @param simulation
* the {@link IBatch} to remove
*/
private final void removeActiveBatch(final IBatch batch) {
batches.remove(batch);
// We're no longer a listener
batch.removeBatchListener(this);
fireBatchManagerChanged(NONE, new IBatch[] { batch });
} // removeActiveBatch
/**
* Create a {@link IBatch} that's ready to run
*
* @param experiment
* the {@link Experiment} that is the basis for the {@link Batch}
* @return an {@link IBatch} from the {@link Experiment}
*/
public IBatch createBatch(final Experiment experiment) {
final Batch batch = new Batch(experiment,
getAndIncrementBatchSequenceNumber());
batch.setPriority(Job.LONG);
final IBatch retValue = new BatchAdapter(batch);
addActiveBatch(retValue);
return retValue;
} // createBatch
/**
* @param configurationElement
* @param monitor
* @return
*/
protected IBatch createBatch(
final IConfigurationElement configurationElement,
final IProgressMonitor monitor) {
IBatch retValue = null;
final String experimentURIString = configurationElement
.getAttribute(DublinCore.IDENTIFIER);
try {
retValue = createBatch(URI.createURI(experimentURIString), monitor);
} catch (final Exception e) {
Activator
.logError(
MessageFormat
.format(
Messages
.getString("BatchMgr.Deserialization_Error"), experimentURIString), e); //$NON-NLS-1$
retValue = null;
}
return retValue;
} // createBatch
/**
* @param experimentURI
* the {@link URI} of a serialized {@link Experiment}
* @param monitor
* a {@link ProgressMonitor}
* @return an {@link IBatch} deserialized from the source identified by the
* experimentURI, or <code>null</code> if there was a problem.
*/
private IBatch createBatch(final URI experimentURI,
final IProgressMonitor monitor) {
IBatch retValue = null;
try {
final ResourceSet resourceSet = new ResourceSetImpl();
final Resource resource = resourceSet.getResource(experimentURI,
true);
monitor.subTask("Reading Experiment from file");
resource.load(null);
monitor.subTask("Creating Batch from Experiment");
retValue = createBatch((Experiment) resource.getContents().get(0));
} catch (final Exception e) {
Activator
.logError(
MessageFormat
.format(
Messages
.getString("BatchMgr.Deserialization_Error"), experimentURI.toString()), e); //$NON-NLS-1$
retValue = null;
}
return retValue;
} // createBatch
/**
* Create a {@link IBatch} from an {@link Experiment} instance and then
* start it running.
*
* @param experiment
* the {@link Experiment} to be run
*/
public void createAndRunBatch(final Experiment experiment) {
new Job(Messages.getString("BatchMgr.StartBatch")) {
@Override
protected IStatus run(final IProgressMonitor monitor) {
try {
final IBatch batch = createBatch(experiment);
monitor.subTask(Messages.getString("BatchMgr.Run"));
batch.run();
} catch (final Exception e) {
// The error was logged in createBatch
monitor.done();
} // catch Exception
return Status.OK_STATUS;
} // run
}.schedule();
} // createAndRunBatch
/**
* @param configurationElement
* a {@link IConfigurationElement} that specifies the details and
* serialized location of an {@link Experiment} to simulate.
*/
public void createAndRunBatch(
final IConfigurationElement configurationElement) {
new Job(Messages.getString("BatchMgr.StartBatch")) { //$NON-NLS-1$
@Override
protected IStatus run(final IProgressMonitor monitor) {
monitor.beginTask(Messages.getString("BatchMgr.CrtSim"),
IProgressMonitor.UNKNOWN);
try {
final IBatch batch = createBatch(configurationElement,
monitor);
monitor.subTask(Messages.getString("BatchMgr.Run"));
batch.run();
} // try
catch (final NullPointerException e) {
// The error was logged in createBatch
monitor.done();
}
monitor.done();
return Status.OK_STATUS;
} // run
}.schedule();
} // createAndRunBatch
/**
* @return the current batch sequence number
*/
public final int getSequenceNumber() {
return sequenceNumber;
} // getSequenceNumber
/**
* @param listener
* a listener wanting to be told about changes to the manager.
*/
public void addBatchManagerListener(final IBatchManagerListener listener) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
} // addBatchManagerListener
/**
* @param listenerSync
* a listener wanting to be told about changes to the manager.
*/
public void addBatchManagerListenerSync(
final IBatchManagerListenerSync listenerSync) {
if (!listenersSync.contains(listenerSync)) {
listenersSync.add(listenerSync);
}
} // addBatchManagerListenerSync
/**
* @param listener
* a listener NOT wanting to be told about changes to the
* manager.
*/
public void removeListener(final IBatchManagerListener listener) {
listeners.remove(listener);
} // removeListener
/**
* @param listenerSync
* a listener NOT wanting to be told about changes to the
* manager.
*/
public void removeListenerSync(final IBatchManagerListenerSync listenerSync) {
listenersSync.remove(listenerSync);
} // removeListenerSync
/**
* Tell the listeners about the change.
*
* @param batchesAdded
* the {@link Batch}s added
* @param batchesRemoved
* the {@link Batch}s removed
*/
private void fireBatchManagerChanged(final IBatch[] batchesAdded,
final IBatch[] batchesRemoved) {
final BatchManagerEvent event = new BatchManagerEvent(this,
batchesAdded, batchesRemoved);
// Inform the asynchronous listeners
for (final IBatchManagerListener listener : listeners) {
try {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
listener.batchesChanged(event);
}
});
} catch (final Error ncdfe) {
// If we get this exception, it is because we're not running in
// eclipse. Just fire the event.
listener.batchesChanged(event);
} // catch
} // for
// Now inform the synchronized listeners
for (final IBatchManagerListenerSync listenerSync : listenersSync) {
try {
Display.getDefault().syncExec(new Runnable() {
public void run() {
listenerSync.batchesChangedSync(event);
}
});
} catch (final Error ncdfe) {
// If we get this exception, it is because we're not running in
// eclipse. Just fire the event.
listenerSync.batchesChangedSync(event);
} // catch
} // for
} // fireBatchManagerChanged
/**
* @see org.eclipse.stem.jobs.batch.IBatchListener#batchChanged(org.eclipse.stem.jobs.batch.BatchEvent)
*/
public void batchChanged(final BatchEvent event) {
// Has a batch stopped?
if (event.getBatchState().equals(BatchState.STOPPED)) {
// Yes
removeActiveBatch(event.getBatch());
}
} // batchChanged
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
for (final IBatch batch : batches) {
sb.append(batch.toString());
sb.append("\n");
} // for each Batch
return sb.toString();
} // toString
/**
* Discard the current {@link BatchManager} and replace it with a newly
* allocated one. This method is primarily intended to be used by JUnit test
* cases that need a {@link BatchManager} with a known initial state.
*/
static void resetBatchManager() {
INSTANCE = new BatchManager();
} // resetBatchManager
/**
* This class adapts a {@link Batch} instance that runs as an asynchronous
* Eclipse {@link Job} to be an {@link IBatch} instance. The adapter manages
* thread safety issues so that the UI thread can access the {@link IBatch}
* instance directly.
*/
protected static class BatchAdapter implements IBatch, IBatchListener,
IBatchListenerSync {
/**
* The collection of {@link IBatchListener}'s waiting to be told about
* {@link BatchEvent}s.
*/
private final List<IBatchListener> listeners = new CopyOnWriteArrayList<IBatchListener>();
/**
* The collection of {@link IBatchListenerSync}'s waiting to be told
* about {@link BatchEvent}s.
*/
private final List<IBatchListenerSync> listenersSync = new CopyOnWriteArrayList<IBatchListenerSync>();
/**
* The {@link Batch} instance to adapt. This will be an instance of
* {@link Batch} which is also an eclipse {@link Job} and runs
* asynchronously from the UI thread of eclipse. This adapter registers
* as a listener of adapted {@link Batch} and safely passes
* {@link BatchEvent}'s on to listeners in the UI or other threads.
*/
private final IBatch batch;
/**
* Constructor
*
* @param batch
* the {@link Batch} to adapt
*/
protected BatchAdapter(final IBatch batch) {
this.batch = batch;
batch.addBatchListener(this);
batch.addBatchListenerSync(this);
} // BatchAdapter
/**
* @see org.eclipse.stem.jobs.batch.IBatch#getName()
*/
public String getName() {
return batch.getName();
}
/**
* @see org.eclipse.stem.jobs.batch.IBatch#getNameWithSequenceNumber()
*/
public String getNameWithSequenceNumber() {
return batch.getNameWithSequenceNumber();
}
/**
* @see org.eclipse.stem.jobs.batch.IBatch#getExperiment()
*/
public Experiment getExperiment() {
return batch.getExperiment();
}
/**
* @see org.eclipse.stem.jobs.execution.IExecutable#getCreationTime()
*/
public Date getCreationTime() {
return batch.getCreationTime();
}
/**
* @see org.eclipse.stem.jobs.execution.IExecutable#getUniqueIDString()
*/
public String getUniqueIDString() {
return batch.getUniqueIDString();
}
/**
* @see org.eclipse.stem.jobs.batch.IBatch#getBatchState()
*/
public BatchState getBatchState() {
return batch.getBatchState();
}
/**
* @see org.eclipse.stem.jobs.batch.IBatch#getSequenceNumber()
*/
public int getSequenceNumber() {
return batch.getSequenceNumber();
}
/**
* @see org.eclipse.stem.jobs.batch.IBatch#isRunningInBackground()
*/
public boolean isRunningInBackground() {
return batch.isRunningInBackground();
}
/**
* @see org.eclipse.stem.jobs.batch.IBatch#isRunningSimulationsSequentially()
*/
public boolean isRunningSimulationsSequentially() {
return batch.isRunningSimulationsSequentially();
}
/**
* @see org.eclipse.stem.jobs.batch.IBatch#setRunningInBackground(boolean)
*/
public void setRunningInBackground(final boolean runningInBackground) {
batch.setRunningInBackground(runningInBackground);
}
/**
* @see org.eclipse.stem.jobs.batch.IBatch#setRunningSimulationsSequentially(boolean)
*/
public void setRunningSimulationsSequentially(
final boolean runningSimulationsSequentially) {
batch
.setRunningSimulationsSequentially(runningSimulationsSequentially);
}
/**
* @see org.eclipse.stem.jobs.batch.IBatch#run()
*/
public void run() {
batch.run();
}
/**
* @see org.eclipse.stem.jobs.execution.IExecutable#pause()
*/
public void pause() {
batch.pause();
}
/**
* @see org.eclipse.stem.jobs.execution.IExecutable#step()
*/
public void step() {
batch.step();
}
/**
* @see org.eclipse.stem.jobs.execution.IExecutable#reset()
*/
public void reset() {
batch.reset();
}
/**
* @see org.eclipse.stem.jobs.batch.IBatch#stop()
*/
public void stop() {
batch.stop();
}
/**
* @see org.eclipse.stem.jobs.execution.IExecutable#isRunning()
*/
public boolean isRunning() {
return batch.isRunning();
}
/**
* @see org.eclipse.stem.jobs.execution.IExecutable#isStoppable()
*/
public boolean isStoppable() {
return batch.isStoppable();
}
/**
* @see org.eclipse.stem.jobs.batch.IBatch#cancel()
*/
public boolean cancel() {
return batch.cancel();
}
/**
* @see org.eclipse.stem.jobs.batch.IBatch#addBatchListener(org.eclipse.stem.jobs.batch.IBatchListener)
*/
public void addBatchListener(final IBatchListener listener) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
} // addBatchListener
/**
* @see org.eclipse.stem.jobs.batch.IBatch#removeBatchListener(org.eclipse.stem.jobs.batch.IBatchListener)
*/
public void removeBatchListener(final IBatchListener listener) {
listeners.remove(listener);
} // removeBatchListener
/**
* @see org.eclipse.stem.jobs.batch.IBatch#addBatchListenerSync(org.eclipse.stem.jobs.batch.IBatchListenerSync)
*/
public void addBatchListenerSync(final IBatchListenerSync listener) {
if (!listenersSync.contains(listener)) {
listenersSync.add(listener);
}
} // addBatchListenerSync
/**
* @see org.eclipse.stem.jobs.batch.IBatch#removeBatchListenerSync(org.eclipse.stem.jobs.batch.IBatchListenerSync)
*/
public void removeBatchListenerSync(final IBatchListenerSync listener) {
listenersSync.remove(listener);
} // removeBatchListenerSync
/**
* Tell the listeners about the change in the {@link Batch}'s state
*
* @param batchState
* the new state of the {@link Batch}
*/
protected void fireBatchChanged(final BatchState batchState) {
final BatchEvent event = new BatchEvent(this, batchState);
for (final IBatchListener listener : listeners) {
listener.batchChanged(event);
} // for
} // fireBatchManagerChanged
/**
* Tell the listeners about the change in the {@link Batch}'s state
*
* @param batchState
* the new state of the {@link Batch}
*/
protected void fireBatchChangedSync(final BatchState batchState) {
final BatchEvent event = new BatchEvent(this, batchState);
for (final IBatchListenerSync listener : listenersSync) {
listener.batchChangedSync(event);
} // for
} // fireBatchChangedSync
/**
* This is where the adapted {@link Batch} tells us of its state changes
* and we need to adapt them to the UI thread.
*
* @see org.eclipse.stem.jobs.batch.IBatchListener#batchChanged(org.eclipse.stem.jobs.batch.BatchEvent)
*/
public void batchChanged(final BatchEvent event) {
try {
if (!Display.getDefault().isDisposed()) {
// Yes
Display.getDefault().asyncExec(new Runnable() {
public void run() {
fireBatchChanged(event.getBatchState());
}
});
} // if
} // try
catch (final NullPointerException e) {
// Nothing to do, shutting down...
} // catch NullPointerException
catch (final Error ncdfe) {
// If we get this exception, it is because we're not running in
// eclipse. Just fire the event.
fireBatchChanged(event.getBatchState());
} // catch
} // batchChanged
/**
* @see org.eclipse.stem.jobs.batch.IBatchListenerSync#batchChangedSync(org.eclipse.stem.jobs.batch.BatchEvent)
*/
public void batchChangedSync(final BatchEvent event) {
try {
if (!Display.getDefault().isDisposed()) {
// Yes
Display.getDefault().asyncExec(new Runnable() {
public void run() {
fireBatchChangedSync(event.getBatchState());
}
});
} // if
} // try
catch (final NullPointerException e) {
// Nothing to do, shutting down...
} // catch NullPointerException
catch (final Error ncdfe) {
// If we get this exception, it is because we're not running in
// eclipse. Just fire the event.
fireBatchChanged(event.getBatchState());
} // catch
} // batchChangedSync
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return batch.toString();
} // toString
} // BatchAdapter
} // BatchManager