Package fr.imag.adele.apam.maven.plugin.validation

Source Code of fr.imag.adele.apam.maven.plugin.validation.ContextualExpressionValidator

package fr.imag.adele.apam.maven.plugin.validation;

import java.util.Collection;
import java.util.Collections;
import java.util.Set;

import fr.imag.adele.apam.CST;
import fr.imag.adele.apam.declarations.AtomicImplementationDeclaration;
import fr.imag.adele.apam.declarations.ComponentDeclaration;
import fr.imag.adele.apam.declarations.ComponentKind;
import fr.imag.adele.apam.declarations.PropertyDefinition;
import fr.imag.adele.apam.declarations.RelationDeclaration;
import fr.imag.adele.apam.declarations.instrumentation.InstrumentedClass;
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.declarations.references.resources.UnknownReference;
import fr.imag.adele.apam.maven.plugin.validation.property.CollectionType;
import fr.imag.adele.apam.maven.plugin.validation.property.PrimitiveType;
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.Attribute;
import fr.imag.adele.apam.util.Substitute;
import fr.imag.adele.apam.util.Substitute.SplitSub;

/**
* This class performs validation of contextual expressions that can include substitution of expressions
* with navigation of relations and properties in the model
* @author vega
*
*/
public class ContextualExpressionValidator extends AbstractValidator<String,Type> {

  /**
   * The parser used to get the type of referenced properties
   */
  private final TypeParser typeParser;
 
  public ContextualExpressionValidator(AbstractValidator<?,?> parent, TypeParser typeParser) {
    super(parent);
    this.typeParser = typeParser;
  }

  /**
   * Verifies if the value is a substitution expression
   */
  public boolean isContextualExpression(String value) {
    return value.startsWith("@") || value.startsWith("$");
   
  }
 
 
  /**
   * Validates that the value is valid, and tries to infer its Type.
   *
   * Return null if there are errors in the substitution expressions, and may return {@link #RUNTIME_TYPE}
   * if the type can only be determined at runtime
   *
   */
  public Type validate(String value) {

    initializeState(value);
   
    if (value.startsWith("@")) {
      return validateFunctionInvocation();
    }
    else if (value.startsWith("$")) {
      return validateNavigationExpresion();
    }
    else
      return null;
  }
 
  /**
   * Validates that the specified navigation expression is valid in the context of the component where the value is specified
   */
  private Type validateNavigationExpresion() {

    /*
     * TODO We should convert class Substitute into a parser with better error reporting
     */
    SplitSub expression = Substitute.split(value);
    if (expression == null) {
      error("Invalid substitute expression, syntax is incorrect "+value);
      return null;
    }

    /*
     * Get the source of the relations' navigation
     */
    String sourceName            = expression.sourceName;
    ComponentReference<?> sourceReference  = sourceName.equals("this") ? getComponent().getReference() : new ComponentReference<ComponentDeclaration>(sourceName);
    ComponentDeclaration source        = getComponent(sourceReference, true);

    if (source == null) {
      error("Invalid substitute expression "+value+ ", component not found "+sourceReference.getName());
      return null;
    }
   
    /*
     * iteratively get the target and the multiplicity of the relations' navigation
     */
   
    boolean hasMultipleNavigation  = false;
    ResolvableReference target    = source.getReference();

    navigation:
    for (String relationName : expression.depIds != null ? expression.depIds : Collections.<String> emptyList()) {


      /*
       * Otherwise try to best approximate the target of the relation with the build-time information
       */
      if (CST.isFinalRelation(relationName)) {
       
        if (relationName.equals(CST.REL_COMPOSITE) || relationName.equals(CST.REL_COMPOTYPE) || relationName.equals(CST.REL_CONTAINS)) {
          /*
           * For relations that navigate the nested hierarchy of composites, we cannot know the actual types
           * at build-time, as this hierarchy is completely build at runtime. We simply stop navigation
           */
          target = new UnknownReference(new ResourceReference("<RUNTIME>"));
        }       
        else {
          /*
           * For relations that navigate the abstraction levels, we can navigate to more abstract levels, and
           * for more concrete levels we continue validating in the same context
           */
          ComponentKind targetLevel = source.getKind();

          if (relationName.equals(CST.REL_SPEC)) {
            targetLevel = ComponentKind.SPECIFICATION;
          }
          else if (relationName.equals(CST.REL_IMPL) || relationName.equals(CST.REL_IMPLS)) {
            targetLevel = ComponentKind.IMPLEMENTATION;
          }
          else if (relationName.equals(CST.REL_INST) || relationName.equals(CST.REL_INSTS)) {
            targetLevel = ComponentKind.INSTANCE;
          }
         
          ComponentDeclaration sourceAtDifferentLevel =  changeAbstractionLevel(source,targetLevel);
          if (sourceAtDifferentLevel == null) {
            error("Invalid substitute expression "+value+ " , relation "+quoted(relationName)+" not defined for component "+source.getName());
            return null;
          }
         
          source          = sourceAtDifferentLevel;
          target          = source.getReference();
          hasMultipleNavigation  = hasMultipleNavigation || relationName.equals(CST.REL_IMPLS) || relationName.equals(CST.REL_INSTS);
        }
      }
      else {

        /*
         * For normal relations, we get the relation declaration and follow the specified target
         */
        RelationDeclaration relation = source.getRelation(relationName);
       
        if (relation == null) {
          error("Invalid substitute expression "+value+ " , relation "+quoted(relationName)+" not defined in component "+source.getName());
          return null;
        }
       
        target          = relation.getTarget();
        hasMultipleNavigation  = hasMultipleNavigation || relation.isMultiple();
       
        /*
         * If the target is a component, we can move the source one step further
         */
        if (relation.getTarget().as(ComponentReference.class) != null) {
          source = getComponent(relation.getTarget().as(ComponentReference.class),true);
          if (source == null) {
            error("Invalid substitute expression "+value+ " , relation "+quoted(relationName)+" has unknown target "+relation.getTarget().getName());
            return null;
          }
         
        }
       
      }
     
      /*
       * When we get to an interface target, we cannot validate any further
       */
      if (target.as(ResourceReference.class) != null)
        break navigation;
     
    }

   
    /*
     * Get the type of the referenced property
     */
    Type propertyType = null;

    if (! Attribute.isFinalAttribute(expression.attr)) {
     

      /*
       * If the last target cannot be determined at build time, simply give up
       */
      if (target.as(ComponentReference.class) == null) {
        warning("Substitute expression "+value+ ", cannot be completely validated at build-time");
        return Type.NONE;
      }
     
      /*
       * The last computed source of the navigation is used to look for the final property
       */
      PropertyDefinition property = source.getPropertyDefinition(expression.attr);
      if (property == null) {
        error("Invalid substitute expression "+value+ ", invalid property "+quoted(expression.attr)+" for component "+target.getName());
        return null;
      }

      /*
       * Infer the type of the expression from the type of the property, and the multiplicity of the navigation
       */
     
      propertyType = typeParser.parse(property.getType());
      if (propertyType == null) {
        error("Invalid substitute expression "+value+ " , invalid type "+property.getType()+" for property "+property.getName());
        return null;
      }
     
    }
   
   
    /*
     * If there are prefixes or suffixes, or the referenced property is predefined, the expression is a string
     */
    if (Attribute.isFinalAttribute(expression.attr) || expression.prefix != null || expression.suffix != null)
      propertyType = PrimitiveType.STRING;
   
    /*
     * If at least one of the navigates relations is multiple, the result type is a collection, even if the property is
     * not a collection.
     *
     * However, if the property is already a collection the result is the concatenation of all sets
     */
    return hasMultipleNavigation && !(propertyType instanceof CollectionType) ? new CollectionType(propertyType,true) :  propertyType;
  }
 

 
  /**
   * Given a component, return a parent of this component at the specified abstraction level.
   *
   * NOTE Notice that we can only return a more abstract component, by navigating the group hierarchy.
   *
   * If a more concrete level is requested, we simply return the given component. This is enough for
   * validating substitution expression, as the mentioned relations and attributes must be defined in
   * the abstract component.
   */
  private ComponentDeclaration changeAbstractionLevel(ComponentDeclaration component, ComponentKind level) {
   
    ComponentDeclaration result = component;
    while (level.isMoreAbstractThan(result.getKind())) {
     
      /*
       * try to go up the abstraction levels
       */
      ComponentDeclaration parent = getComponent(result.getGroupVersioned(),true);
     
      /*
       * There is no ancestor defined, we have to stop
       */
      if (parent == null) {
        return null;
      }
     
      result = parent;
    }
   
    return result;
  }
 
