blob: b6de283ecdf0645ae2e044d530b4d925a9e76059 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2009 Metascape, LLC.
* 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:
* Metascape - Initial API and Implementation
*
* </copyright>
*
*/
package org.eclipse.amp.escape.ide;
import java.util.ArrayList;
import java.util.List;
import org.ascape.AscapeRuntimePlugin;
import org.ascape.model.Agent;
import org.ascape.model.Scape;
import org.ascape.model.event.ControlEvent;
import org.ascape.model.event.ScapeEvent;
import org.ascape.model.event.ScapeListener;
import org.ascape.model.rule.PropogateScapeOnly;
import org.ascape.runtime.NonGraphicRunner;
import org.eclipse.amp.axf.core.EngineStateAdapter;
import org.eclipse.amp.axf.core.IEngine;
import org.eclipse.amp.axf.core.IEngineObserver;
import org.eclipse.amp.axf.core.ILifeCycleListener;
import org.eclipse.amp.axf.core.IModel;
import org.eclipse.amp.axf.ide.ModelViewManager;
import org.eclipse.amp.axf.ide.view.DelayView;
import org.eclipse.amp.axf.ide.view.RunMonitorView;
import org.eclipse.amp.axf.time.ITimeGranularity;
import org.eclipse.amp.axf.time.TimeGranularity;
import org.eclipse.amp.escape.ascape.wrap.ModelWrapperScapeListener;
import org.eclipse.amp.escape.ascape.wrap.ScapeWrapperModel;
import org.eclipse.amp.escape.ascape.wrap.ScapeWrapperModelListener;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
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.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.statushandlers.StatusManager;
/**
* The Class EclipseEscapeRunner.
*/
public class EclipseEscapeRunner extends NonGraphicRunner implements IEngine {
private static final long serialVersionUID = 1L;
protected ProjectLoader delegateLoader;
protected IProgressMonitor modelMonitor;
private IModel model;
private IProject project;
private IStatus finishStatus = Status.OK_STATUS;
private ITimeGranularity updateGranularity = TimeGranularity.DEFAULT_GRANULARITY;
public final static long KILL_AFTER_NO_RESPONSE_TIME = 5000;
// would be nice to have this in Ascape proper but not worth IP submission
private boolean closeOnStop = false;
boolean modelShutdown = false;
private Job escapeRun;
private ArrayList<IEngineObserver> observers = new ArrayList<IEngineObserver>();
private EngineStateAdapter engineStateAdapter = new EngineStateAdapter(isRunning(), isStep(), isPaused());
/**
* Open.
*
* @param modelResource
* the model resource
* @param modelClassName
* the model class name
* @param modelName
* the model name
*/
public void open(final IResource modelResource, final String modelClassName, String modelName) {
open(modelResource, modelClassName, modelName, new String[0]);
}
/**
* Open. Wait until execution has stopped to return.
*
* @param projectResource
* the project resource
* @param modelClassName
* the model class name
* @param modelName
* the model name
* @param args
* the args
*/
public void open(final IResource projectResource, final String modelClassName, String modelName, String[] args) {
open(projectResource, modelClassName, modelName, args, true);
}
/**
* Open.
*
* @param projectResource
* the project resource
* @param modelClassName
* the model class name
* @param modelName
* the model name
* @param args
* the args
* @param block
* true if this method should only return on execution completion.
*/
public void open(final IResource projectResource, final String modelClassName, String modelName, String[] args,
boolean block) {
openNotStart(projectResource, modelClassName, modelName, args);
start();
if (block) {
while (!modelShutdown) {
try {
Thread.sleep(30L);
} catch (InterruptedException e) {
//
}
}
}
}
/**
* Open all Files and initialize the Simulation, but don't run it.
*
* @param projectResource
* @param modelClassName
* @param modelName
* @param args
*/
public void openNotStart(final IResource projectResource, final String modelClassName, String modelName, String[] args) {
this.project = projectResource.getProject();
setStartOnOpen(false);
setAutoRestart(false);
addConsole(modelName);
AscapeRuntimePlugin.getDefault();
try {
delegateLoader = new ProjectLoader(projectResource.getProject());
} catch (LoaderCreationException e) {
StatusManager.getManager().handle(
new Status(Status.WARNING, project.getName(),
"Couldn't create project classloader; falling back to default loader", e));
}
Scape newAgent;
try {
newAgent = (Scape) instanceFromName(modelClassName);
} catch (RuntimeException e) {
StatusManager.getManager().handle(
new Status(Status.ERROR, project.getName(), "Could not read class: " + modelClassName
+ ". Please ensure that the class exists in class path.", e), StatusManager.SHOW | StatusManager.LOG);
dispose();
throw e;
}
newAgent.setName(modelName);
setRootScape(newAgent);
model = new ScapeWrapperModel(getRootScape());
ModelViewManager.getInstance().register(model, this, getRootScape());
try {
open(args, true);
} catch (RuntimeException e) {
closeFinally();
throw e;
}
final List<ScapeListener> newListeners = new ArrayList<ScapeListener>();
getRootScape().executeOnRoot(new PropogateScapeOnly() {
private static final long serialVersionUID = -6277857634508661314L;
@SuppressWarnings("unchecked")
@Override
public void execute(Agent agent) {
newListeners.addAll(((Scape) agent).getScapeListeners());
super.execute(agent);
}
});
for (ScapeListener scapeListener : newListeners) {
if (!(scapeListener instanceof ModelWrapperScapeListener)) {
ScapeWrapperModelListener listenerWrapper = new ScapeWrapperModelListener(getRootScape(), scapeListener);
model.addModelListener(listenerWrapper);
}
}
}
protected void addConsole(String modelName) {
final MessageConsole eclipseConsole = new MessageConsole("Run " + modelName, null);
getEnvironment().getConsole().setIMessageStream(new MessageAdapter(eclipseConsole));
IConsole[] consules = { eclipseConsole };
ConsolePlugin.getDefault().getConsoleManager().addConsoles(consules);
eclipseConsole.activate();
}
private void removeModel() {
if (ModelViewManager.getInstance().getModels().contains(model)) {
ModelViewManager.getInstance().remove(model);
}
modelShutdown = true;
}
private void dispose() {
if (delegateLoader != null) {
delegateLoader.restore();
}
delegateLoader = null;
}
@Override
public void start() {
escapeRun = new Job("Run " + getModel().getName()) {
@Override
protected IStatus run(IProgressMonitor runMonitor) {
try {
setMonitor(runMonitor);
RunMonitorView runMonitorView = new RunMonitorView(runMonitor);
getModel().addModelListener(runMonitorView);
DelayView delayView = new DelayView();
getModel().addModelListener(delayView);
// do {
try {
EclipseEscapeRunner.this.run();
if (isCloseOnStop()) {
close();
}
} catch (RuntimeException e) {
closeFinally();
throw e;
}
// } while (isRestartRequested());
getModel().removeModelListener(runMonitorView);
} catch (RuntimeException e) {
String message = "An exception occurred: ";
if (model != null) {
message = "An exception occurred when running: " + getModel().getName();
}
StatusManager.getManager().handle(new Status(Status.ERROR, project.getName(), message, e),
StatusManager.SHOW | StatusManager.LOG);
if (isCloseOnStop()) {
close();
} else {
stop();
}
}
return Status.OK_STATUS;
}
@Override
protected void canceling() {
close();
}
};
escapeRun.setPriority(Job.SHORT);
escapeRun.schedule();
}
/**
*
* @see org.ascape.runtime.Runner#close()
*/
@SuppressWarnings("deprecation")
@Override
public void close() {
if (!isCloseRequested()) {
setCloseRequested(true);
Thread closeThread = new Thread(this, "Close Escape Model") {
@Override
public void run() {
setPaused(false);
EclipseEscapeRunner.super.close();
}
};
closeThread.start();
}
if (modelMonitor != null) {
modelMonitor.setTaskName("Waiting for Close...");
}
try {
long elapsed = 0;
while (isCloseRequested() && isThreadAlive() && elapsed <= KILL_AFTER_NO_RESPONSE_TIME) {
if (elapsed >= KILL_AFTER_NO_RESPONSE_TIME) {
if (modelMonitor != null) {
modelMonitor.setTaskName("Forcing Close");
}
EscapeIDEPlugin.warn("Couldn't close model normally, forcing stop.", null);
if (escapeRun != null) {
escapeRun.getThread().stop();
}
closeFinally();
}
Thread.sleep(30);
elapsed += 30;
}
} catch (InterruptedException e) {
//
}
if (modelMonitor != null) {
modelMonitor.setTaskName("Closed");
}
modelShutdown = true;
}
@Override
public void closeFinally() {
super.closeFinally();
modelShutdown = true;
((ScapeWrapperModel) getModel()).setClosed(true);
removeModel();
dispose();
}
/**
* Gets the finish status.
*
* @return the finish status
*/
public IStatus getFinishStatus() {
return finishStatus;
}
/**
* Sets the finish status.
*
* @param finishStatus
* the new finish status
*/
public void setFinishStatus(IStatus finishStatus) {
this.finishStatus = finishStatus;
}
public IModel getModel() {
return model;
}
public int getIterationsPerRedraw() {
return getScape().getIterationsPerRedraw();
}
@Override
public void respondControl(final org.ascape.model.event.ControlEvent control) {
new Thread(new Runnable() {
public void run() {
EclipseEscapeRunner.super.respondControl(control);
}
}).start();
}
public void control(org.eclipse.amp.axf.core.EngineControl control) {
super.respondControl(adaptEngineControl(control));
}
@Override
public void waitForViewsUpdate() {
super.waitForViewsUpdate();
/*
* Running, paused and step are not all update using setter methods. So this
* is a workaround to not miss any state changes.
*/
updateStateAdapter();
}
@Override
public void setPaused(boolean paused) {
super.setPaused(paused);
updateStateAdapter();
}
@Override
public void setStep(boolean step) {
super.setStep(step);
updateStateAdapter();
}
@Override
public void setRunning(boolean running) {
super.setRunning(running);
updateStateAdapter();
}
private void updateStateAdapter() {
if (engineStateAdapter.update(isRunning(), isStep(), isPaused())) {
for (IEngineObserver observer : observers) {
observer.engineStateChanged(this, engineStateAdapter.getState());
}
}
}
public void addEngineObserver(IEngineObserver observer) {
if (!observers.contains(observer)) {
observers.add(observer);
}
}
public void removeEngineObserver(IEngineObserver observer) {
if (observers.contains(observer)) {
observers.remove(observer);
}
}
private ControlEvent adaptEngineControl(org.eclipse.amp.axf.core.EngineControl control) {
int id = 0;
switch (control) {
case OPEN:
id = ControlEvent.REQUEST_OPEN;
break;
case START:
id = ControlEvent.REQUEST_START;
break;
case RESTART:
id = ControlEvent.REQUEST_RESTART;
break;
case STEP:
id = ControlEvent.REQUEST_STEP;
break;
case PAUSE:
id = ControlEvent.REQUEST_PAUSE;
break;
case RESUME:
id = ControlEvent.REQUEST_RESUME;
break;
case STOP:
id = ControlEvent.REQUEST_STOP;
break;
case CLOSE:
id = ControlEvent.REQUEST_CLOSE;
break;
case SAVE:
id = ControlEvent.REQUEST_SAVE;
break;
default:
throw new RuntimeException("Couldn't convert control: " + control);
}
return new org.ascape.model.event.ControlEvent(getScape(), id);
}
@Override
public void notify(final ScapeEvent event, final ScapeListener listener) {
listener.scapeNotification(event);
}
public void setIterationsPerRedraw(int iterations) {
getScape().setIterationsPerRedraw(iterations);
}
/**
* Gets the scape.
*
* @return the scape
*/
public Scape getScape() {
return getRootScape();
}
/**
* Gets the monitor.
*
* @return the monitor
*/
public IProgressMonitor getMonitor() {
return modelMonitor;
}
/**
* Sets the monitor.
*
* @param modelMonitor
* the new monitor
*/
public void setMonitor(IProgressMonitor modelMonitor) {
this.modelMonitor = modelMonitor;
}
/**
*
* @see org.eclipse.amp.axf.core.IEngine#observationComplete(org.eclipse.amp.axf.core.ILifeCycleListener)
*/
public void observationComplete(ILifeCycleListener observer) {
// respondControl(new org.ascape.model.event.ControlEvent(getScape(),
// ControlEvent.REPORT_LISTENER_UPDATED));
}
/**
* @return
* @see org.eclipse.amp.axf.core.IEngine#getUpdateGranularity()
*/
public ITimeGranularity getUpdateGranularity() {
return updateGranularity;
}
/**
* @param granularity
* @see org.eclipse.amp.axf.core.IEngine#setUpdateGranularity(org.eclipse.amp.axf.time.ITimeGranularity)
*/
public void setUpdateGranularity(ITimeGranularity granularity) {
this.updateGranularity = granularity;
}
public IProject getExecutingProject() {
return project;
}
/**
* Should the model exit upon a stop command?
*
* @see #setCloseOnStop
* @return whether to complete run on model stop; default is false
*/
public boolean isCloseOnStop() {
return closeOnStop;
}
/**
* Specifies whether the runner should exit upon a model stop. The default
* behavior is false, which leaves the model open (and typically able to
* restart) until explicitly closed. For batch runs we don't want the model to
* stay open as that requires a view notification and response to close it.
* It's probably cleaner to allow the runner to exit immediately rather than
* wait for a listener to trigger one.
*
* Note that the model will not stop in the case where a restart has been
* requested, and listener's will be notified of a model stop before actually
* closing. This gives output control listeners -- for a parameter sweep
* control, for example -- an opportunity to set restart to request a restart.
* It also implies that it makes no sense to have autoRestart true if this
* value is also true. Since auto-restart is by default true, be sure to set
* it to false.
*
* This setting can be safely changed during a model run.
*
* @param closeOnStop
* whether to complete run on model stop; default is false
*/
public void setCloseOnStop(boolean closeOnStop) {
this.closeOnStop = closeOnStop;
}
public boolean isThreadAlive() {
return getModelThread() != null && getModelThread().isAlive();
}
public Job getEscapeRun() {
return escapeRun;
}
}