/**
* Copyright (c) 2009-2011, The HATS Consortium. All rights reserved.
* This file is licensed under the terms of the Modified BSD License.
*/
package org.absmodels.abs.plugin.actions;
import static org.absmodels.abs.plugin.util.Constants.*;
import static org.absmodels.abs.plugin.util.UtilityFunctions.*;
import java.io.*;
import java.util.ArrayList;
import org.absmodels.abs.plugin.builder.AbsNature;
import org.absmodels.abs.plugin.exceptions.*;
import org.absmodels.abs.plugin.internal.NoModelException;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.osgi.framework.Bundle;
import abs.backend.maude.MaudeCompiler;
import abs.common.WrongProgramArgumentException;
import abs.frontend.ast.Model;
import abs.frontend.delta.DeltaModellingException;
public class MaudeJob extends Job{
private Process process;
private IProject project;
private File destFile;
private boolean exec;
private boolean realTime;
private boolean abort;
private boolean partialExec;
private int steps;
private String product;
private String mainBlock; /* may be null or empty */
/**
* MaudeJobs created with this constructor will generate a .maude file out of a given project and may execute
* the generated file afterwards.
* @param project The project to be executed
* @param realTime Indicates, if the realTime scheduler should be used. Note, that this functionality has
* never been tested or used
* @param exec indicates, if the generated .maude file should be executed
*/
public MaudeJob(IProject project, boolean realTime, boolean exec){
super("Maude Job");
this.project = project;
this.realTime = realTime;
this.exec = exec;
this.partialExec = false;
setUser(true);
}
/**
* MaudeJobs created with this constructor will generate a .maude file out of a given project and execute
* Maude with a command resulting in a partial execution with only a given number of steps.
* @param project The project to be executed
* @param realTime Indicates, if the realTime scheduler should be used. Note, that this functionality has
* never been tested or used
* @param exec (probably superfluous parameter here) indicates, if the generated .maude file should be executed
* @param steps Number of steps to be executed
*/
public MaudeJob(IProject project, boolean realTime, boolean exec, int steps){
super("Maude Job");
this.project = project;
this.realTime = realTime;
this.exec = exec;
this.partialExec = true;
this.steps = steps;
setUser(true);
}
@Override
public IStatus run(IProgressMonitor monitor){
//Set title and totalWork of the process according to clicked button
if(exec){
monitor.beginTask("Executing Maude", 11);
} else{
monitor.beginTask("Compiling Maude", 6);
}
return runJob(monitor);
}
public IStatus runJob(IProgressMonitor monitor) {
abort = false;
boolean failed = false;
StringBuffer output = new StringBuffer();
AbsNature nature = getAbsNature(project);
if(nature == null){
return new Status(IStatus.INFO, PLUGIN_ID, "Could not compile current selection. Project is not an ABS project.");
}
//Compile Maude Code
monitor.subTask("Compiling ABS model to Maude");
try{
if(!abort) compileMaude(monitor,nature);
} catch(CoreException e1) {
return new Status(IStatus.ERROR, PLUGIN_ID, "Fatal error while compilig", e1);
} catch (IOException e2) {
return new Status(IStatus.ERROR, PLUGIN_ID, "Fatal error while compilig", e2);
} catch (ParseException e4) {
return new Status(IStatus.INFO, PLUGIN_ID, MAUDE_ERROR, "Could not compile current selection. Code has parse errors.", e4);
} catch (TypeCheckerException e5) {
return new Status(IStatus.INFO, PLUGIN_ID, MAUDE_ERROR, "Could not compile current selection. Code has type errors.", e5);
} catch (WrongProgramArgumentException e) {
return new Status(IStatus.ERROR, PLUGIN_ID, MAUDE_ERROR, "Could not compile current selection.", e);
} catch (DeltaModellingException e) {
return new Status(IStatus.ERROR, PLUGIN_ID, MAUDE_ERROR, "Could not compile current selection.", e);
} catch (NoModelException e) {
return new Status(IStatus.INFO, PLUGIN_ID, "No ABS model in project");
}
monitor.worked(5);
//Execute generated Maude code
monitor.subTask("Executing generated Maude code");
if(exec){
try{
if(!abort) failed = !executeMaude(output);
} catch (IOException e1) {
return new Status(IStatus.INFO, PLUGIN_ID, MAUDE_ERROR_MAUDE_PATH, "Encountered IOException while executing Maude (probably misconfigured location of maude executable)", e1);
} catch (InterruptedException e2) {
return new Status(IStatus.ERROR, PLUGIN_ID, "Fatal error while executing Maude", e2);
} finally{
if(!monitor.isCanceled()){
monitor.worked(5);
monitor.done();
}
}
}
//If an error was encountered during Maude execution, the info code of the status is set to ERROR_MAUDE, otherwise MAUDE_INFO.
if(!abort){
if(failed){
return new Status(IStatus.OK, PLUGIN_ID , MAUDE_ERROR_MAUDE, output.toString(), null);
} else{
if(exec){
return new Status(IStatus.OK, PLUGIN_ID, MAUDE_INFO, output.toString(), null);
} else{
return new Status(IStatus.OK, PLUGIN_ID, MAUDE_OK, output.toString(), null);
}
}
} else{
monitor.setCanceled(true);
return new Status(IStatus.INFO, PLUGIN_ID, MAUDE_USER_ABORT, null, null);
}
}
@Override
protected void canceling() {
abort = true;
if(process != null){
process.destroy();
}
super.cancel();
}
/**
* Compiles an ABS project into a .maude file. If an ABS file is currently opened in the editor, the project containing this file
* will be compiled, otherwise the project currently selected in the project explorer will be compiled.
* @throws ParseException Is thrown, if the project which is compiled has parse errors
* @throws TypeCheckerException Is thrown, if the project which is compiled has type errors
* @throws DeltaModellingException
* @throws WrongProgramArgumentException
* @throws NoModelException
*/
private void compileMaude(IProgressMonitor monitor, AbsNature nature) throws CoreException, IOException, ParseException, TypeCheckerException, WrongProgramArgumentException, DeltaModellingException, NoModelException {
PrintStream ps = null;
FileInputStream fis = null;
try{
String path = nature.getProjectPreferenceStore().getString(MAUDE_PATH);
IFolder folder = project.getFolder(path);
prepareFolder(monitor, folder);
String fileName = project.getName()+".maude";
final IFile wspaceFile = folder.getFile(fileName);
/* generateMaude only knows how to fill PrintStreams */
final File tmpFile = File.createTempFile(fileName,null);
ps = new PrintStream(tmpFile);
//Get model, check for errors and throw respective exception
Model model = nature.getCompleteModel();
if (model == null)
throw new NoModelException();
if(model.hasParserErrors()){
throw new ParseException(model.getParserErrors());
}
if (getProduct() != null) {
// work on a copy:
model = model.parseTreeCopy();
model.flattenForProduct(getProduct());
model.flushCache(); // #335
}
if(model.hasTypeErrors()){
throw new TypeCheckerException(model.typeCheck());
}
String mb = getMainBlock();
if (mb != null && mb.isEmpty())
mb = null;
// KLUDGE: use default values for clock limit, resource cost for now
if(realTime){
model.generateMaude(ps, MaudeCompiler.SIMULATOR.EQ_TIMED, mb, 100, 0);
} else{
model.generateMaude(ps, MaudeCompiler.SIMULATOR.RL, mb, 100, 0);
}
ps.close();
fis = new FileInputStream(tmpFile);
if (wspaceFile.exists())
wspaceFile.setContents(fis, true, false, monitor);
else
wspaceFile.create(fis, true, monitor);
fis.close();
tmpFile.delete();
destFile = new File(project.getLocation().append(path).toFile(),fileName);
} finally{
if (ps != null){
ps.flush();
ps.close();
}
if (fis != null) {
fis.close();
}
}
}
/**
* Eclipsism for recursively creating folder hierarchy.
* @author stolz
*/
static void prepareFolder(IProgressMonitor monitor, IFolder folder) throws CoreException{
IContainer parent = folder.getParent();
if (parent instanceof IFolder)
prepareFolder(monitor,(IFolder)parent);
if (!folder.exists())
folder.create(true,true,monitor);
}
/**
* Runs configured Maude executable with the earlier generated .maude file in a separate process. Appends output of the process
* to output String. Return value depends on which stream of the process was copied. If the error stream contains characters, this
* stream will be copied, otherwise the output stream.
* @return true if successful (output stream contained data), false else (error stream contained data)
*/
private boolean executeMaude(StringBuffer output) throws IOException, InterruptedException{
PrintWriter pw = null;
try{
ArrayList<String> args = new ArrayList<String>();
args.add(getMaudePath());
args.add("-no-banner");
args.add(getFilePath());
if(!abort) process = Runtime.getRuntime().exec(args.toArray(new String[args.size()]));
//Write Maude command and execute it
OutputStream os = process.getOutputStream();
pw = new PrintWriter(os);
if(partialExec){
pw.write(getMaudeCommand(steps));
} else{
pw.write(MAUDE_COMMAND);
}
} finally{
if(pw != null){
pw.close();
}
}
return getProcessOutput(process, output);
}
private String getFilePath(){
if(Platform.getOS().equals(Platform.OS_WIN32)){
return convertToCygDrivePath(destFile);
} else {
return destFile.getAbsolutePath();
}
}
private String getMaudePath() {
return getDefaultPreferenceStore().getString(MAUDE_EXEC_PATH);
}
private String convertToCygDrivePath(File destFile) {
return "/cygdrive/" + convertToUnixPath(destFile);
}
private String convertToUnixPath(File destFile) {
return destFile.getAbsolutePath().replaceFirst(":", "").replace('\\', '/');
}
public String getProduct() {
return product;
}
public void setProduct(String product) {
this.product = product;
}
public void setMainBlock(String mainBlock) {
this.mainBlock = mainBlock;
}
public String getMainBlock() {
return mainBlock;
}
}