/*******************************************************************************
* Copyright (c) 2013 Rene Schneider, GEBIT Solutions GmbH and others.
* 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
*******************************************************************************/
package de.gebit.integrity.operations.standard;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.inject.Inject;
import com.google.inject.Injector;
import de.gebit.integrity.dsl.StandardOperation;
import de.gebit.integrity.dsl.ValueOrEnumValueOrOperation;
import de.gebit.integrity.operations.UnexecutableException;
import de.gebit.integrity.operations.standard.operands.OperatorNode;
/**
* Abstract base implementation for a modular processor processing standard operations. This processor builds a kind of
* abstract syntax tree for evaluation of operations.
*
* @author Rene Schneider - initial API and implementation
*
*/
public abstract class AbstractModularStandardOperationProcessor implements StandardOperationProcessor {
/**
* A map storing the operator precedence order.
*/
private Map<String, Integer> operatorPrecedences = new HashMap<String, Integer>();
/**
* A map storing the operator node classes for fast access.
*/
private Map<String, Class<? extends OperatorNode<?, ?>>> operatorNodeClasses = new HashMap<String, Class<? extends OperatorNode<?, ?>>>();
/**
* The injector.
*/
@Inject
private Injector injector;
/**
* Creates an instance.
*/
public AbstractModularStandardOperationProcessor() {
initializeOperatorInfo();
}
/**
* Initializes all operators by calls to {@link #addOperatorInfo(String, Class)}.
*/
protected abstract void initializeOperatorInfo();
/**
* Adds an operator.
*
* @param anOperator
* the operator string
* @param aNodeClass
* the node class representing this operator
*/
protected void addOperatorInfo(String anOperator, Class<? extends OperatorNode<?, ?>> aNodeClass) {
operatorPrecedences.put(anOperator, operatorPrecedences.size());
operatorNodeClasses.put(anOperator, aNodeClass);
}
@Override
public Object executeOperation(StandardOperation anOperation) throws UnexecutableException {
try {
OperatorNode<?, ?> tempRootNode = parseOperation(anOperation);
return tempRootNode.evaluate();
} catch (SecurityException exc) {
throw new UnexecutableException(exc);
} catch (IllegalArgumentException exc) {
throw new UnexecutableException(exc);
} catch (NoSuchMethodException exc) {
throw new UnexecutableException(exc);
} catch (InstantiationException exc) {
throw new UnexecutableException(exc);
} catch (IllegalAccessException exc) {
throw new UnexecutableException(exc);
} catch (InvocationTargetException exc) {
throw new UnexecutableException(exc);
}
}
/**
* Parses the given operation.
*
* @param anOperation
* the operation to parse
* @return the AST representing the operation
* @throws SecurityException
* @throws IllegalArgumentException
* @throws NoSuchMethodException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
protected OperatorNode<?, ?> parseOperation(StandardOperation anOperation) throws SecurityException,
IllegalArgumentException, NoSuchMethodException, InstantiationException, IllegalAccessException,
InvocationTargetException {
List<Object> tempOperands = new ArrayList<Object>();
List<String> tempOperators = new ArrayList<String>();
tempOperands.add(anOperation.getFirstOperand());
for (ValueOrEnumValueOrOperation tempOperand : anOperation.getMoreOperands()) {
tempOperands.add(tempOperand);
}
for (String tempOperator : anOperation.getOperators()) {
tempOperators.add(tempOperator);
}
while (tempOperators.size() > 0) {
int tempHighestPreference = -1;
int tempHighestPreferencePos = 0;
for (int i = 0; i < tempOperators.size(); i++) {
String tempOperator = tempOperators.get(i);
int tempPreference = getOperatorPrecedence(tempOperator);
if (tempPreference > tempHighestPreference) {
tempHighestPreference = tempPreference;
tempHighestPreferencePos = i;
}
}
String tempOperator = tempOperators.remove(tempHighestPreferencePos);
Object tempLeftOperand = tempOperands.get(tempHighestPreferencePos); // no remove; will be replaced later!
Object tempRightOperand = tempOperands.remove(tempHighestPreferencePos + 1);
// These shortcuts handle the most-common case of operands being operations themselves. Technically these
// are optional, evaluation would happen anyway during value conversion, but this speeds things up.
if (tempLeftOperand instanceof StandardOperation) {
tempLeftOperand = parseOperation((StandardOperation) tempLeftOperand);
}
if (tempRightOperand instanceof StandardOperation) {
tempRightOperand = parseOperation((StandardOperation) tempRightOperand);
}
OperatorNode<?, ?> tempNewNode = createNode(tempOperator, tempLeftOperand, tempRightOperand);
tempOperands.set(tempHighestPreferencePos, tempNewNode);
}
return (OperatorNode<?, ?>) tempOperands.get(0);
}
/**
* Determines the precedence (ordering) of the given operator.
*
* @param anOperator
* the operator to evaluate
* @return the numeric precedence (higher = evaluate first)
*/
protected int getOperatorPrecedence(String anOperator) {
Integer tempPrecedence = operatorPrecedences.get(anOperator);
if (tempPrecedence == null) {
// Actually this should never happen, but...
throw new RuntimeException("Operator '" + anOperator + "' is unknown!");
}
return tempPrecedence;
}
/**
* Creates a new node.
*
* @param anOperator
* the operator
* @param aLeftOperand
* the left operand (not evaluated)
* @param aRightOperand
* the right operand (not evaluated)
* @return the node
* @throws SecurityException
* @throws NoSuchMethodException
* @throws IllegalArgumentException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
protected OperatorNode<?, ?> createNode(String anOperator, Object aLeftOperand, Object aRightOperand)
throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException,
IllegalAccessException, InvocationTargetException {
Class<? extends OperatorNode<?, ?>> tempClass = operatorNodeClasses.get(anOperator);
if (tempClass == null) {
// Actually this should never happen, but...
throw new RuntimeException("Operator '" + anOperator + "' is unknown!");
}
Constructor<? extends OperatorNode<?, ?>> tempConstructor = tempClass.getConstructor(new Class<?>[] {
Object.class, Object.class });
OperatorNode<?, ?> tempInstance = tempConstructor.newInstance(aLeftOperand, aRightOperand);
injector.injectMembers(tempInstance);
return tempInstance;
}
}