/******************************************************************************* | |
* Copyright (c) 2008 The University of York. | |
* This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License 2.0 | |
* which is available at https://www.eclipse.org/legal/epl-2.0/ | |
* | |
* Contributors: | |
* Dimitrios Kolovos - initial API and implementation | |
******************************************************************************/ | |
package org.eclipse.epsilon.workflow.tasks; | |
import java.io.File; | |
import java.io.PrintWriter; | |
import java.io.StringWriter; | |
import java.net.URI; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.stream.Collectors; | |
import org.apache.tools.ant.BuildException; | |
import org.apache.tools.ant.Project; | |
import org.eclipse.epsilon.common.parse.problem.ParseProblem; | |
import org.eclipse.epsilon.common.util.StringUtil; | |
import org.eclipse.epsilon.eol.IEolModule; | |
import org.eclipse.epsilon.eol.exceptions.models.EolModelLoadingException; | |
import org.eclipse.epsilon.eol.exceptions.models.EolModelNotFoundException; | |
import org.eclipse.epsilon.eol.execute.context.IEolContext; | |
import org.eclipse.epsilon.eol.execute.context.Variable; | |
import org.eclipse.epsilon.eol.models.IModel; | |
import org.eclipse.epsilon.eol.models.IReflectiveModel; | |
import org.eclipse.epsilon.eol.models.ModelReference; | |
import org.eclipse.epsilon.eol.models.ModelRepository; | |
import org.eclipse.epsilon.eol.models.ReflectiveModelReference; | |
import org.eclipse.epsilon.eol.tools.EolSystem; | |
import org.eclipse.epsilon.eol.types.EolPrimitiveType; | |
import org.eclipse.epsilon.profiling.Profiler; | |
import org.eclipse.epsilon.profiling.ProfilingExecutionListener; | |
import org.eclipse.epsilon.workflow.tasks.hosts.HostManager; | |
import org.eclipse.epsilon.workflow.tasks.nestedelements.ModelNestedElement; | |
import org.eclipse.epsilon.workflow.tasks.nestedelements.ParameterNestedElement; | |
import org.eclipse.epsilon.workflow.tasks.nestedelements.VariableNestedElement; | |
public abstract class ExecutableModuleTask extends EpsilonTask { | |
/** | |
* Allow Epsilon Tasks to have arbitrary nested property settings | |
* @author Horacio Hoyos Rodriguez | |
* @since 1.6 | |
*/ | |
protected static class ModuleProperty { | |
String name, value; | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
public String getValue() { | |
return value; | |
} | |
public void setValue(String value) { | |
this.value = value; | |
} | |
public static Map<String, ?> toMap(Collection<ModuleProperty> properties) { | |
return properties.stream() | |
.collect(Collectors.toMap( | |
ModuleProperty::getName, ModuleProperty::getValue) | |
); | |
} | |
} | |
protected List<ModelNestedElement> modelNestedElements = new ArrayList<>(); | |
protected List<VariableNestedElement> usesVariableNestedElements = new ArrayList<>(); | |
protected List<VariableNestedElement> exportsVariableNestedElements = new ArrayList<>(); | |
protected List<ParameterNestedElement> parameterNestedElements = new ArrayList<>(); | |
protected File src; | |
protected String code = ""; | |
protected IEolModule module; | |
protected boolean assertions = true; | |
protected String uri; | |
protected Object result; | |
private boolean isGUI = true; | |
private boolean isDebug = false; | |
protected boolean setBeans = false; | |
protected boolean fine; | |
/** | |
* Provide a specific module class implementation at runtime | |
* @since 1.6 | |
*/ | |
protected String moduleImplementationClass; | |
/** Module specific settings | |
* @since 1.6 | |
*/ | |
List<ModuleProperty> properties = new ArrayList<>(); | |
static { | |
HostManager.getHost().initialise(); | |
} | |
public ModelNestedElement createModel() { | |
ModelNestedElement model = new ModelNestedElement(); | |
modelNestedElements.add(model); | |
return model; | |
} | |
public VariableNestedElement createUses() { | |
VariableNestedElement variableNestedElement = new VariableNestedElement(); | |
usesVariableNestedElements.add(variableNestedElement); | |
return variableNestedElement; | |
} | |
public VariableNestedElement createExports() { | |
VariableNestedElement variableNestedElement = new VariableNestedElement(); | |
exportsVariableNestedElements.add(variableNestedElement); | |
return variableNestedElement; | |
} | |
public ParameterNestedElement createParameter() { | |
ParameterNestedElement parameterNestedElement = new ParameterNestedElement(); | |
parameterNestedElements.add(parameterNestedElement); | |
return parameterNestedElement; | |
} | |
@SuppressWarnings("unchecked") | |
protected void configureModule() throws EolModelNotFoundException, BuildException, EolModelLoadingException { | |
// We can only run these if we're inside a real Eclipse instance: | |
// we must avoid these calls if we're running the Ant task inside | |
// a JUnit test | |
HostManager.getHost().addNativeTypeDelegates(module); | |
HostManager.getHost().configureUserInput(module, isGUI()); | |
module.getContext().setExtendedProperties(getExtendedProperties()); | |
HostManager.getHost().addStopCapabilities(getProject(), module); | |
EolSystem system = new EolSystem(); | |
system.setContext(module.getContext()); | |
module.getContext().setAssertionsEnabled(assertions); | |
module.getContext().getFrameStack().put( | |
Variable.createReadOnlyVariable("System", system), | |
Variable.createReadOnlyVariable("null", null) | |
); | |
if (setBeans) { | |
Project project = getProject(); | |
module.getContext().getFrameStack().put(Variable.createReadOnlyVariable("project", project)); | |
addVariables(module.getContext(), | |
project.getProperties(), project.getUserProperties(), | |
project.getCopyOfReferences(), project.getCopyOfTargets()); | |
} | |
populateModelRepository(false); | |
accessParameters(); | |
useVariables(); | |
} | |
protected void addVariables(IEolContext context, @SuppressWarnings("unchecked") Map<String, ?>... variableMaps) { | |
for (Map<String, ?> variableMap : variableMaps) { | |
for (String key : variableMap.keySet()) { | |
module.getContext().getFrameStack().put(Variable.createReadOnlyVariable(key, variableMap.get(key))); | |
} | |
} | |
} | |
protected void useResults() throws Exception { | |
exportVariables(); | |
examine(); | |
} | |
protected void populateModelRepository(Boolean mustReload) throws EolModelNotFoundException, EolModelLoadingException { | |
ModelRepository repository = module.getContext().getModelRepository(); | |
if (mustReload) { | |
repository.dispose(); | |
} | |
ModelRepository projectRepository = getProjectRepository(); | |
for (ModelNestedElement modelNestedElement : modelNestedElements) { | |
IModel model = projectRepository.getModelByName(modelNestedElement.getRef()); | |
if (mustReload) { | |
model.load(); | |
} | |
ModelReference reference = createReference(model); | |
if (modelNestedElement.getAs() != null) { | |
reference.setName(modelNestedElement.getAs()); | |
} | |
if (modelNestedElement.getAlias() != null) { | |
reference.getAliases().addAll(StringUtil.split(modelNestedElement.getAlias(), ",")); | |
} | |
repository.addModel(reference); | |
} | |
} | |
private ModelReference createReference(IModel model) { | |
if (model instanceof IReflectiveModel) { | |
return new ReflectiveModelReference((IReflectiveModel)model); | |
} else { | |
return new ModelReference(model); | |
} | |
} | |
private void accessParameters() { | |
for (ParameterNestedElement parameterNestedElement : parameterNestedElements) { | |
module.getContext().getFrameStack().putGlobal( | |
Variable.createReadOnlyVariable( | |
parameterNestedElement.getName(), | |
parameterNestedElement.getValue() | |
)); | |
} | |
} | |
private void useVariables() throws BuildException { | |
for (VariableNestedElement usesVariableNestedElement : usesVariableNestedElements) { | |
useVariable(usesVariableNestedElement.getRef(), | |
usesVariableNestedElement.getAs(), | |
usesVariableNestedElement.isOptional(), | |
usesVariableNestedElement.isAnt()); | |
} | |
} | |
private void exportVariables() { | |
for (VariableNestedElement exportVariableNestedElement : exportsVariableNestedElements) { | |
exportVariable( | |
exportVariableNestedElement.getRef(), | |
exportVariableNestedElement.getAs(), | |
exportVariableNestedElement.isOptional(), | |
exportVariableNestedElement.isAnt()); | |
} | |
} | |
@Override | |
public String getTaskName() { | |
if (src != null) { | |
return super.getTaskName() + " - " + src.getName(); | |
} | |
else { | |
return super.getTaskName(); | |
} | |
} | |
@Override | |
public void executeImpl() throws BuildException { | |
try { | |
parseModule(); | |
if (src != null && profile) { | |
Profiler.INSTANCE.start(src.getName(), "", module); | |
} | |
configureModule(); | |
initialize(); | |
if (fine) { | |
module.getContext().getExecutorFactory().addExecutionListener(new ProfilingExecutionListener()); | |
} | |
if (!isDebug() || !HostManager.getHost().supportsDebugging()) { | |
result = module.execute(); | |
} | |
else { | |
HostManager.getHost().debug(module, getSrc()); | |
} | |
useResults(); | |
if (src != null && profile) Profiler.INSTANCE.stop(src.getName()); | |
} | |
catch (Throwable t) { | |
if (profile) Profiler.INSTANCE.stop(src.getName()); | |
if (t instanceof BuildException) { | |
throw (BuildException) t; | |
} | |
else { | |
StringWriter sw = new StringWriter(); | |
t.printStackTrace(new PrintWriter(sw)); | |
log("EXCEPTION: " + sw.toString(), Project.MSG_ERR); | |
throw new BuildException(t); | |
} | |
} | |
} | |
private void parseModule() throws Exception { | |
module = createModule(); | |
if (src != null) { | |
module.parse(src); | |
} | |
else if (uri != null) { | |
module.parse(URI.create(uri)); | |
} | |
else { | |
module.parse(code); | |
} | |
if (module.getParseProblems().size() > 0) { | |
for (ParseProblem problem : module.getParseProblems()) { | |
log(problem.toString(), Project.MSG_ERR); | |
} | |
} | |
} | |
public void addText(String msg) { | |
if ((msg != null) && (src == null)) { | |
code += getProject().replaceProperties(msg); | |
} | |
} | |
public File getSrc() { | |
return src; | |
} | |
public void setSrc(File src) { | |
this.src = src; | |
} | |
public void setUri(String uri) { | |
this.uri = uri; | |
} | |
public String getUri() { | |
return uri; | |
} | |
public boolean isAssertions() { | |
return assertions; | |
} | |
public void setAssertions(boolean assertions) { | |
this.assertions = assertions; | |
} | |
/** | |
* Changes whether Epsilon's graphical user input facilities should be enabled or not. | |
* By default, they are enabled for all tasks except for the EUnit Ant task, in which | |
* they are disabled. | |
*/ | |
public void setGUI(boolean gui) { | |
this.isGUI = gui; | |
} | |
/** | |
* Returns whether Epsilon's graphical user input facilities should be enabled or not. | |
* @see #setGUI(boolean) | |
*/ | |
public boolean isGUI() { | |
return isGUI; | |
} | |
/** | |
* Changes whether the debugger should be used (<code>true</code>) or not (<code>false</code>) | |
* for this module. By default, it is not used. | |
*/ | |
public void setDebug(boolean isDebug) { | |
this.isDebug = isDebug; | |
} | |
/** | |
* Returns whether the debugger will be used (<code>true</code>) or not (<code>false</code>). | |
*/ | |
public boolean isDebug() { | |
return isDebug; | |
} | |
protected void useVariable(final String var, final String as, final boolean optional, boolean ant) { | |
Variable usedVariable = null; | |
if (ant) { | |
usedVariable = new Variable(var, getProject().getProperty(var), EolPrimitiveType.String); | |
} | |
else { | |
usedVariable = getProjectStackFrame().get(var); | |
} | |
// FIXME : Remove this hack using a proper design! | |
if (usedVariable != null) { | |
Object value = usedVariable.getValue(); | |
if (value instanceof IModel) { | |
IModel model = (IModel) value; | |
ModelReference reference = createReference(model); | |
if (as != null) { | |
reference.setName(as); | |
} | |
else { | |
reference.setName(var); | |
} | |
module.getContext().getModelRepository().addModel(reference); | |
return; | |
} | |
} | |
// ENDFIXME | |
if (usedVariable == null) { | |
if (getProject().getProperty(var) != null) { | |
usedVariable = new Variable(var, getProject().getProperty(var), EolPrimitiveType.String); | |
} | |
} | |
if (usedVariable == null && !optional) throw new BuildException("Undefined variable " + var); | |
if (as != null) { | |
usedVariable.setName(as); | |
} | |
module.getContext().getFrameStack().putGlobal(usedVariable); | |
} | |
protected void exportVariable(String var, String as, final boolean optional, boolean ant) { | |
Variable exportedVariable = module.getContext().getFrameStack().get(var); | |
// FIXME : 2nd part of the hack | |
if (exportedVariable == null) { | |
IModel model = module.getContext().getModelRepository().getModelByNameSafe(var); | |
if (model != null) { | |
exportedVariable = Variable.createReadOnlyVariable(var, model); | |
} | |
} | |
// ENDFIXME | |
if (exportedVariable != null) { | |
if (as != null) { | |
exportedVariable.setName(as); | |
} | |
if (ant) { | |
getProject().setProperty(exportedVariable.getName(), module.getContext().getPrettyPrinterManager().print(exportedVariable.getValue())); | |
} | |
else { | |
getProjectStackFrame().put(exportedVariable); | |
} | |
} else { | |
if (!optional) { | |
throw new BuildException("Variable " + var + " is undefined"); | |
} | |
} | |
} | |
public void setSetBeans(boolean setBeans) { | |
this.setBeans = setBeans; | |
} | |
public boolean isSetBeans() { | |
return setBeans; | |
} | |
public boolean isFine() { | |
return fine; | |
} | |
public void setFine(boolean fine) { | |
this.fine = fine; | |
} | |
protected abstract void initialize() throws Exception; | |
protected abstract void examine() throws Exception; | |
protected abstract IEolModule createDefaultModule() throws Exception; | |
protected IEolModule createModule() throws Exception { | |
IEolModule result = moduleImplementationClass == null ? createDefaultModule() : createAlternativeModule(); | |
Set<String> requiredProperties = result.getConfigurationProperties(); | |
Map<String, Object> props = new HashMap<>(requiredProperties.size()); | |
for (String rp : requiredProperties) { | |
for (ModuleProperty mp : properties) { | |
if (rp.equals(mp.name)) { | |
props.put(mp.name, mp.value); | |
} | |
} | |
} | |
result.configure(props); | |
return result; | |
} | |
public String getModuleImplementation() { | |
return moduleImplementationClass; | |
} | |
public void setModuleImplementation(String moduleImplementation) { | |
this.moduleImplementationClass = moduleImplementation; | |
} | |
/** Ant constructor for nested elements */ | |
public ModuleProperty createModuleProperty() { | |
ModuleProperty property = new ModuleProperty(); | |
properties.add(property); | |
return property; | |
} | |
/** | |
* Create an alternative module instance from the provided qualified name of the module class | |
* @since 1.6 | |
* @return The instantiated and configured IEolModule. | |
* @throws Exception If there is an error instantiating the module class and/or configuring the module | |
*/ | |
protected IEolModule createAlternativeModule() throws Exception { | |
@SuppressWarnings("unchecked") | |
Class<? extends IEolModule> clazz = (Class<? extends IEolModule>) Class.forName(moduleImplementationClass); | |
IEolModule module = clazz.getConstructor().newInstance(); | |
module.configure(ModuleProperty.toMap(properties)); | |
return module; | |
} | |
} |