Java tutorial
/* * Copyright 2009-2013 University of Hildesheim, Software Systems Engineering * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Stack; import org.apache.commons.lang.SystemUtils; import de.uni_hildesheim.sse.easy_producer.instantiator.Bundle; import de.uni_hildesheim.sse.easy_producer.instantiator.model.artifactModel.Path; import de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel.Rule.Side; import de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel.RuleExecutionResult.Status; import de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel.execOperand.ExecutableOperand; import de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel.execOperand.IExecutableOperandType; import de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel.matchLoop.BuildCollectionApplicator; import de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel.matchLoop.BuildEnablingApplicator; import de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel.matchLoop.BuildExecutionApplicator; import de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel.matchLoop.LhsRhsMatchLoop; import de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel.matchLoop.RuleBodyExecutor; import de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel.ruleMatch.AbstractRuleMatchExpression; import de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel.ruleMatch.ArtifactMatchExpression; import de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel.ruleMatch.CollectionMatchExpression; import de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel.ruleMatch.MatchResolver; import de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel.ruleMatch.PathMatchExpression; import de.uni_hildesheim.sse.easy_producer.instantiator.model.buildlangModel.ruleMatch.StringMatchExpression; import de.uni_hildesheim.sse.easy_producer.instantiator.model.common.ExecutionVisitor; import de.uni_hildesheim.sse.easy_producer.instantiator.model.common.IResolvableModel; import de.uni_hildesheim.sse.easy_producer.instantiator.model.common.ModelCallExpression; import de.uni_hildesheim.sse.easy_producer.instantiator.model.common.VilLanguageException; import de.uni_hildesheim.sse.easy_producer.instantiator.model.expressions.AbstractCallExpression; import de.uni_hildesheim.sse.easy_producer.instantiator.model.expressions.CallArgument; import de.uni_hildesheim.sse.easy_producer.instantiator.model.expressions.CallExpression; import de.uni_hildesheim.sse.easy_producer.instantiator.model.expressions.ConstantExpression; import de.uni_hildesheim.sse.easy_producer.instantiator.model.expressions.Expression; import de.uni_hildesheim.sse.easy_producer.instantiator.model.expressions.ExpressionException; import de.uni_hildesheim.sse.easy_producer.instantiator.model.expressions.ExpressionParserRegistry; import de.uni_hildesheim.sse.easy_producer.instantiator.model.expressions.IExpressionParser; import de.uni_hildesheim.sse.easy_producer.instantiator.model.expressions.IResolvable; import de.uni_hildesheim.sse.easy_producer.instantiator.model.expressions.StringReplacer; import de.uni_hildesheim.sse.easy_producer.instantiator.model.expressions.ExpressionParserRegistry.ILanguage; import de.uni_hildesheim.sse.easy_producer.instantiator.model.vilTypes.ArraySequence; import de.uni_hildesheim.sse.easy_producer.instantiator.model.vilTypes.ArraySet; import de.uni_hildesheim.sse.easy_producer.instantiator.model.vilTypes.ArtifactException; import de.uni_hildesheim.sse.easy_producer.instantiator.model.vilTypes.Collection; import de.uni_hildesheim.sse.easy_producer.instantiator.model.vilTypes.FixedListSequence; import de.uni_hildesheim.sse.easy_producer.instantiator.model.vilTypes.ITypedModel; import de.uni_hildesheim.sse.easy_producer.instantiator.model.vilTypes.IVilType; import de.uni_hildesheim.sse.easy_producer.instantiator.model.vilTypes.ListSequence; import de.uni_hildesheim.sse.easy_producer.instantiator.model.vilTypes.OperationDescriptor; import de.uni_hildesheim.sse.easy_producer.instantiator.model.vilTypes.Project; import de.uni_hildesheim.sse.easy_producer.instantiator.model.vilTypes.StringValueHelper; import de.uni_hildesheim.sse.easy_producer.instantiator.model.vilTypes.TypeDescriptor; import de.uni_hildesheim.sse.easy_producer.instantiator.model.vilTypes.TypeRegistry; import de.uni_hildesheim.sse.easy_producer.instantiator.model.vilTypes.configuration.Configuration; import de.uni_hildesheim.sse.easy_producer.instantiator.model.vilTypes.configuration.IvmlTypes; import de.uni_hildesheim.sse.utils.logger.EASyLoggerFactory; import de.uni_hildesheim.sse.utils.logger.EASyLoggerFactory.EASyLogger; import de.uni_hildesheim.sse.utils.modelManagement.AvailableModels; import de.uni_hildesheim.sse.utils.modelManagement.IVersionRestriction; import de.uni_hildesheim.sse.utils.modelManagement.ModelInfo; import de.uni_hildesheim.sse.utils.modelManagement.ModelManagementException; /** * Executes a build language project. Please note that for full * functionality, {@link #setExpressionParser(IExpressionParser)} * must be called appropriately. * * @author Holger Eichelberger */ public class BuildlangExecution extends ExecutionVisitor<Script, Rule, VariableDeclaration> implements IBuildlangVisitor, RuleBodyExecutor { public static final ILanguage LANGUAGE = new ILanguage() { @Override public String getName() { return "VIL"; } }; /** * The default source project parameter (called {@value}). */ public static final String PARAM_SOURCE = "source"; /** * The default target project parameter (called {@value}). */ public static final String PARAM_TARGET = "target"; /** * The default configuration parameter (called {@value}). */ public static final String PARAM_CONFIG = "config"; /** * The name of the default main rule (called {@value}). */ public static final String DEFAULT_MAIN_RULE = "main"; private RuntimeEnvironment environment; private ITracer tracer; private File base; private String startRuleName; private List<Rule> failed = new ArrayList<Rule>(); private MatchResolver matchResolver; private VariableFinder variableFinder; private ExecutableRules executableRules; private Stack<RuleExecutionContext> ruleStack = new Stack<RuleExecutionContext>(); private Resolver resolver; /** * Creates a new execution environment. * * @param tracer the tracer * @param base the base directory for making files absolute * @param parameter the top-level parameter for the script to be executed */ public BuildlangExecution(ITracer tracer, File base, Map<String, Object> parameter) { this(tracer, base, DEFAULT_MAIN_RULE, parameter); } /** * Creates a new execution environment. * * @param tracer the tracer * @param base the base directory for making files absolute * @param startRuleName the name of the start rule; if multiple source projects are given, * as a convention the first one shall the top-level project that needs to be executed. * @param parameter the top-level parameter for the script to be executed */ public BuildlangExecution(ITracer tracer, File base, String startRuleName, Map<String, Object> parameter) { super(new RuntimeEnvironment(), tracer, parameter); this.tracer = tracer; this.environment = (RuntimeEnvironment) getRuntimeEnvironment(); this.base = base; this.startRuleName = startRuleName; initialize(); } /** * Creates a new execution visitor for import expression evaluation. * * @param environment the runtime environment to be used for expression evaluation */ BuildlangExecution(RuntimeEnvironment environment) { super(environment, NoTracer.INSTANCE, new HashMap<String, Object>()); this.tracer = NoTracer.INSTANCE; this.environment = environment; this.base = new File(""); this.startRuleName = DEFAULT_MAIN_RULE; initialize(); } @Override protected IExpressionParser getExpressionParser() { return ExpressionParserRegistry.getExpressionParser(LANGUAGE); } /** * Does common initializations based on already set values (requires {@link #environment) to be set properly). */ private void initialize() { this.matchResolver = new MatchResolver(environment, getExpressionParser(), this); this.variableFinder = new VariableFinder(); this.executableRules = new ExecutableRules(); this.resolver = new Resolver(environment.getTypeRegistry()); } /** * Makes <code>path</code> absolute with respect to <code>base</code> if necessary. * * @param path the file to be made absolute * @param base the base path * @return the absolute file */ private static File absolute(String path, File base) { File result = new File(path); if (!result.isAbsolute()) { result = new File(base, path); } return result; } /** * Returns the number of failed rules. * * @return the number of failed rules */ public int getFailedCount() { return failed.size(); } /** * Returns the specified failed rule. * * @param index the index of the failed rule * @return the failed rule * @throws IndexOutOfBoundsException if <code>index < 0 || index >= {@link #getFailedCount()}</code> */ public Rule getFailed(int index) { return failed.get(index); } /** * Checks the results of an evaluation of a condition. * * @param value the evaluation result * @param element the element to check on * @param test how to test individual artifacts * @return <code>true</code> if the condition is fulfilled, <code>false</code> else */ protected boolean checkConditionResult(Object value, Object element, ConditionTest test) { boolean ok = true; if (element instanceof MapExpression) { ok = true; // maps never fail } else if (value instanceof RuleExecutionResult) { ok = ((RuleExecutionResult) value).getStatus() != Status.FAIL; } else { ok = super.checkConditionResult(value, element, test); } return ok; } @Override public Object visitScript(Script script) throws VilLanguageException { environment.switchContext(script); // the initial context, method is called only from outside return executeScript(script, null); } /** * Executes the given script starting at <code>start</code>. * * @param script the script to be executed * @param start the start rule (may be <b>null</b> to determine main) * @return the result of the execution * @throws VilLanguageException if the execution fails */ private Object executeScript(Script script, RuleCallExpression start) throws VilLanguageException { Map<String, Object> scriptParam; try { scriptParam = determineScriptParam(start); } catch (ExpressionException e) { throw new VilLanguageException(e); } resolver.pushModel(script); tracer.visitScript(script); if (null != scriptParam) { scriptParam = replaceParameter(scriptParam); } visitModelHeader(script); List<File> vtlPaths = new ArrayList<File>(); for (int p = 0; p < script.getParameterCount(); p++) { Object value; try { value = environment.getValue(script.getParameter(p)); } catch (VilLanguageException e) { value = null; // don't care for undefined variables } if (value instanceof Configuration) { Configuration config = (Configuration) value; if (null == config.getRootScript()) { config.setRootScript(script); } } String pName = script.getParameter(p).getName(); if (pName.equals(PARAM_SOURCE) || pName.equals(PARAM_TARGET)) { if (value instanceof Project) { vtlPaths.add(((Project) value).getVtlFolder().getAbsolutePath()); } if (value instanceof Project[]) { for (Project prj : (Project[]) value) { vtlPaths.add(prj.getVtlFolder().getAbsolutePath()); } } } } executableRules.collect(script); ITypedModel oldContext = environment.switchContext(script); environment.setContextPaths(vtlPaths); processProperties(script, getTargetPath(script, scriptParam)); checkConstants(script); tracer.visitedScript(script); Object result; if (null == start) { result = executeMain(script, script.determineStartRule(startRuleName)); } else { try { result = start.accept(this); } catch (ExpressionException e) { throw new VilLanguageException(e); } } resolver.popModel(); if (null != scriptParam) { scriptParam = replaceParameter(scriptParam); } environment.switchContext(oldContext); return result; } /** * Returns the target path or {@link #base}. * * @param script the script to obtain the target path from * @param scriptParam the script parameters to consider (additional, may be <b>null</b>) * @return the path to the target project or {@link #base} if not found */ private File getTargetPath(Script script, Map<String, Object> scriptParam) { File result = base; Object target = null; if (null != scriptParam) { target = scriptParam.get(PARAM_TARGET); } if (null == target && script.getParameterCount() >= 3) { try { target = environment.getValue(script.getParameter(2)); } catch (VilLanguageException e) { } if (!(target instanceof Project)) { target = null; } } if (null == target) { for (int p = 0; null == target && p < script.getParameterCount(); p++) { VariableDeclaration decl = script.getParameter(p); if (decl.getName().equals(PARAM_TARGET)) { try { target = environment.getValue(decl); } catch (VilLanguageException e) { } } } } if (target instanceof Project) { result = ((Project) target).getPath().getAbsolutePath(); } return result; } @Override protected void initializeImplicitVariables(IResolvableModel<VariableDeclaration> model) throws VilLanguageException { if (model instanceof Script) { Script script = (Script) model; VariableDeclaration var = script.getVariableDeclaration(Script.NAME_OTHERPROJECTS); if (null != var) { environment.addValue(var, environment.getOtherProjects()); } var = script.getVariableDeclaration(Script.NAME_SCRIPTDIR); if (null != var) { String scriptDir; File file = script.getContainingFolder(); if (null == file) { scriptDir = ""; } else { scriptDir = file.getAbsolutePath(); } environment.addValue(var, scriptDir); } } } @Override protected ModelCallExpression<VariableDeclaration, Script, Rule> createModelCall(Script model, Rule operation, CallArgument... arguments) throws ExpressionException { return new RuleCallExpression(model, operation, arguments); } @Override protected void setModelArgument(VariableDeclaration param, Object value) throws VilLanguageException { // input may be Project or Project[] instance, expected may be collection or project // perform explicit conversion if possible - otherways fail and execution will fail anyway // this is needed for multi-project instantiation Object newVal = value; if (null != value) { TypeDescriptor<? extends IVilType> projectType = IvmlTypes.projectType(); TypeDescriptor<? extends IVilType> type = param.getType(); if (type.isCollection() && 1 == type.getParameterCount() && projectType.isAssignableFrom(type.getParameterType(0))) { // expected type is a collection of Projects Project[] pArray = null; if (value instanceof Project[]) { // just convert the array into collection type later pArray = (Project[]) value; } else if (value instanceof Project) { // turn project into 1-array and requried collection type later pArray = new Project[1]; pArray[0] = (Project) value; } if (null != pArray) { // turn array into collection type if (type.isSequence()) { newVal = new ArraySequence<Project>(pArray, Project.class); } else { newVal = new ArraySet<Project>(pArray, Project.class); } } } else if (type.isAssignableFrom(projectType)) { // expected type is a Project if (value instanceof Project[]) { Project[] pArray = (Project[]) value; if (pArray.length > 0) { // use also in array case the first project ... assumption on call newVal = pArray[0]; } } } } super.setModelArgument(param, newVal); } /** * Processes the {@link LoadProperties}. * * @param script the script to process the properties for * @param base the base path to make relative paths absolute * @throws VilLanguageException in case that something goes wrong */ private void processProperties(Script script, File base) throws VilLanguageException { Properties loaded = new Properties(); for (int p = 0; p < script.getPropertiesCount(); p++) { LoadProperties prop = script.getProperties(p); String path = prop.getPath(); try { path = StringReplacer.substitute(path, environment, getExpressionParser(), this); } catch (ExpressionException e) { throw new VilLanguageException(e); } File file = absolute(path, base); loadProperties(file, loaded, null); if (SystemUtils.IS_OS_MAC) { loadProperties(file, loaded, "macos"); } else if (SystemUtils.IS_OS_UNIX) { loadProperties(file, loaded, "unix"); } else if (SystemUtils.IS_OS_WINDOWS) { loadProperties(file, loaded, "win"); } } for (int v = 0; v < script.getVariableDeclarationCount(); v++) { VariableDeclaration var = script.getVariableDeclaration(v); String value = loaded.getProperty(var.getName(), null); if (null != value) { if (var.isConstant() && null != var.getExpression()) { throw new VilLanguageException("constant '" + var.getName() + "' is already assigned a value", VilLanguageException.ID_IS_CONSTANT); } Object actValue = evaluateExternalValue(var, value); environment.setValue(var, actValue); tracer.valueDefined(var, actValue); } } } /** * Loads properties from <code>file</code> into <code>prop</code> possibly overriding existing * properties. * * @param file the file name * @param prop the loaded properties (to be modified as a side effect) * @param os if not <b>null</b> to be inserted after the last "." with a following ".". If file * not exists, no exception will be thrown. * @throws VilLanguageException in case of loading problems */ private void loadProperties(File file, Properties prop, String os) throws VilLanguageException { boolean loadFile = true; if (null != os) { String f = file.toString(); int pos = f.lastIndexOf('.'); if (pos > 0 && pos < f.length()) { f = f.substring(0, pos + 1) + os + "." + f.substring(pos + 1); file = new File(f); loadFile = file.exists(); } else { loadFile = false; } } if (loadFile) { try { FileInputStream fis = new FileInputStream(file); Properties p = new Properties(); p.load(fis); prop.putAll(p); fis.close(); } catch (IOException e) { throw new VilLanguageException(e.getMessage(), e, VilLanguageException.ID_IO); } } } /** * Evaluates an external value for a given <code>var</code>. * * @param var the variable to evaluate the value for * @param value the external value * @return the actual value for <code>var</code> * @throws VilLanguageException in case that conversion does not work */ private Object evaluateExternalValue(VariableDeclaration var, String value) throws VilLanguageException { Object actValue; try { ConstantExpression ex = new ConstantExpression(var.getType(), value, environment.getTypeRegistry()); actValue = ex.accept(this); } catch (ExpressionException e) { // cannot be turned into a primitive value try { actValue = value; TypeDescriptor<? extends IVilType> varType = var.getType(); TypeDescriptor<? extends IVilType> exType = TypeRegistry.stringType(); if (!varType.isAssignableFrom(exType)) { OperationDescriptor desc = TypeDescriptor.findConversionOnBoth(exType, varType); if (null != desc) { Expression ex = new ConstantExpression(exType, value, environment.getTypeRegistry()); ex = new CallExpression(desc, new CallArgument(ex)); actValue = ex.accept(this); } } } catch (ExpressionException e1) { throw new VilLanguageException(e1); } } return actValue; } /** * Checks the constant values for proper initialization. * * @param script the project to process the properties for * @throws VilLanguageException in case that something goes wrong */ private void checkConstants(Script script) throws VilLanguageException { for (int v = 0; v < script.getVariableDeclarationCount(); v++) { VariableDeclaration var = script.getVariableDeclaration(v); if (var.isConstant()) { if (!environment.isDefined(var)) { throw new VilLanguageException( "constant '" + var.getName() + "' must be assigned a value " + "(either in script or via loaded properties)", VilLanguageException.ID_SEMANTIC); } } } } /** * Post processes system call arguments, e.g., by removing line separators. * * @param argument the argument to be processed * @return the processed argument */ private String postprocessSystemCallArgument(String argument) { return null == argument ? "" : argument.replaceAll("(\\r|\\n)", ""); } @Override public Object visitStrategyCallExpression(StrategyCallExpression call) throws ExpressionException { Object result; if (call.isPlaceholder()) { result = null; } else { result = visitStrategyCallExpressionImpl(call); } return result; } /** * Implements the strategy call execution of an executable call. * * @param call the call to execute * @return the execution result * @throws ExpressionException in case of execution problems */ public Object visitStrategyCallExpressionImpl(StrategyCallExpression call) throws ExpressionException { Object result; switch (call.getType()) { case EXECUTE: String exec; try { Object nameVarVal = environment.getValue(call.getNameVariable()); if (nameVarVal instanceof Path) { exec = ((Path) nameVarVal).getAbsolutePath().getAbsolutePath(); } else { exec = StringValueHelper.getStringValue(nameVarVal, null); exec = StringReplacer.substitute(exec, environment, getExpressionParser(), this); } } catch (VilLanguageException e) { throw new ExpressionException(e); } String[] args = new String[call.getArgumentsCount() + 1]; args[0] = postprocessSystemCallArgument(exec); for (int a = 1; a < args.length; a++) { Object o = call.getArgument(a - 1).accept(this); IExecutableOperandType type = ExecutableOperand.getExecutableType(o); if (null == type) { if (null == o) { args[a] = null; } else { args[a] = o.toString(); } } else { try { args[a] = type.convert(o); } catch (ArtifactException e) { throw new ExpressionException(e, ExpressionException.ID_RUNTIME); } } args[a] = postprocessSystemCallArgument( StringReplacer.substitute(args[a], environment, getExpressionParser(), this)); } EASyLogger logger = EASyLoggerFactory.INSTANCE.getLogger(StrategyCallExpression.class, Bundle.ID); logger.info("system call: " + Arrays.toString(args)); tracer.visitSystemCall(args); ProcessBuilder builder = new ProcessBuilder(args); try { Process process = builder.start(); InputStream is = process.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String line; while ((line = br.readLine()) != null) { logger.info(line); } int res = process.waitFor(); logger.info("execution result: " + res); } catch (InterruptedException e) { throw new ExpressionException(e, ExpressionException.ID_SYSTEM_EXEC); } catch (IOException e) { throw new ExpressionException(e, ExpressionException.ID_SYSTEM_EXEC); } result = Boolean.TRUE; break; case INSTANTIATOR: tracer.visitingInstantiator(call.getName()); result = visitCallExpression(call); tracer.visitedInstantiator(call.getName(), result); break; default: throw new ExpressionException("illegal strategy type " + call.getType(), ExpressionException.ID_INTERNAL); } return result; } /* @Override public void visitDefer(Defer defer) throws BuildlangException { }*/ @Override public Object visitLoadProperties(LoadProperties properties) throws VilLanguageException { // done in processProperties return null; } /** * Registers the parameter of <code>rule</code>. * * @param rule the rule to register the parameter for * @throws VilLanguageException in case of any execution error */ private void registerParameter(Rule rule) throws VilLanguageException { for (int p = 0; p < rule.getParameterCount(); p++) { VariableDeclaration var = rule.getParameter(p); IResolvable res = environment.get(var.getName()); if (null == res) { throw new VilLanguageException("parameter " + var.getName() + " is not defined", VilLanguageException.ID_RUNTIME_PARAMETER); } else { try { environment.addValue(var, environment.getValue(res)); } catch (ExpressionException e) { throw new VilLanguageException(e); } } } } /** * Adds the variables of <code>rule</code> at the given <code>side</code> to the {@link #variableFinder}. * * @param rule the rule to take the variables from * @param side the side to consider * @param matchVariables shall match variables (entire expression) or related loop variables be added */ private void addVariablesToFinder(Rule rule, Side side, boolean matchVariables) { for (int i = 0; i < rule.getVariablesCount(side); i++) { VariableDeclaration decl; if (matchVariables) { decl = rule.getMatchVariable(side, i); } else { decl = rule.getVariable(side, i); } if (null != decl) { variableFinder.addToSearch(decl); } } } /** * Determines pre- and postcondition matches and, if enabled, assigns RHS/LHS variables and executes the rule body. * Actually, this method has two purposes, namely, testing for enabling pre- and post-condition matches as well * as executing the rule body, in case of RHS/LHS matches in iterative fashion. We combined both functionalities * as they are closely related and modifications to the matching would otherwise need modifications to multple * algorithms. This also prevents storing the matching (outdated/non-existing) pairs in memory. * * @param rule the rule to be executed (contains the rule body) * @param rhsValues the evaluated RHS conditions (values, determined by visiting the RHS matching expressions * and therefore are Objects) * @param context the rule execution context, may be <b>null</b> if actually no execution of the rule * body shall be performed and just pre-postcondition matches shall be tested * @return the execution status if <code>execute</code> is true, the matching status else (in particular * {@link Status#SUCCESS} and {@link Status#NOT_APPLICABLE}). * @throws VilLanguageException in case of serious execution problems */ private Status applyRuleBody(Rule rule, Object[] rhsValues, RuleExecutionContext context) throws VilLanguageException { Status status = Status.SUCCESS; if (null != context) { // determine whether rule match variables are actually used and determine body execution strategy variableFinder.reset(); addVariablesToFinder(rule, Side.LHS, false); addVariablesToFinder(rule, Side.RHS, false); rule.accept(variableFinder); boolean iteratedExecution = variableFinder.wasFound(); variableFinder.reset(); //addVariablesToFinder(rule, Side.LHS, true); addVariablesToFinder(rule, Side.RHS, true); rule.accept(variableFinder); boolean matchExecution = variableFinder.wasFound(); if (matchExecution) { LhsRhsMatchLoop.matchLoop(rule, rhsValues, new BuildCollectionApplicator(environment), tracer); } if (iteratedExecution) { BuildExecutionApplicator applicator = new BuildExecutionApplicator(environment, context, this); LhsRhsMatchLoop.matchLoop(rule, rhsValues, applicator, tracer); status = applicator.getStatus(); } else { status = executeRuleBody(rule, context); } } else { // check for enabling BuildEnablingApplicator applicator = new BuildEnablingApplicator(); LhsRhsMatchLoop.matchLoop(rule, rhsValues, applicator, tracer); status = applicator.allConditionsEnabled() ? Status.SUCCESS : Status.NOT_APPLICABLE; } return status; } @Override public Status executeRuleBody(RuleBlock ruleBody, RuleExecutionContext context) throws VilLanguageException { Status status = Status.SUCCESS; for (int e = 0; Status.SUCCESS == status && e < ruleBody.getBodyElementCount(); e++) { IRuleElement elt = ruleBody.getBodyElement(e); Object eltVal = elt.accept(this); if (mayFail(elt) // guard expression && !checkConditionResult(eltVal, elt, ConditionTest.DONT_CARE)) { tracer.failedAt(ruleBody.getBodyElement(e)); status = Status.FAIL; } else { context.add(eltVal); } } return status; } /** * Resolves rule condition matches. * * @param rule the rule to consider * @param side the side of the rule * @throws ExpressionException in case of resolution problems */ void resolveMatches(Rule rule, Side side) throws ExpressionException { int count = rule.getRuleConditionCount(side); if (count > 0) { for (int i = 0; i < count; i++) { rule.getRuleCondition(side, i).accept(matchResolver); } } } /** * Returns whether the given <code>rule</code> is on the execution stack. * * @param rule the rule to be tested * @return <code>true</code> if it is on the actual execution stack, <code>false</code> else */ boolean isOnStack(Rule rule) { boolean found = false; for (int i = ruleStack.size() - 1; !found && i >= 0; i--) { found = ruleStack.get(i).getRule() == rule; // handled internally so reference equality is ok } return found; } /** * Checks the RHS/LHS matching. * * @param rule the rule the matching shall be done for * @param rhsValues an already initialized array for carrying the primary evaluation/matching results of the RHS * matching expression(s) * @return the status of the matching, i.e., {@link Status#SUCCESS} if execution can go on, * {@link Status#NOT_APPLICABLE} if the rule shall not be applied, {@link Status#FAIL} if already the (execution * of the preconditions) fails and, thus, the entire rule shall fail * @throws ExpressionException in case of evaluating an LHS/RHS expression fails * @throws VilLanguageException in case that execution seriously fails */ private Status determineRhsLhsMatching(Rule rule, Object[] rhsValues) throws ExpressionException, VilLanguageException { Status status = Status.SUCCESS; // determine the RHS matching results -> collections; stop if no result int rhsCondCount = rule.getRuleConditionCount(Side.RHS); for (int c = 0; Status.SUCCESS == status && c < rhsCondCount; c++) { AbstractRuleMatchExpression cond = rule.getRuleCondition(Side.RHS, c); rhsValues[c] = cond.accept(this); // has elements; actual - if not, check further assert cond.inferType().isCollection(); assert rhsValues[c] instanceof Collection; if (!checkConditionResult(rhsValues[c], rule, ConditionTest.DONT_CARE)) { rhsValues[c] = executableRules.buildContributing(cond, this); if (!checkConditionResult(rhsValues[c], cond, ConditionTest.DONT_CARE)) { status = Status.NOT_APPLICABLE; } } } // look into the RHS-LHS matching based on timestamp/existence; stop if no matches if (Status.SUCCESS == status) { status = applyRuleBody(rule, rhsValues, null); // } return status; } @Override public Object visitRule(Rule rule) throws VilLanguageException { RuleExecutionContext context = new RuleExecutionContext(rule, environment); RuleExecutionResult result; if (rule.isPlaceholder()) { result = new RuleExecutionResult(Status.NOT_APPLICABLE, context); } else { ruleStack.push(context); Status status = Status.SUCCESS; boolean visited = false; try { environment.pushLevel(); // evaluate parameter and replace generic matches registerParameter(rule); resolveMatches(rule, Side.RHS); resolveMatches(rule, Side.LHS); Object[] rhsValues = null; // store for iteration int rhsCondCount = rule.getRuleConditionCount(Side.RHS); if (rhsCondCount > 0) { rhsValues = new Object[rhsCondCount]; status = determineRhsLhsMatching(rule, rhsValues); } for (int c = 0; Status.SUCCESS == status && c < rule.getRuleCallCount(Side.RHS); c++) { RuleCallExpression ex = rule.getRuleCall(Side.RHS, c); RuleExecutionResult res = (RuleExecutionResult) ex.accept(this); if (Status.FAIL == res.getStatus()) { status = Status.FAIL; // don't care fore not_applicable or success } environment.addValue(rule.getVariable(Side.RHS, rhsCondCount + c), res); context.add(res); } // process the body tracer.visitRule(rule, environment); visited = true; if (Status.SUCCESS == status) { status = applyRuleBody(rule, rhsValues, context); for (int c = 0; Status.SUCCESS == status && c < rule.getRuleConditionCount(Side.LHS); c++) { AbstractRuleMatchExpression ex = rule.getRuleCondition(Side.LHS, c); if (!checkConditionResult(ex.accept(this), ex, ConditionTest.EXISTS)) { status = Status.FAIL; tracer.failedAt(ex); } } } } catch (ExpressionException e) { throw new VilLanguageException(e); } catch (VilLanguageException e) { throw new VilLanguageException(e); } try { ruleStack.pop(); environment.popLevel(); } catch (ArtifactException e) { throw new VilLanguageException(e); } if (Status.FAIL == status) { this.failed.add(rule); } result = new RuleExecutionResult(status, context); if (visited) { tracer.visitedRule(rule, environment, result); } } return result; } @Override public Object visitRuleCallExpression(RuleCallExpression ex) throws ExpressionException { return visitModelCallExpression(ex); } @Override protected Object executeModelCall(Rule rule) throws VilLanguageException { return rule.accept(this); } @Override public Object visitPathMatchExpression(PathMatchExpression expression) throws ExpressionException { return expression.evaluate(this); } @Override public Object visitStringMatchExpression(StringMatchExpression expression) throws ExpressionException { return expression.evaluate(this); } @Override public Object visitArtifactMatchExpression(ArtifactMatchExpression expression) throws ExpressionException { return expression.evaluate(this); } @Override public Object visitCollectionMatchExpression(CollectionMatchExpression expression) throws ExpressionException { return expression.evaluate(this); } @Override public Object visitJoinExpression(JoinExpression ex) throws ExpressionException { environment.pushLevel(); Object result; try { result = join(ex); } catch (VilLanguageException e) { throw new ExpressionException(e); } try { environment.popLevel(); } catch (ArtifactException e) { throw new ExpressionException(e); } return result; } /** * Evaluates the condition for a variable. * * @param var the variable to be evaluated * @return the evaluation result * @throws ExpressionException in case of expression evaluation errors */ @SuppressWarnings("unchecked") private Collection<Object> evaluate(JoinVariableDeclaration var) throws ExpressionException { return (Collection<Object>) var.getExpression().accept(this); } /** * Creates an empty array of collections. * * @param length the length of the array to be created * @return the array instance */ @SuppressWarnings("unchecked") private static Collection<Object>[] createCollectionArray(int length) { return new Collection[length]; } /** * Creates an empty array of iterators. * * @param length the length of the array to be created * @return the array instance */ @SuppressWarnings("unchecked") private static Iterator<Object>[] createIteratorArray(int length) { return new Iterator[length]; } /** * Performs the join operation. * * @param join the join expression to be evaluated * @return the join result * @throws ExpressionException in case of expression problems * @throws VilLanguageException in case of execution problems */ private Object join(JoinExpression join) throws ExpressionException, VilLanguageException { List<IVilType> simpleResult = new ArrayList<IVilType>(); List<IVilType[]> complexResult = new ArrayList<IVilType[]>(); // simple (inefficient) join for the initial implementation... Collection<Object>[] collection = createCollectionArray(join.getVariablesCount()); Iterator<Object>[] iter = createIteratorArray(join.getVariablesCount()); for (int i = 0; i < join.getVariablesCount(); i++) { collection[i] = tracer.adjustSequenceForJoin(evaluate(join.getVariable(i))); iter[i] = collection[i].iterator(); } // initialize boolean stop = false; int lastIndex = join.getVariablesCount() - 1; for (int i = 0; !stop && i <= lastIndex; i++) { if (iter[i].hasNext()) { setJoinVariableValue(join, i, iter[i].next()); } else { stop = true; } } if (!stop && iter[lastIndex].hasNext()) { boolean propagate = false; while (iter[0].hasNext()) { if (propagate) { // reset last iterator iter[lastIndex] = collection[lastIndex].iterator(); setJoinVariableValue(join, lastIndex, iter[lastIndex].next()); // propagate int pos = lastIndex - 1; while (pos >= 0 && !iter[pos].hasNext()) { pos--; } if (pos >= 0) { setJoinVariableValue(join, pos, iter[pos].next()); for (int i = lastIndex - 1; i > pos; i--) { iter[i] = collection[i].iterator(); if (iter[i].hasNext()) { iter[i] = collection[i].iterator(); setJoinVariableValue(join, i, iter[i].next()); } } } // else end outer loop } evaluateJoinCombination(join, simpleResult, complexResult); // loop last while (iter[lastIndex].hasNext()) { setJoinVariableValue(join, lastIndex, iter[lastIndex].next()); evaluateJoinCombination(join, simpleResult, complexResult); } propagate = true; } } Object result; if (1 == join.getVisibleVariablesCount()) { result = new FixedListSequence<IVilType>(simpleResult, environment.getTypeRegistry(), types(join)); } else { result = new FixedListSequence<IVilType[]>(complexResult, environment.getTypeRegistry(), types(join)); } return result; } /** * Sets the specified join variable. * * @param join the join expression * @param index the index of the variable * @param value the value * @throws VilLanguageException in case that setting the variable fails */ private void setJoinVariableValue(JoinExpression join, int index, Object value) throws VilLanguageException { environment.addValue(join.getVariable(index), value); } /** * Evaluates a join combination. * * @param join the join to be evaluated * @param simpleResult modified in case of 1-sided joins * @param complexResult modified in case of multi-sided joins * @throws ExpressionException in case that the evaluation fails * @throws VilLanguageException in case that the evaluation fails */ private void evaluateJoinCombination(JoinExpression join, List<IVilType> simpleResult, List<IVilType[]> complexResult) throws ExpressionException, VilLanguageException { Boolean bool; if (null != join.getCondition()) { bool = (Boolean) join.getCondition().accept(this); } else { bool = Boolean.TRUE; } if (null != bool && bool.booleanValue()) { if (1 == join.getVisibleVariablesCount()) { simpleResult.add((IVilType) environment.getValue(join.getVisibleVariable(0))); } else { IVilType[] tmp = new IVilType[join.getVisibleVariablesCount()]; for (int i = 0; i < join.getVisibleVariablesCount(); i++) { tmp[i] = (IVilType) environment.getValue(join.getVisibleVariable(i)); } complexResult.add(tmp); } } } /** * Returns the types to be used to parameterize the return type. * * @param join the join expression * * @return the types */ @SuppressWarnings("unchecked") private static Class<? extends IVilType>[] types(JoinExpression join) { int count = join.getVisibleVariablesCount(); Class<? extends IVilType>[] result = new Class[count]; for (int i = 0; i < count; i++) { result[i] = join.getVisibleVariable(i).getType().getTypeClass(); } return result; } @Override public Object visitJoinVariableDeclaration(JoinVariableDeclaration decl) throws VilLanguageException { try { return decl.getExpression().accept(this); } catch (ExpressionException e) { throw new VilLanguageException(e); } } @Override public Object visitAlternativeExpression(AlternativeExpression alt) throws ExpressionException { Object result = null; Object condition = alt.getCondition().accept(this); if (condition instanceof Boolean) { boolean execThenPart = ((Boolean) condition).booleanValue(); tracer.visitAlternative(execThenPart); IRuleBlock execute; if (execThenPart) { execute = alt.getIfPart(); } else { execute = alt.getElsePart(); } if (null != execute) { RuleExecutionContext context = ruleStack.peek(); boolean failed = false; IRuleElement determinesResult = Utils.findLastExpressionStatement(execute); try { environment.pushLevel(); for (int e = 0; !failed && e < execute.getBodyElementCount(); e++) { IRuleElement elt = execute.getBodyElement(e); Object eltRes = elt.accept(this); context.add(eltRes); if (elt == determinesResult) { result = eltRes; // collect the last one } else if (mayFail(elt)) { failed = !checkConditionResult(eltRes, elt, ConditionTest.DONT_CARE); } } environment.popLevel(); } catch (VilLanguageException e) { throw new ExpressionException(e); } catch (ArtifactException e) { throw new ExpressionException(e); } } } return result; } @Override public Object visitMapExpression(MapExpression map) throws ExpressionException { boolean failed = false; List<Object> result; RuleExecutionContext context = ruleStack.peek(); TypeDescriptor<? extends IVilType> mapType = map.inferType(); result = TypeRegistry.voidType() == mapType ? null : new ArrayList<Object>(); try { environment.pushLevel(); tracer.visitMap(map, environment); Expression expr = map.getExpression(); Object set = convertToContainer(expr, expr.accept(this), "map"); if (set instanceof Collection) { Collection<?> coll = (Collection<?>) set; if (coll.allowSequenceAdjustment()) { coll = tracer.adjustSequenceForMap(coll); } for (Object entry : coll) { if (map.getVariablesCount() > 1) { Object[] data = (Object[]) entry; for (int v = 0; v < map.getVariablesCount(); v++) { VariableDeclaration var = map.getVariable(v); environment.addValue(var, data[v]); tracer.visitMapIteratorAssignment(var, data[v]); } } else { VariableDeclaration var = map.getVariable(0); environment.addValue(var, entry); tracer.visitMapIteratorAssignment(var, entry); } Object iterResult = null; IRuleElement determinesResult = map.determinesResult(); for (int e = 0; !failed && e < map.getBodyElementCount(); e++) { IRuleElement elt = map.getBodyElement(e); Object eltRes = elt.accept(this); context.add(eltRes); if (elt == determinesResult) { iterResult = eltRes; // collect the last one } else if (mayFail(elt)) { failed = !checkConditionResult(eltRes, elt, ConditionTest.DONT_CARE); } } if (null != result) { result.add(iterResult); } } } tracer.visitedMap(map, environment); } catch (VilLanguageException e) { throw new ExpressionException(e); } catch (ClassCastException e) { // for handcrafted models throw new ExpressionException(e.getMessage(), VilLanguageException.ID_INTERNAL); } catch (IndexOutOfBoundsException e) { // for handcrafted models throw new ExpressionException("index out of bounds " + e.getMessage(), VilLanguageException.ID_INTERNAL); } try { environment.popLevel(); } catch (ArtifactException e) { throw new ExpressionException(e); } return mapResult(mapType, result, failed); } /** * Creates the actual result of executing a {@link MapExpression}. * * @param mapType the type of the map expression * @param result the collected results (may be <b>null</b> if <code>type</code> is {@link TypeDescriptor#VOID}) * @param failed whether the actual execution failed * @return the return object for the map */ private static Object mapResult(TypeDescriptor<? extends IVilType> mapType, List<Object> result, boolean failed) { Object mapResult; if (null == result) { mapResult = !failed; // no problem as not processed further and allows further execution } else { TypeDescriptor<? extends IVilType>[] param; if (1 == mapType.getParameterCount()) { param = TypeDescriptor.createArray(1); param[0] = mapType.getParameterType(0); } else { param = null; } // may actually be empty - considered by checkConditionResult mapResult = new ListSequence<Object>(result, param); } return mapResult; } @Override public Object visitConstantExpression(ConstantExpression cst) throws ExpressionException { Object result = cst.getValue(); // we have to care for $name and ${} but only in strings if (result instanceof String) { result = StringReplacer.substitute(result.toString(), environment, getExpressionParser(), this); } return result; } @Override protected Rule dynamicDispatch(Rule operation, Object[] args) { return AbstractCallExpression.dynamicDispatch(operation, args, Rule.class, environment.getTypeRegistry()); } @Override protected void handleParameterInSequence(IResolvableModel<VariableDeclaration> model, Map<String, VariableDeclaration> varMap) throws VilLanguageException { if (model.getParameterCount() >= 3) { // check default sequence instead, source, config, target, optional boolean ok = IvmlTypes.projectType().isAssignableFrom(model.getParameter(0).getType()); ok &= IvmlTypes.configurationType().isAssignableFrom(model.getParameter(1).getType()); ok &= IvmlTypes.projectType().isAssignableFrom(model.getParameter(2).getType()); if (ok) { setModelArgument(model.getParameter(0), getParameter(PARAM_SOURCE)); setModelArgument(model.getParameter(1), getParameter(PARAM_CONFIG)); setModelArgument(model.getParameter(2), getParameter(PARAM_TARGET)); for (int p = 0; p < 3; p++) { varMap.remove(model.getParameter(p).getName()); } } } } /** * Resolves the script for an instantiation expression. * * @param project the project to resolve the script on * @param restrictions the version restrictions (may be <b>null</b>) * @return the resolved script * @throws ExpressionException in case that the version is not valid or that the script cannot be found */ private Script resolveScript(Project project, IVersionRestriction restrictions) throws ExpressionException { Script script = null; //ModelInfo<Script> info = null; AvailableModels<Script> available = BuildModel.INSTANCE.availableModels(); Script current = resolver.getCurrentModel(); ModelInfo<Script> currentInfo = available.getModelInfo(current); try { script = BuildModel.INSTANCE.resolve(project.getName(), restrictions, currentInfo.getLocation(), environment); } catch (ModelManagementException e) { throw new ExpressionException(e.getMessage(), e.getId()); } /*List<ModelInfo<Script>> infos = available.getVisibleModelInfo(project.getName(), currentInfo.getLocation()); if (null != infos) { if (1 == infos.size()) { info = infos.get(0); } else { Version max = null; for (int i = 0; i < infos.size(); i++) { ModelInfo<Script> tmp = infos.get(i); if (null == max || max.compareTo(tmp.getVersion()) < 0) { max = tmp.getVersion(); info = tmp; } } } } if (null == info) { throw new ExpressionException("cannot resolve main model for " + project.getName(), ExpressionException.ID_CANNOT_RESOLVE); } else { script = info.getResolved(); } if (null != restrictions) { try { script = BuildModel.INSTANCE.resolve(script.getName(), restrictions); } catch (ModelManagementException e) { throw new ExpressionException(e.getMessage(), e.getId()); } }*/ return script; } @Override public Object visitInstantiateExpression(InstantiateExpression inst) throws ExpressionException { // no tracer needed, will happen in ex.accept Script script = null; String name = null; if (null != inst.getProject()) { name = inst.getName(); if (null == name) { name = DEFAULT_MAIN_RULE; } try { Object pr = environment.getValue(inst.getProject()); if (pr instanceof Project) { Project project = (Project) pr; script = resolveScript(project, inst.getVersionRestriction()); // resolve it in the local context if (null == script) { // fallback - ask the project script = project.getMainVilScript(); } if (null == script) { // this may only happen if the projects are initially passed in as files throw new ExpressionException( "cannot resolve script " + name + " in project " + project.getName(), ExpressionException.ID_RUNTIME); } } } catch (VilLanguageException e) { throw new ExpressionException(e); } if (null == script) { // this may only happen if the projects are initially passed in as files throw new ExpressionException("cannot resolve script " + name, ExpressionException.ID_RUNTIME); } resolver.pushModel(script); } else { name = inst.getQualifiedName(); } CallArgument[] args = new CallArgument[inst.getArgumentsCount()]; for (int a = 0; a < args.length; a++) { args[a] = inst.getArgument(a); } RuleCallExpression ex = resolver.createCallExpression(false, name, args); if (null != inst.getProject()) { resolver.popModel(); } if (null == ex) { throw new ExpressionException("cannot resolve rule " + name, ExpressionException.ID_RUNTIME); } Object result; if (null == script) { result = ex.accept(this); } else { try { result = executeScript(script, ex); } catch (VilLanguageException e) { throw new ExpressionException(e); } } return result; } }