All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.takari.builder.internal.model.BuilderValidationVisitor Maven / Gradle / Ivy

package io.takari.builder.internal.model;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import io.takari.builder.IArtifactMetadata;
import io.takari.builder.IArtifactResources;
import io.takari.builder.IDirectoryFiles;
import io.takari.builder.Parameter;
import io.takari.builder.ResolutionScope;

public abstract class BuilderValidationVisitor implements BuilderMetadataVisitor {

  static boolean isEmpty(String[] value) {
    return value == null || value.length == 0;
  }

  protected abstract void error(BuilderMethod builder, String message);

  protected abstract void error(AbstractParameter parameter, String message);

  private void error(AbstractParameter parameter, String message, Object... values) {
    error(parameter, String.format(message, values));
  }

  private ResolutionScope resolutionScope;

  private void validateParameterAnnotation(AbstractParameter parameter) {
    MemberAdapter element = parameter.originatingElement();
    Annotation annotation = parameter.annotation();
    Class annotationClass =
        annotation != null ? annotation.annotationType() : null;

    assert annotationClass == null || element.isAnnotationPresent(annotationClass);

    TreeSet presentAnnotations = BuilderClass.parameterAnnotations().stream() //
        .filter(a -> element.isAnnotationPresent(a)) //
        .map(a -> a.getSimpleName()) //
        .collect(Collectors.toCollection(TreeSet::new));

    if (presentAnnotations.size() > 1) {
      error(parameter, "ambigous parameter annotation present: %s", presentAnnotations);
    }
  }

  //
  //
  //

  @Override
  public boolean enterBuilderClass(BuilderClass metadata) {
    // validate parameter names are unique
    Map parameters = new HashMap<>();
    for (AbstractParameter parameter : metadata.parameters()) {
      if (parameters.containsKey(parameter.name())) {
        AbstractParameter origin = parameters.get(parameter.name());
        error(parameter, "Builder parameter '%s' duplicates parameter defined in %s",
            parameter.name(), origin.originatingElement().getDeclaringType().qualifiedName());
      }
      parameters.put(parameter.name(), parameter);
    }

    return true;
  }

  @Override
  public void visitBuilder(BuilderMethod metadata) {
    // validate goal name
    String name = metadata.annotation().name();
    if (!name.matches("[\\p{Alnum}-]+")) {
      error(metadata, "invalid goal name");
    }

    // validate type is concrete
    TypeAdapter type = metadata.declaringType();
    if (type.isInterface() || type.isLocalClass() || type.isAnonymousClass() || type.isInnerClass()
        || type.isAbstract()) {
      error(metadata, "Only concrete classes are allowed to contain Builder methods");
    }

    // TODO validate type has no-arg constructor

    // validate method does not have parameters
    if (metadata.originatingElement().getParameterCount() > 0) {
      error(metadata, "Buidler method must not take parameters");
    }
  }

  @Override
  public boolean enterMultivalue(MultivalueParameter metadata) {
    // validate target type, see BuildInputsBulder for runtime implementation counterpart
    TypeAdapter type = metadata.type();
    if (!type.isArray()) {
      if (!type.isInterface() && !type.hasNoargConstructor()) {
        error(metadata, "multivalue prarmeter type %s must have no-arg constructor",
            type.qualifiedName());
      }
      if (type.isInterface() //
          && !type.isAssignableFrom(List.class) //
          && !type.isAssignableFrom(Set.class) //
          && !type.isAssignableFrom(Collection.class)) {
        error(metadata,
            "multivalue prarmeter type must be concrete type or one of Collection, List and Set");
      }
    }

    return true;
  }

  @Override
  public void visitUnsupportedCollection(UnsupportedCollectionParameter metadata) {
    List elementTypes = metadata.elementTypes;
    if (elementTypes.isEmpty()) {
      error(metadata, "Raw Collection or wildcard Collection element type");
    } else {
      error(metadata, "Unsupported Collection element type %s", elementTypes.get(0).simpleName());
    }
  }

  @Override
  public void visitMap(MapParameter metadata) {
    TypeAdapter type = metadata.type();
    if (!type.isMap()) {
      throw new IllegalArgumentException(); // this is a bug in our code
    }

    List types = metadata.element.getParameterTypes();
    if (types.size() != 2) {
      error(metadata, "Raw Map or wildcard Map key and/or value types");
      return;
    }

    if (!types.get(0).isSameType(String.class)) {
      error(metadata, "Key for map parameter must be assignable from String");
    }
    if (!SimpleParameter.isSimpleType(types.get(1))) {
      error(metadata, "Only simple parameters are allowed as values for map parameters");
    }
  }

  @Override
  public boolean enterComposite(CompositeParameter metadata) {
    // TODO validate composite member names are unique
    // TODO validate executable type (concrete, has no-arg constructor)

    Parameter ann = metadata.annotation();
    if (ann != null && (ann.value().length > 0 || ann.defaultValue().length > 0)) {
      error(metadata, "@Parameter target type %s does not support value/defaultValue",
          metadata.type().qualifiedName());
    }

    return true;
  }