  /**
   * Validates that the specified function invocation is valid in the context of the component where the value is specified
   */
  private Type validateFunctionInvocation() {
   

    /*
     *  A function is only valid for instrumented concrete components
     */
   
    ComponentDeclaration context   = getComponent();
    String method           = value.substring(1).trim();
   
    if (!(context instanceof AtomicImplementationDeclaration)) {
      error("Invalid substitute value, function substitution is only valid for atomic components "+method);
      return null;
    }
   
    /*
     *  Validate the specified method is valid
     */
    InstrumentedClass instrumentedClass = ((AtomicImplementationDeclaration) context).getImplementationClass();

    try {
      /*
       * Verify the method has a single parameter of type Instance
       */
      String parameterType  = instrumentedClass.getMethodParameterType(method,true);
     
      if (parameterType == null) {
        error("Invalid substitute value, method "+method+" with a single parameter is not defined in class "+instrumentedClass.getName());
        return null;
      }
     
      if (!ComponentKind.INSTANCE.isAssignableTo(parameterType)) {
        error("Invalid substitute value, method "+method+" with a single Instance parameter is not defined in class "+instrumentedClass.getName());
        return null;
      }

      /*
       * Verify the method return type is one of the supported property primitive types
       */
      String  returnType = instrumentedClass.getMethodReturnType(method, null,true);
      if (returnType == null) {
        error("Invalid substitute value, method "+method+"does not return a value");
        return null;
      }
     
      for (Type primitive : PrimitiveType.values()) {
        if (primitive.isAssignableFrom(returnType))
          return primitive;
      }
     
      /*
       * As a last resort, we also consider collections of strings
       *
       *  TODO modify CodeReflection to perform this validation and get the type of the collection if possible
       *  to be able to accept collections of primitive types
       */
      if ( returnType.equals(Collection.class.getCanonicalName()) || returnType.equals(Set.class.getCanonicalName())) {
        return new CollectionType(PrimitiveType.STRING,true);
      }
     
      error("Invalid substitute value, method "+method+"does not return a value of a valid type "+returnType);
      return null;
     
    } catch (NoSuchMethodException exc) {
      error("Invalid substitute value, method "+method+" with a single parameter is not defined in class "+instrumentedClass.getName());
      return null;
    }
   
  }
 
  /**
   * The value that is being validated
   */
  private String value;

  /**
   * Initializes the internal state of the validator
   */
  protected void initializeState(String value) {
    this.value    = value.trim();
 
 
  /**
   * Frees all references to the state of validation
   */
  @Override
  public void resetState() {
   
    this.value     = null;
    super.resetState();
  }
 
}
TOP

Related Classes of fr.imag.adele.apam.maven.plugin.validation.ContextualExpressionValidator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.