/*******************************************************************************
* 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.runner.modelcheck;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.xtext.common.types.JvmType;
import com.google.inject.Inject;
import de.gebit.integrity.classloading.IntegrityClassLoader;
import de.gebit.integrity.dsl.Call;
import de.gebit.integrity.dsl.CustomOperation;
import de.gebit.integrity.dsl.EnumValue;
import de.gebit.integrity.dsl.FixedParameterName;
import de.gebit.integrity.dsl.FixedResultName;
import de.gebit.integrity.dsl.KeyValuePair;
import de.gebit.integrity.dsl.MethodReference;
import de.gebit.integrity.dsl.NamedCallResult;
import de.gebit.integrity.dsl.NamedResult;
import de.gebit.integrity.dsl.NestedObject;
import de.gebit.integrity.dsl.Operation;
import de.gebit.integrity.dsl.Parameter;
import de.gebit.integrity.dsl.ParameterName;
import de.gebit.integrity.dsl.ParameterTableHeader;
import de.gebit.integrity.dsl.ParameterTableValue;
import de.gebit.integrity.dsl.ResultTableHeader;
import de.gebit.integrity.dsl.StandardOperation;
import de.gebit.integrity.dsl.StaticValue;
import de.gebit.integrity.dsl.Suite;
import de.gebit.integrity.dsl.SuiteParameter;
import de.gebit.integrity.dsl.TableTest;
import de.gebit.integrity.dsl.TableTestRow;
import de.gebit.integrity.dsl.Test;
import de.gebit.integrity.dsl.TypedNestedObject;
import de.gebit.integrity.dsl.Value;
import de.gebit.integrity.dsl.ValueOrEnumValueOrOperation;
import de.gebit.integrity.dsl.ValueOrEnumValueOrOperationCollection;
import de.gebit.integrity.dsl.Variable;
import de.gebit.integrity.dsl.VariableVariable;
import de.gebit.integrity.exceptions.MethodNotFoundException;
import de.gebit.integrity.exceptions.ModelRuntimeLinkException;
import de.gebit.integrity.modelsource.ModelSourceExplorer;
import de.gebit.integrity.modelsource.ModelSourceInformationElement;
/**
* Default implementation of a {@link ModelChecker}.
*
* @author Rene Schneider - initial API and implementation
*
*/
public class DefaultModelChecker implements ModelChecker {
/**
* The classloader to use for class resolving.
*/
@Inject
protected IntegrityClassLoader classLoader;
/**
* The model source explorer.
*/
@Inject
protected ModelSourceExplorer modelSourceExplorer;
@Override
public void check(Test aTest) throws ModelRuntimeLinkException {
if (aTest.getDefinition() == null || aTest.getDefinition().getName() == null) {
throw new ModelRuntimeLinkException("Failed to resolve test definition for test statement", aTest,
modelSourceExplorer.determineSourceInformation(aTest));
}
if (aTest.getDefinition().getFixtureMethod() == null
|| aTest.getDefinition().getFixtureMethod().getMethod() == null) {
throw new ModelRuntimeLinkException("Failed to resolve test fixture for test definition", aTest,
modelSourceExplorer.determineSourceInformation(aTest));
}
tryFixtureMethodResolve(aTest.getDefinition().getFixtureMethod());
String tempFixtureName = aTest.getDefinition().getName();
checkParameters(aTest.getParameters(), tempFixtureName);
for (NamedResult tempNamedResult : aTest.getResults()) {
if (tempNamedResult.getName() == null) {
throw new ModelRuntimeLinkException("Failed to resolve named result name", tempNamedResult,
modelSourceExplorer.determineSourceInformation(tempNamedResult));
}
if (tempNamedResult.getName() instanceof FixedResultName) {
checkSingleFixedNamedResult((FixedResultName) tempNamedResult.getName(), tempFixtureName);
}
checkValueContainer(tempNamedResult.getValue());
}
}
@Override
public void check(Call aCall) throws ModelRuntimeLinkException {
if (aCall.getDefinition() == null || aCall.getDefinition().getName() == null) {
throw new ModelRuntimeLinkException("Failed to resolve call definition for call statement", aCall,
modelSourceExplorer.determineSourceInformation(aCall));
}
if (aCall.getDefinition().getFixtureMethod() == null
|| aCall.getDefinition().getFixtureMethod().getMethod() == null) {
throw new ModelRuntimeLinkException("Failed to resolve call fixture for call definition", aCall,
modelSourceExplorer.determineSourceInformation(aCall));
}
tryFixtureMethodResolve(aCall.getDefinition().getFixtureMethod());
String tempFixtureName = aCall.getDefinition().getName();
checkParameters(aCall.getParameters(), tempFixtureName);
if (aCall.getResult() != null) {
checkVariableVariable(aCall.getResult());
}
for (NamedCallResult tempResult : aCall.getResults()) {
checkVariableVariable(tempResult.getTarget());
}
}
@Override
public void check(TableTest aTableTest) throws ModelRuntimeLinkException {
if (aTableTest.getDefinition() == null || aTableTest.getDefinition().getName() == null) {
throw new ModelRuntimeLinkException("Failed to resolve test definition for tabletest statement", aTableTest,
modelSourceExplorer.determineSourceInformation(aTableTest));
}
if (aTableTest.getDefinition().getFixtureMethod() == null
|| aTableTest.getDefinition().getFixtureMethod().getMethod() == null) {
throw new ModelRuntimeLinkException("Failed to resolve test fixture for test definition", aTableTest,
modelSourceExplorer.determineSourceInformation(aTableTest));
}
tryFixtureMethodResolve(aTableTest.getDefinition().getFixtureMethod());
String tempFixtureName = aTableTest.getDefinition().getName();
// First check the normal parameters of the table
checkParameters(aTableTest.getParameters(), tempFixtureName);
// Now look at the headers for parameters...
for (ParameterTableHeader tempTableHeader : aTableTest.getParameterHeaders()) {
if (tempTableHeader.getName() == null) {
throw new ModelRuntimeLinkException("Failed to resolve parameter name", tempTableHeader,
modelSourceExplorer.determineSourceInformation(tempTableHeader));
}
checkSingleParameterName(tempTableHeader.getName(), tempFixtureName);
}
// ...and results!
for (ResultTableHeader tempTableHeader : aTableTest.getResultHeaders()) {
if (tempTableHeader.getName() != null) {
if (tempTableHeader.getName() instanceof FixedResultName) {
checkSingleFixedNamedResult((FixedResultName) tempTableHeader.getName(), tempFixtureName);
}
}
}
// Now finally look at all the cells
for (TableTestRow tempRow : aTableTest.getRows()) {
for (ParameterTableValue tempValue : tempRow.getValues()) {
checkValueContainer(tempValue.getValue());
}
}
}
/**
* Used to keep track on which methods a class loading has already been attempted. Subsequent loadings are assumed
* to deliver the same result and thus omittable.
*/
private Set<MethodReference> resolvedFixtureMethods = new HashSet<MethodReference>();
private void tryFixtureMethodResolve(MethodReference aMethodReference) {
if (resolvedFixtureMethods.contains(aMethodReference)) {
return;
}
resolvedFixtureMethods.add(aMethodReference);
try {
classLoader.loadMethod(aMethodReference);
} catch (ClassNotFoundException exc) {
ModelSourceInformationElement tempSourceInfo = modelSourceExplorer
.determineSourceInformation(aMethodReference);
String tempClassName = tempSourceInfo.getSnippet().split("#")[0].trim();
throw new ModelRuntimeLinkException("Failed to resolve fixture class '" + tempClassName + "'",
aMethodReference, tempSourceInfo, exc);
} catch (MethodNotFoundException exc) {
ModelSourceInformationElement tempSourceInfo = modelSourceExplorer
.determineSourceInformation(aMethodReference);
throw new ModelRuntimeLinkException("Failed to resolve fixture method '" + exc.getMessage() + "'",
aMethodReference, tempSourceInfo, exc);
}
}
/**
* Used to keep track on which operation classes a class loading has already been attempted. Subsequent loadings are
* assumed to deliver the same result and thus omittable.
*/
private Set<JvmType> resolvedOperationClasses = new HashSet<JvmType>();
@Override
public void check(CustomOperation aCustomOperation) throws ModelRuntimeLinkException {
if (aCustomOperation.getDefinition() == null) {
throw new ModelRuntimeLinkException("Failed to resolve operation definition for custom operation",
aCustomOperation, modelSourceExplorer.determineSourceInformation(aCustomOperation));
} else if (aCustomOperation.getDefinition().getOperationType() == null
|| aCustomOperation.getDefinition().getOperationType().getType() == null) {
throw new ModelRuntimeLinkException("Failed to resolve operation class for custom operation definition",
aCustomOperation, modelSourceExplorer.determineSourceInformation(aCustomOperation));
}
JvmType tempType = aCustomOperation.getDefinition().getOperationType().getType();
if (resolvedOperationClasses.contains(tempType)) {
return;
}
resolvedOperationClasses.add(tempType);
try {
classLoader.loadClass(tempType);
} catch (ClassNotFoundException exc) {
ModelSourceInformationElement tempSourceInfo = modelSourceExplorer.determineSourceInformation(tempType);
String tempClassName = tempSourceInfo.getSnippet().split("#")[0].trim();
throw new ModelRuntimeLinkException("Failed to resolve operation class '" + tempClassName + "'", tempType,
tempSourceInfo, exc);
}
if (aCustomOperation.getPrefixOperand() != null) {
checkValueContainer(aCustomOperation.getPrefixOperand());
}
if (aCustomOperation.getPostfixOperand() != null) {
checkValueContainer(aCustomOperation.getPostfixOperand());
}
}
/**
* Checks a list of parameters (of a test or call).
*
* @param someParameters
* a list of parameters to check
* @param aFixtureName
* the fixture name that these parameters belong to (used for error output)
*/
protected void checkParameters(List<Parameter> someParameters, String aFixtureName) {
for (Parameter tempParameter : someParameters) {
if (tempParameter.getName() == null) {
throw new ModelRuntimeLinkException("Failed to resolve parameter name", tempParameter,
modelSourceExplorer.determineSourceInformation(tempParameter));
}
checkSingleParameterName(tempParameter.getName(), aFixtureName);
if (tempParameter.getValue() != null) {
checkValueContainer(tempParameter.getValue());
}
}
}
/**
* Checks a single parameter name of a test or call.
*
* @param aParameterName
* the parameter name to check
* @param aTestOrCallName
* the test/call name that this parameter belongs to (used for error output)
*/
protected void checkSingleParameterName(ParameterName aParameterName, String aTestOrCallName) {
if (aParameterName instanceof FixedParameterName) {
// Fixed parameters must be connected to a fixtures' parameter annotation
FixedParameterName tempFixedParameterName = (FixedParameterName) aParameterName;
if (tempFixedParameterName.getAnnotation() == null
|| tempFixedParameterName.getAnnotation().getAnnotation() == null) {
ModelSourceInformationElement tempSourceInfo = modelSourceExplorer
.determineSourceInformation(tempFixedParameterName);
throw new ModelRuntimeLinkException("Failed to resolve parameter name '" + tempSourceInfo.getSnippet()
+ "' in test/call '" + aTestOrCallName + "'.", tempFixedParameterName, tempSourceInfo);
}
}
}
/**
* Checks a single fixed named result of a test.
*
* @param aFixedResultName
* the fixed result name to check
* @param aTestName
* the test name that this result belongs to (for error output)
*/
protected void checkSingleFixedNamedResult(FixedResultName aFixedResultName, String aTestName) {
if (aFixedResultName.getField() == null || aFixedResultName.getField().getType() == null) {
ModelSourceInformationElement tempSourceInfo = modelSourceExplorer
.determineSourceInformation(aFixedResultName);
throw new ModelRuntimeLinkException("Failed to resolve named result field '" + tempSourceInfo.getSnippet()
+ "' in test '" + aTestName + "'.", aFixedResultName, tempSourceInfo);
}
}
@Override
public void check(Suite aSuite) throws ModelRuntimeLinkException {
if (aSuite.getDefinition() == null) {
ModelSourceInformationElement tempSourceInfo = modelSourceExplorer.determineSourceInformation(aSuite);
throw new ModelRuntimeLinkException("Failed to resolve suite", aSuite, tempSourceInfo);
}
if (aSuite.getDefinition().getName() == null) {
ModelSourceInformationElement tempSourceInfo = modelSourceExplorer.determineSourceInformation(aSuite);
throw new ModelRuntimeLinkException(
"Failed to resolve suite referenced in suite call '" + tempSourceInfo.getSnippet() + "'",
aSuite.getDefinition(), tempSourceInfo);
}
for (SuiteParameter tempParameter : aSuite.getParameters()) {
if (tempParameter.getName() == null || tempParameter.getName().getName() == null) {
throw new ModelRuntimeLinkException("Failed to resolve suite parameter name", tempParameter,
modelSourceExplorer.determineSourceInformation(tempParameter));
}
checkValueContainer(tempParameter.getValue());
}
}
/**
* Check a {@link ValueOrEnumValueOrOperationCollection}.
*
* @param aValue
*/
protected void checkValueContainer(ValueOrEnumValueOrOperationCollection aValue) {
if (aValue.getValue() == null) {
throw new ModelRuntimeLinkException("Failed to resolve value", aValue,
modelSourceExplorer.determineSourceInformation(aValue));
} else {
checkValueContainer(aValue.getValue());
}
for (ValueOrEnumValueOrOperation tempMoreValue : aValue.getMoreValues()) {
checkValueContainer(tempMoreValue);
}
}
/**
* Check a {@link ValueOrEnumValueOrOperation}.
*
* @param aValue
*/
protected void checkValueContainer(ValueOrEnumValueOrOperation aValue) {
if (aValue instanceof Value) {
checkValueContainer((Value) aValue);
} else if (aValue instanceof EnumValue) {
checkEnumValue((EnumValue) aValue);
} else if (aValue instanceof Operation) {
checkOperation((Operation) aValue);
}
}
/**
* Check a {@link Value}.
*
* @param aValue
*/
protected void checkValueContainer(Value aValue) {
if (aValue instanceof StaticValue) {
// no further checks supported
} else if (aValue instanceof Variable) {
checkVariable((Variable) aValue);
} else if (aValue instanceof NestedObject) {
checkNestedObject((NestedObject) aValue);
} else if (aValue instanceof TypedNestedObject) {
checkTypedNestedObject((TypedNestedObject) aValue);
}
}
/**
* Checks an {@link EnumValue}.
*
* @param aValue
*/
protected void checkEnumValue(EnumValue aValue) {
if (aValue.getEnumValue() == null) {
throw new ModelRuntimeLinkException("Failed to resolve enum value", aValue.getEnumValue(),
modelSourceExplorer.determineSourceInformation(aValue.getEnumValue()));
}
}
/**
* Checks an {@link Operation}.
*
* @param anOperation
*/
protected void checkOperation(Operation anOperation) {
if (anOperation instanceof CustomOperation) {
check((CustomOperation) anOperation);
} else if (anOperation instanceof StandardOperation) {
StandardOperation tempOperation = (StandardOperation) anOperation;
if (tempOperation.getFirstOperand() != null) {
checkValueContainer(tempOperation.getFirstOperand());
}
for (ValueOrEnumValueOrOperation tempOperand : tempOperation.getMoreOperands()) {
checkValueContainer(tempOperand);
}
}
}
/**
* Checks a {@link Variable}.
*
* @param aVariable
*/
protected void checkVariable(Variable aVariable) {
if (aVariable.getName() == null || aVariable.getName().getName() == null) {
throw new ModelRuntimeLinkException("Failed to resolve variable name", aVariable,
modelSourceExplorer.determineSourceInformation(aVariable));
}
}
/**
* Checks a {@link VariableVariable}.
*
* @param aVariable
*/
protected void checkVariableVariable(VariableVariable aVariable) {
if (aVariable.getName() == null || aVariable.getName().getName() == null) {
throw new ModelRuntimeLinkException("Failed to resolve variable name", aVariable,
modelSourceExplorer.determineSourceInformation(aVariable));
}
}
/**
* Checks a {@link NestedObject}.
*
* @param anObject
*/
protected void checkNestedObject(NestedObject anObject) {
for (KeyValuePair tempPair : anObject.getAttributes()) {
checkValueContainer(tempPair.getValue());
}
}
/**
* Checks a {@link TypedNestedObject}.
*
* @param anObject
*/
protected void checkTypedNestedObject(TypedNestedObject anObject) {
checkNestedObject(anObject.getNestedObject());
}
}