  @Override
  public void visitSimple(SimpleParameter metadata) {
    validateParameterAnnotation(metadata);

    // TODO parameter target type must be supported
    // TODO if target type is enum, validate value/defaultValue (skip if use ${properties})

    // only one of value and defaultValue can be specified
    if (!isEmpty(metadata.value()) && !isEmpty(metadata.defaultValue())) {
      error(metadata, "@Parameter 'value' and 'defaultValue' attributes cannot be both specified");
    }

    // TODO if either value or defaultValue is specified, required must be true

    // if parameter target type is primitive, required must be true
    if (metadata.type().isPrimitive() && metadata.annotation() != null && !metadata.required()) {
      error(metadata, "Parameter '%s' of primitive type '%s' must be required", metadata.name(),
          metadata.type().qualifiedName());
    }

    // TODO validate value/defaultValue cardinality
  }

  @Override
  public void visitInputDirectory(InputDirectoryParameter metadata) {
    validateParameterAnnotation(metadata);

    // parameter target type
    TypeAdapter type = metadata.type();
    if (!type.isSameType(File.class) && !type.isSameType(Path.class)) {
      error(metadata, "@InputDirectory paramerer must be of type File or Path");
    }

    // only one of value and defaultValue can be specified
    if (!isEmpty(metadata.value()) && !isEmpty(metadata.defaultValue())) {
      error(metadata,
          "@InputDirectory 'value' and 'defaultValue' attributes cannot be both specified");
    }

    // TODO if either value or defaultValue is specified, required must be true
    // TODO validate value/defaultValue cardinality
  }

  @Override
  public void visitInputDirectoryFiles(InputDirectoryFilesParameter metadata) {
    validateParameterAnnotation(metadata);

    // parameter target type
    TypeAdapter type = metadata.type();
    if (type.isArray() || type.isIterable()) {
      type = metadata.originatingElement().getParameterTypes().get(0);
    }
    if (!type.isSameType(IDirectoryFiles.class) && !type.isSameType(File.class)
        && !type.isSameType(Path.class)) {
      error(metadata,
          "@InputDirectoryFiles paramerer must be of type DirectoryFiles, File, or Path");
    }

    // only one of value and defaultValue can be specified
    if (!isEmpty(metadata.value()) && !isEmpty(metadata.defaultValue())) {
      error(metadata,
          "@InputDirectoryFiles 'value' and 'defaultValue' attributes cannot be both specified");
    }
  }

  @Override
  public void visitDependencies(DependenciesParameter metadata) {
    TypeAdapter type = metadata.type();
    if (type.isMap()) {
      List types = metadata.element.getParameterTypes();
      if (types.size() != 2) {
        error(metadata, "Raw Map or wildcard Map key and/or value types");
        return;
      }

      if (!types.get(0).isSameType(IArtifactMetadata.class)) {
        error(metadata, "Key for @Dependency map parameter must be of type IArtifactMetadata");
      }
      if (!types.get(1).isSameType(File.class) && !types.get(1).isSameType(Path.class)) {
        error(metadata, "Only Files or Paths are allowed as values for @Dependency map parameters");
      }
    } else {
      if (!metadata.type().isSameType(File.class)
          && !metadata.type().isSameType(IArtifactMetadata.class)
          && !metadata.type().isSameType(Path.class)) {
        error(metadata, "@Dependencies must be of type %s, %s or %s", File.class, Path.class,
            IArtifactMetadata.class);
      }
    }
    validateResolutionScope(metadata.annotation().scope(), metadata);
  }

  private void validateResolutionScope(ResolutionScope scope, AbstractParameter metadata) {
    if (resolutionScope != null && !resolutionScope.equals(scope)) {
      error(metadata, "ambiguous resolution scope configuration");
    } else {
      resolutionScope = scope;
    }
  }

  private void validateArtifactResources(AbstractResourceSelectionParameter metadata,
      ResolutionScope scope, String parameterName) {
    validateParameterAnnotation(metadata);
    validateResolutionScope(scope, metadata);

    // parameter target type
    TypeAdapter type = metadata.type();
    if (type.isArray() || type.isIterable()) {
      type = metadata.originatingElement().getParameterTypes().get(0);
    }
    if (!type.isSameType(URL.class) && !type.isSameType(IArtifactResources.class)) {
      error(metadata, parameterName + " parameter must be of type URL or IArtifactResources");
    }
  }

  @Override
  public void visitOutputDirectory(OutputDirectoryParameter metadata) {
    validateParameterAnnotation(metadata);
  }

  @Override
  public void visitOutputFile(OutputFileParameter metadata) {
    validateParameterAnnotation(metadata);
  }

  @Override
  public void visitGeneratedResourcesDirectory(GeneratedResourcesDirectoryParameter metadata) {
    validateParameterAnnotation(metadata);
  }

  @Override
  public void visitGeneratedSourcesDirectory(GeneratedSourcesDirectoryParameter metadata) {
    validateParameterAnnotation(metadata);
  }

  @Override
  public void visitInputFile(InputFileParameter metadata) {
    validateParameterAnnotation(metadata);
  }

  @Override
  public void visitDependencyResources(DependencyResourcesParameter metadata) {
    validateArtifactResources(metadata, metadata.annotation().scope(), "@DependencyResources");
  }

  @Override
  public void visitArtifactResources(ArtifactResourcesParameter metadata) {
    validateArtifactResources(metadata, metadata.annotation().scope(), "@ArtifactResources");
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy