package com.sap.runlet.abstractexpressionpad; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Stack; import java.util.logging.Logger; import org.antlr.runtime.BitSet; import org.antlr.runtime.RecognitionException; import org.eclipse.emf.common.util.BasicDiagnostic; import org.eclipse.emf.common.util.Diagnostic; import org.eclipse.emf.common.util.DiagnosticChain; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EValidator; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import com.sap.furcas.runtime.common.implementation.ResolvedModelElementProxy; import com.sap.furcas.runtime.common.interfaces.IModelElementProxy; import com.sap.furcas.runtime.parser.ParsingError; import com.sap.furcas.runtime.parser.impl.DelayedReference; import com.sap.furcas.runtime.parser.impl.ObservableInjectingParser; import com.sap.furcas.runtime.parser.impl.context.ContextManager; import com.sap.runlet.abstractinterpreter.AbstractRunletInterpreter; import com.sap.runlet.abstractinterpreter.StackFrame; import com.sap.runlet.abstractinterpreter.objects.RunletObject; import com.sap.runlet.abstractinterpreter.repository.Repository; /** * Takes an ExpressionType or a StatementType in a concrete textual syntax and parses it * into a transient set of model elements. If parsing went through correctly, the resulting * ExpressionType or StatementType is handed to the {@link AbstractRunletInterpreter interpreter} * for evaluation. * <p> * * For statement execution, an instance of this class maintains a * stack frame of type StackFrameType to which variables can be added. * * @author Axel Uhl (D043530) * @author Jan Karstens (D046040) */ public abstract class Evaluator<MetaClass extends EObject, TypeUsage extends EObject, ClassUsage extends TypeUsage, LinkMetaObject extends EObject, LinkEndMetaObject extends EObject, StatementType extends EObject, ExpressionType extends EObject, SignatureImplementationType extends EObject, StackFrameType extends StackFrame<LinkEndMetaObject, TypeUsage, ClassUsage, SignatureImplementationType>, NativeType extends SignatureImplementationType, InterpreterType extends AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, StatementType, ExpressionType, SignatureImplementationType, StackFrameType, NativeType, InterpreterType>, Block extends SignatureImplementationType> { private Logger log = Logger.getLogger(Evaluator.class.getName()); private ResourceSet conn; /** * The stack frame corresponding to the {@link #contextBlock} for statement * execution. Holds the variable declarations made during statement * execution. */ private StackFrameType stackFrame; /** * For statement sequence execution we need a {@link Block} as context for * variable declarations. We'll set this on the parser using * {@link ObservableInjectingParser#addContext(com.sap.mi.textual.common.interfaces.IModelElementProxy, String...)} * . */ private Block contextBlock; /** * Helper variable to save the parser state between subsequent statement * evaluations */ private ContextManager contextManager = null; /** * Helper variable to save the parser state between subsequent statement * evaluations */ private Stack<IModelElementProxy> currentContextStack = null; /** * Maintains state such as a {@link RunletLinkContainer} maintaining links * of associations, across invocations of {@link #execute(String...)} and * {@link #evaluate(String...)}. */ private AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, StatementType, ExpressionType, SignatureImplementationType, StackFrameType, NativeType, InterpreterType> interpreter; public static class ExecuteResult<LinkEndMetaObject, TypeUsage, ClassUsage extends TypeUsage> { private RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>[] result; private String[] errors; public ExecuteResult(RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>[] result, String[] errors) { this.setResult(result); this.setErrors(errors); } public void setResult(RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>[] result) { this.result = result; } public RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>[] getResult() { return result; } public void setErrors(String[] errors) { this.errors = errors; } public String[] getErrors() { return errors; } } public static class ParseResult<ExpressionType extends EObject> { private List<String> errors; private DiagnosticChain constraintViolations; private ExpressionType expression; public ParseResult(List<String> errors, DiagnosticChain constraintViolations, ExpressionType expression) { super(); this.errors = errors; this.constraintViolations = constraintViolations; this.expression = expression; } public List<String> getErrors() { return errors; } public DiagnosticChain getConstraintViolations() { return constraintViolations; } public ExpressionType getExpression() { return expression; } } private void init(String projectName, Repository<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> repository) { init(getResourceSet(projectName), repository); } private void init(ResourceSet connection, Repository<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> repository) { this.conn = connection; initLocalFields(connection, repository); if (stackFrame == null || contextBlock == null || interpreter == null) { throw new RuntimeException("Evaluator is not correctly initialized."); } } /** * Implement and initialize the following fields: * * stackFrame * * contextBlock * * interpreter * * closeConnectionUponFinalize (optional, default false) * @param connection */ protected abstract void initLocalFields(ResourceSet connection, Repository<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> repository); /** * Initializes the {@link #getInterpreter() interpreter} with a connection * that is obtained from the project named <tt>projectName</tt>. A new empty * {@link Repository} is created for the interpreter. */ public Evaluator(String projectName) { init(projectName, createRepository()); } /** * Implement to return a new instance of a Repository. * @return */ abstract protected Repository<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> createRepository(); /** * Initializes the {@link #getInterpreter() interpreter} with a connection * that is obtained from the project named <tt>projectName</tt>. The * interpreter uses the <tt>repository</tt> passed to load and store * persistent objects. */ public Evaluator(String projectName, Repository<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> repository) { init(projectName, repository); } public Evaluator(ResourceSet resourceSet, Repository<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> repository) { init(resourceSet, repository); } public Repository<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getRepository() { return getInterpreter().getRepository(); } public static ResourceSet getResourceSet(String projectName) { return new ResourceSetImpl(); } @SuppressWarnings("unchecked") public RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>[] evaluate(String... expressions) throws RecognitionException, SecurityException, IllegalArgumentException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { List<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> result = new LinkedList<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>(); for (String expressionString : expressions) { ExpressionType expression = parse(expressionString).getExpression(); if (expression != null) { result.add(interpreter.evaluate(expression)); } else { result.add(null); } } return (RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>[]) result.toArray(new RunletObject<?, ?, ?>[0]); } public ParseResult<ExpressionType> parse(String expressionString) throws RecognitionException { ObservableInjectingParser parser = createParser(expressionString); BitSet follows = new BitSet(); follows.add(1); // EndOfRule, for top-level rule meaning EOF parser.pushFollow(follows); ExpressionType e = parseExpression(parser); parser.popFollow(); List<String> errors = new LinkedList<String>(); if (parser.input.LA(1) != -1) { // not matched up to EOF errors.add("Not parsed up to EOF. Unparsed text:\n" + parser.input.toString(parser.input.index(), parser.input.size() - 1)); } // append unresolved references to result (for display on console) if (!parser.setDelayedReferencesAfterParsing()) { for (DelayedReference ref : parser.getUnresolvedReferences()) { log.warning("Unresolved reference: " + ref); errors.add("Unresolved reference: " + ref.toString()); } } for (ParsingError err : parser.getInjector().getErrorList()) { errors.add(err.getMessage()); } BasicDiagnostic diagnostics = null; if (e != null) { EValidator v = EValidator.Registry.INSTANCE.getEValidator(e.eClass().getEPackage()); v.validate(e, diagnostics, new HashMap<Object, Object>()); // append constraint violations to result (for display on console) diagnostics = new BasicDiagnostic(); logAndCollectErrors(diagnostics, errors); } return new ParseResult<ExpressionType>(errors, diagnostics, e); } private void logAndCollectErrors(Diagnostic constraintViolations, List<String> errors) { if (constraintViolations.getSeverity() == Diagnostic.ERROR) { log.severe("Constraint violation: " + constraintViolations.getMessage() + " on element " + constraintViolations.getMessage()); errors.add("Constraint violation: " + constraintViolations.getMessage() + " on element " + constraintViolations.getMessage()); } for (Diagnostic child : constraintViolations.getChildren()) { logAndCollectErrors(child, errors); } } /** * Implement * @param parser * @return */ protected abstract ExpressionType parseExpression(ObservableInjectingParser parser) throws RecognitionException; @SuppressWarnings("unchecked") public ExecuteResult<LinkEndMetaObject, TypeUsage, ClassUsage> execute(String... statements) throws Exception { List<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> result = new LinkedList<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>(); List<String> errors = new LinkedList<String>(); Collection<Exception> constraintViolations = null; for (String statementString : statements) { ObservableInjectingParser parser = createParser(statementString); restoreParserState(parser); // parse statement, pretending it is the main rule, requiring EOF at // end of stream BitSet follows = new BitSet(); follows.add(1); // EndOfRule, for top-level rule meaning EOF follows.add(getStatementSeperatorBit()); // permit end-of-statement-token parser.pushFollow(follows); StatementType statement = parseStatement(parser); parser.popFollow(); if (parser.input.LA(1) != -1) { // not matched up to EOF errors.add("Not parsed up to EOF. Unparsed text:\n" + parser.input.toString(parser.input.index(), parser.input.size() - 1)); } if (statement != null) { // adding to block needs to happen before checking for unresolved references // because, e.g., variables are found via the block's declarations addToBlock(statement, contextBlock); } // append unresolved references to result (for display on console) if (!parser.setDelayedReferencesAfterParsing()) { for (DelayedReference ref : parser.getUnresolvedReferences()) { if (!ref.isOptional()) { log.warning("Unresolved reference: " + ref.toString()); errors.add("Unresolved reference: " + ref.toString()); } } } for (ParsingError err : parser.getInjector().getErrorList()) { errors.add(err.getMessage()); } if (statement != null) { EValidator v = EValidator.Registry.INSTANCE.getEValidator(statement.eClass().getEPackage()); BasicDiagnostic diagnostics = new BasicDiagnostic(); v.validate(statement, diagnostics, new HashMap<Object, Object>()); // append constraint violations to result (for display on console) logAndCollectErrors(diagnostics, errors); saveParserState(parser); interpreter.push(stackFrame); try { result.add(interpreter.evaluateStatement(statement)); } catch (Throwable t) { if (errors.size() + (constraintViolations == null ? 0 : constraintViolations.size()) > 0) { // Parser already had errors, so runtime failure was to // be expected; simply log log.info("Exception during execution: " + t.getMessage()); errors.add(t.getMessage()); } else { // probably an internal error log.severe("Error during execution although parser didn't find any errors: " + t.getMessage()); throw new RuntimeException(t); } } finally { if (interpreter.getCallstack().size() > 0) { interpreter.pop(); } } } else { result.add(null); } // append constraint violations to result (for display on console) if (constraintViolations != null) { for (Exception constraintViolation : constraintViolations) { log.severe("Constraint violation: " + constraintViolation.getMessage() + " on element " + constraintViolation.getMessage()); errors.add("Constraint violation: " + constraintViolation.getMessage() + " on element " + constraintViolation.getMessage()); } } } return new ExecuteResult<LinkEndMetaObject, TypeUsage, ClassUsage>((RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>[]) result .toArray(new RunletObject<?, ?, ?>[0]), errors.toArray(new String[0])); } /** * Implementors should add the <code>statement</code> at the end of the <code>contextBlock</code> * @param statement * @param contextBlock */ abstract protected void addToBlock(StatementType statement, Block contextBlock); /** * Parse a single statement. Implement this method by calling the correct method on your concrete * {@link ObservableInjectingParser} to parse a single statement. * @param parser * @return */ protected abstract StatementType parseStatement(ObservableInjectingParser parser) throws RecognitionException; /** * Return the bit as <code>int</code> that separates statements according to the syntax of the * concrete parser. * You will find this bit declaration on the Parser class inheriting from {@link ObservableInjectingParser}. * (e.g. the constant SEMICOLON). * @return */ protected abstract int getStatementSeperatorBit(); /** * Factory method for creating a parser object from a statement string. * * @param statementString * @return */ protected abstract ObservableInjectingParser createParser(String statementString); /** * Helper method to save the internal parser state for correct parsing of * further statements. * * @param parser */ private void saveParserState(ObservableInjectingParser parser) { contextManager = parser.getContextManager(); currentContextStack = parser.getCurrentContextStack(); } /** * Helper method to restore the internal parser state for correct parsing of * subsequent statements. * * @param parser */ private void restoreParserState(ObservableInjectingParser parser) { if (contextManager != null) { parser.initParser(contextManager, currentContextStack); } else { parser.addContext(new ResolvedModelElementProxy(contextBlock), "block"); } } /** * Getter method for external inspection of the runtime context. Must be * public but not intended for public usage. * * @return */ public Block getContextBlock() { return contextBlock; } @SuppressWarnings("unchecked") public Block getInterpreterOutermostBlockWrapper() { return (Block)currentContextStack.peek(); } /** * Getter method for external inspection of the runtime stack. Must be * public but not inteded for public usage. * * @return */ public StackFrame<LinkEndMetaObject, TypeUsage, ClassUsage, SignatureImplementationType> getStackFrame() { return stackFrame; } public AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, StatementType, ExpressionType, SignatureImplementationType, StackFrameType, NativeType, InterpreterType> getInterpreter() { return interpreter; } public ResourceSet getConnection() { return conn; } protected void setStackFrame(StackFrameType stackFrame) { this.stackFrame = stackFrame; } protected void setContextBlock(Block contextBlock) { this.contextBlock = contextBlock; } protected void setInterpreter( AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, StatementType, ExpressionType, SignatureImplementationType, StackFrameType, NativeType, InterpreterType> interpreter) { this.interpreter = interpreter; } }