/*******************************************************************************
* 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.validation;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;
import static java.util.Collections.emptySet;
import java.text.ParseException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.common.types.JvmAnnotationReference;
import org.eclipse.xtext.common.types.JvmFormalParameter;
import org.eclipse.xtext.util.Pair;
import org.eclipse.xtext.util.PolymorphicDispatcher;
import org.eclipse.xtext.validation.Check;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import de.gebit.integrity.annotation.FixtureParameterAssessment;
import de.gebit.integrity.annotation.JvmFixtureEvaluation;
import de.gebit.integrity.dsl.Call;
import de.gebit.integrity.dsl.ConstantDefinition;
import de.gebit.integrity.dsl.DateAndTimeValue;
import de.gebit.integrity.dsl.DateValue;
import de.gebit.integrity.dsl.DslPackage;
import de.gebit.integrity.dsl.MethodReference;
import de.gebit.integrity.dsl.PackageDefinition;
import de.gebit.integrity.dsl.Parameter;
import de.gebit.integrity.dsl.ParameterTableHeader;
import de.gebit.integrity.dsl.SuiteDefinition;
import de.gebit.integrity.dsl.SuiteStatementWithResult;
import de.gebit.integrity.dsl.TableTest;
import de.gebit.integrity.dsl.Test;
import de.gebit.integrity.dsl.TimeValue;
import de.gebit.integrity.dsl.VariableDefinition;
import de.gebit.integrity.utils.DateUtil;
import de.gebit.integrity.utils.IntegrityDSLUtil;
/**
* These validators perform some more extensive validation on parts of the syntax tree.
*
* @author Rene Schneider - initial API and implementation
*
*/
// SUPPRESS CHECKSTYLE LONG MethodLength
public class DSLJavaValidator extends AbstractDSLJavaValidator {
/** Evaluates JvmFixtures and provides tooling for handling the abstract types. */
@Inject
private JvmFixtureEvaluation evaluator;
/** Polymorphical dispatches calls to _checkParameter" methods. */
private PolymorphicDispatcher<Void> checkParameterDispatcher = PolymorphicDispatcher
.createForSingleTarget("_checkParameter", this);
/**
* Checks whether a given {@link DateValue} is actually correct (finds errors like days which don't exist in the
* given month).
*
* @param aValue
*/
@Check
public void checkIfDatesAreValid(DateValue aValue) {
try {
DateUtil.convertDateValue(aValue);
} catch (ParseException exc) {
error("The date entered is not valid!", null);
}
}
/**
* Checks whether a given {@link TimeValue} is actually correct.
*
* @param aValue
*/
@Check
public void checkIfTimesAreValid(TimeValue aValue) {
try {
DateUtil.convertTimeValue(aValue);
} catch (ParseException exc) {
error("The time entered is not valid!", null);
}
}
/**
* Checks whether a given {@link DateAndTimeValue} is actually correct.
*
* @param aValue
*/
@Check
public void checkIfDateAndTimesAreValid(DateAndTimeValue aValue) {
try {
DateUtil.convertDateAndTimeValue(aValue);
} catch (ParseException exc) {
error("The date and/or time entered is not valid!", null);
}
}
/**
* Checks whether a variable definition contains dots, which would be illegal (issue #10).
*
* @param anEntity
*/
@Check
public void checkIfVariableDefinitionsAreValid(VariableDefinition anEntity) {
if (anEntity.getName() != null) {
String tempName = anEntity.getName().getName();
if (tempName != null) {
if (tempName.contains(".")) {
error("Variable definitions may not be fully or partly qualified. Please put the variable in the according packagedef to qualify it!",
null);
}
}
}
}
/**
* Checks whether a parameterized constant is defined in a package scope. It is not supported currently within
* suites.
*
* @param aConstant
*/
@Check
public void checkIfConstantParameterizationIsPossible(ConstantDefinition aConstant) {
if (aConstant.getParameterized() != null) {
if (!(aConstant.eContainer() instanceof PackageDefinition)) {
error("Parameterized constants are only allowed in the scope of a package, not within suites", null);
}
}
}
/**
* Checks for redundant private modifier.
*
* @param anEntity
*/
@Check
public void checkForPrivateVariableInSuite(VariableDefinition anEntity) {
if (anEntity.getPrivate() != null && (anEntity.eContainer() instanceof SuiteDefinition)) {
warning("Redundant visibility modifier: variable definitions inside suites are always 'private'",
DslPackage.Literals.VARIABLE_DEFINITION__PRIVATE);
}
}
/**
* Checks for redundant private modifier.
*
* @param anEntity
*/
@Check
public void checkForPrivateConstantInSuite(ConstantDefinition anEntity) {
if (anEntity.getPrivate() != null && (anEntity.eContainer() instanceof SuiteDefinition)) {
warning("Redundant visibility modifier: constant definitions inside suites are always 'private'",
DslPackage.Literals.CONSTANT_DEFINITION__PRIVATE);
}
}
/**
* Checks for missing parameters.
*
* @param aCall
* Call to be verified.
*/
@Check
public void checkParameter(SuiteStatementWithResult aCall) {
checkParameterDispatcher.invoke(aCall);
}
/** Polymorphic Dispatch of {@link #checkParameter(Call)}. */
protected void _checkParameter(Call aCall) {
Set<String> tempMandatoryParameter = getMandatoryParameterNamesOf(aCall.getDefinition().getFixtureMethod());
Set<String> tempSpecifiedParameter = Sets.newHashSet(transform(aCall.getParameters(), FUNC_PARAMETER_NAME));
checkForMissingParameter(tempMandatoryParameter, tempSpecifiedParameter);
}
/** Polymorphic Dispatch of {@link #checkParameter(Call)}. */
protected void _checkParameter(Test aTest) {
Set<String> tempMandatoryParameter = getMandatoryParameterNamesOf(aTest.getDefinition().getFixtureMethod());
Set<String> tempSpecifiedParameter = Sets.newHashSet(transform(aTest.getParameters(), FUNC_PARAMETER_NAME));
checkForMissingParameter(tempMandatoryParameter, tempSpecifiedParameter);
}
/** Polymorphic Dispatch of {@link #checkParameter(Call)}. */
protected void _checkParameter(TableTest aTableTest) {
Set<String> tempMandatoryParameter = getMandatoryParameterNamesOf(
aTableTest.getDefinition().getFixtureMethod());
Set<String> tempSpecifiedParameter = Sets
.newHashSet(transform(aTableTest.getParameters(), FUNC_PARAMETER_NAME));
Iterables.addAll(tempSpecifiedParameter,
transform(aTableTest.getParameterHeaders(), FUNC_PARAMETER_HEADER_NAME));
checkForMissingParameter(tempMandatoryParameter, tempSpecifiedParameter);
}
/** Polymorphic Dispatch Default Case of {@link #checkParameter(Call)}. */
protected void _checkParameter(SuiteStatementWithResult aCall) { /* Does nothing. */
}
/**
* Checks for duplicate parameters.
*
* @param aParameter
* the parameter to be checked
*/
@Check
protected void checkParameterName(Parameter aParameter) {
EObject tempContainer = aParameter.eContainer();
List<Parameter> tempParameters = null;
if (tempContainer instanceof Test) {
tempParameters = ((Test) tempContainer).getParameters();
} else if (tempContainer instanceof Call) {
tempParameters = ((Call) tempContainer).getParameters();
} else if (tempContainer instanceof TableTest) {
tempParameters = ((TableTest) tempContainer).getParameters();
}
if (tempParameters != null) {
for (Parameter tempOtherParameter : tempParameters) {
if (tempOtherParameter != aParameter && tempOtherParameter.getName() != null
&& aParameter.getName() != null
&& tempOtherParameter.getName().getClass() == aParameter.getName().getClass()) {
if (IntegrityDSLUtil.getParamNameStringFromParameterName(tempOtherParameter.getName())
.equals(IntegrityDSLUtil.getParamNameStringFromParameterName(aParameter.getName()))) {
error("Duplicate parameter", null);
return;
}
}
}
}
}
/**
* Extracts all mandatory parameter names from the given method reference.
*
* @param aMethod
* Method where to get the parameter names from.
* @return All mandatory parameter names.
*/
protected Set<String> getMandatoryParameterNamesOf(MethodReference aMethod) {
if (aMethod == null) {
return emptySet();
}
List<Pair<JvmFormalParameter, JvmAnnotationReference>> tempAnnotatedParameter = evaluator
.getAllAnnotatedParameter(aMethod, FixtureParameterAssessment.ACCEPTED_ANNOTATION);
List<FixtureParameterAssessment> tempParameter = wrap(tempAnnotatedParameter);
return collectMandatoryParameterNames(tempParameter);
}
/**
* Checks if some parameter is missing and reports this.
*
* @param someMandatoryParameters
* Mandatory parameter names.
* @param someSpecifiedParameter
* Specified parameter names.
*/
protected void checkForMissingParameter(Set<String> someMandatoryParameters, Set<String> someSpecifiedParameter) {
SetView<String> tempDifference = Sets.difference(someMandatoryParameters, someSpecifiedParameter);
if (!tempDifference.isEmpty()) {
String tempAllMissing = Joiner.on(", ").join(tempDifference);
error("Missing mandatory parameter(s): " + tempAllMissing, null);
}
}
/**
* Wraps the parameter/annotation tuples in assessment objects for easier access and caches the processing results.
*
* @param someParameters
* Parameters to wrap
* @return Assessment classes for easier access.
*/
public List<FixtureParameterAssessment> wrap(
Iterable<Pair<JvmFormalParameter, JvmAnnotationReference>> someParameters) {
List<FixtureParameterAssessment> tempResult = new LinkedList<FixtureParameterAssessment>();
for (Pair<JvmFormalParameter, JvmAnnotationReference> tempParameterPair : someParameters) {
tempResult.add(new FixtureParameterAssessment(evaluator, tempParameterPair));
}
return tempResult;
}
/**
* Collects all mandatory parameter names and returns them.
*
* @param aCollection
* Some parameter to get the mandatory names from.
* @return
*/
protected Set<String> collectMandatoryParameterNames(Collection<FixtureParameterAssessment> aCollection) {
Iterable<FixtureParameterAssessment> tempMandatory = filter(aCollection,
FixtureParameterAssessment.IS_MANDATORY);
Iterable<String> tempMandatoryNames = transform(tempMandatory, FixtureParameterAssessment.NAME);
return Sets.newLinkedHashSet(tempMandatoryNames);
}
/** Maps parameters to their names. */
private final Function<Parameter, String> FUNC_PARAMETER_NAME = new Function<Parameter, String>() {
public String apply(Parameter aParameter) {
return evaluator.getParameterNameOf(aParameter.getName());
};
};
/** Maps parameters to their names. */
private final Function<ParameterTableHeader, String> FUNC_PARAMETER_HEADER_NAME = new Function<ParameterTableHeader, String>() {
public String apply(ParameterTableHeader aParameterHeader) {
return evaluator.getParameterNameOf(aParameterHeader.getName());
};
};
}