package fr.imag.adele.apam.maven.plugin.validation;
import java.util.Collection;
import fr.imag.adele.apam.declarations.ComponentDeclaration;
import fr.imag.adele.apam.declarations.ConstrainedReference;
import fr.imag.adele.apam.declarations.PropertyDefinition;
import fr.imag.adele.apam.declarations.references.Reference;
import fr.imag.adele.apam.declarations.references.ResolvableReference;
import fr.imag.adele.apam.declarations.references.components.ComponentReference;
import fr.imag.adele.apam.declarations.references.resources.ResourceReference;
import fr.imag.adele.apam.maven.plugin.validation.property.CollectionType;
import fr.imag.adele.apam.maven.plugin.validation.property.Type;
import fr.imag.adele.apam.maven.plugin.validation.property.TypeParser;
import fr.imag.adele.apam.util.ApamFilter;
import fr.imag.adele.apam.util.Attribute;
/**
* The common validation for constrained referenced defined in components
*
* @author vega
*
*/
public class ConstrainedReferenceValidator<R extends ConstrainedReference> extends AbstractValidator<R,Void> {
/**
* The parser used to validate all defined property types
*/
private final TypeParser typeParser;
/**
* The validator used to validate substitution expressions
*/
private final ContextualExpressionValidator expressionValidator;
public ConstrainedReferenceValidator(ComponentValidator<?> parent) {
super(parent);
this.typeParser = new TypeParser();
this.expressionValidator = new ContextualExpressionValidator(this,typeParser);
}
public Void validate(R reference) {
initializeState(reference);
validateTarget();
validateConstraints();
return null;
}
/**
* validate the target of the reference
*/
protected void validateTarget() {
if (getTargetReference() == null) {
return;
}
else if (targetIsComponent() && getTarget() == null) {
error("the specified target component "+getTargetReference().getName()+ " is not available");
}
else if (targetIsResource()) {
checkResourceExists(getTargetReference(ResourceReference.class));
}
}
/**
* validate the associated constraints
*/
protected void validateConstraints() {
validate(getReference().getImplementationConstraints(),"error in Implementation constraints");
validate(getReference().getImplementationPreferences(),"error in Implementation preferences");
validate(getReference().getInstanceConstraints(),"error in Instance constraints");
validate(getReference().getInstancePreferences(),"error in Instance preferences");
}
/**
* Parses the specified type
*/
protected Type getType(PropertyDefinition property) {
return typeParser.parse(property.getType());
}
/**
* Validates the specified filter collection
*/
private void validate(Collection<String> filters, String message) {
for (String filter : filters) {
validate(filter,message);
}
}
/**
* Validates the specified filter
*/
private void validate(String filter, String message) {
/*
* validate syntax
*/
ApamFilter expression = parseFilter(filter);
if (expression == null) {
error(message+", invalid filter syntax "+quoted(filter));
return;
}
/*
* validate typing
*/
validate(expression,message);
}
/**
* Validates the specified filter
*/
private void validate(ApamFilter expression, String message) {
initializeFilter(expression);
switch (expression.op) {
case ApamFilter.AND:
case ApamFilter.OR:
/*
* validate filters of sub-expression
*/
for (ApamFilter subExpression : ((ApamFilter[]) expression.value)) {
validate(subExpression,message);
}
break;
case ApamFilter.NOT:
/*
* validate filters of sub-expression
*/
validate((ApamFilter)expression.value,message);
break;
case ApamFilter.SUBSTRING:
case ApamFilter.EQUAL:
case ApamFilter.GREATER:
case ApamFilter.LESS:
case ApamFilter.APPROX:
case ApamFilter.SUBSET:
case ApamFilter.SUPERSET:
case ApamFilter.PRESENT:
/*
* validate attribute comparison expression
*/
validate(expression.op,expression.attr,getValue(expression),message);
break;
}
resetFilter();
}
/**
* Validates the specified attribute comparison expression is valid
*/
private void validate(int operation, String propertyName, String value, String message) {
if (targetIsResource()) {
warning(message+", substitute expression "+quoted(filter.toString())+ " cannot be completely validated at build-time");
}
/*
* Validate property is well defined
*/
PropertyDefinition property = getTarget() != null ? getTarget().getPropertyDefinition(propertyName) : null;
if (getTarget() != null) {
if (property == null && !Attribute.isFinalAttribute(propertyName)) {
error(message+", invalid filter "+quoted(filter.toString())+", the property "+quoted(propertyName)+ " is not defined in component "+getTarget().getName());
}
if (property != null && property.getDefaultValue() != null && expressionValidator.isContextualExpression(property.getDefaultValue())) {
error(message+", invalid filter "+quoted(filter.toString())+", the property "+quoted(propertyName)+ " can not be used in filters because it is a substitution "+quoted(property.getDefaultValue()));
}
}
/*
* check value is valid with respect to the type of the property
*/
if (value == null)
return;
Type propertyType = property != null ? getType(property) : null;
if (expressionValidator.isContextualExpression(value)) {
/*
* If the value is a substitution expression try to parse and type it. Verify the type of the
* expression is comparable to the type of the property
*
* TODO We need to validate what are the automatic conversions performed by the filter at runtime
*/
Type expressionType = validate(value,expressionValidator);
if (expressionType != null && propertyType != null &&
!expressionType.isAssignableTo(propertyType) && !propertyType.isAssignableTo(expressionType)) {
error(message+", invalid filter "+quoted(filter.toString())+", the property "+quoted(propertyName)+ " has type "+propertyType+" and the value "+quoted(value)+" has type "+expressionType);
}
}
else {
/*
* Operators involving sets perform automatic conversion of the property to a collection
*/
if (operation == ApamFilter.SUBSET || operation == ApamFilter.SUPERSET) {
if (propertyType != null && !(propertyType instanceof CollectionType)) {
propertyType = new CollectionType(propertyType, false);
}
}
/*
* If the property is a collection, the equality operator is overloaded to mean set containment
*/
if (operation == ApamFilter.EQUAL && propertyType != null && propertyType instanceof CollectionType) {
propertyType = ((CollectionType)propertyType).getElementType();
}
/*
* For normal values, just validates the literal is accepted by the type
*/
if (propertyType != null && propertyType.value(value) == null) {
error(message+", invalid filter "+quoted(filter.toString())+", the value "+quoted(value)+" is not valid for the expected type "+quoted(propertyType.getName()));
}
}
}
/**
* The declaration being validated
*/
private R reference;
/**
* The target component, if defined
*/
private ComponentDeclaration target;
/**
* The filter being validated
*/
private ApamFilter filter;
/**
* Initializes the state of this validator
*/
protected void initializeState(R reference) {
this.reference = reference;
/*
* try to load the target component, if any
*/
this.target = getComponent(getTargetReference(ComponentReference.class),true);
}
protected void initializeFilter(ApamFilter filter) {
this.filter = filter;
}
/**
* The constrained reference declaration that is being validated
*/
protected R getReference() {
return reference;
}
/**
* The referenced target
*/
protected ResolvableReference getTargetReference() {
return getReference().getTarget();
}
/**
* The referenced target cast to the appropriate type
*/
protected <T extends Reference> T getTargetReference(Class<T> kind) {
return kind.isInstance(getTargetReference()) ? kind.cast(getTargetReference()) : null;
}
/**
* Determines if the target is a resource
*/
protected boolean targetIsResource() {
return getTargetReference() != null && getTargetReference(ResourceReference.class) !=null;
}
/**
* Determines if the target is a resource
*/
protected boolean targetIsComponent() {
return getTargetReference() != null && getTargetReference(ComponentReference.class) !=null;
}
/**
* The component target of the current declaration, if one is explicitly defined and is available
* on the repository
*/
protected ComponentDeclaration getTarget() {
return target;
}
public void resetFilter() {
this.filter = null;
}
/**
* Frees all references to the state of validation
*/
@Override
public void resetState() {
this.reference = null;
super.resetState();
}
/**
* Get the encoded value of an attribute in a comparison expression.
*/
private static String getValue(ApamFilter expression) {
/*
* For operations not involving a pattern just return the parsed value
*/
if (expression.op != ApamFilter.SUBSTRING)
return (String) expression.value;
/*
* for pattern matching rebuild the pattern from the parsed values
*/
String[] substrings = (String[]) expression.value;
StringBuilder pattern = new StringBuilder();
for (String substr : substrings) {
if (substr == null) /* wildcard */{
pattern.append('*');
} else /* text */{
pattern.append(ApamFilter.encodeValue(substr));
}
}
return pattern.toString();
}
}