/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.asakusafw.operator.flowpart;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import com.asakusafw.operator.CompileEnvironment;
import com.asakusafw.operator.Constants;
import com.asakusafw.operator.model.DataModelMirror;
import com.asakusafw.operator.model.ExternMirror;
import com.asakusafw.operator.model.OperatorClass;
import com.asakusafw.operator.model.OperatorDescription;
import com.asakusafw.operator.model.OperatorDescription.Document;
import com.asakusafw.operator.model.OperatorDescription.Node;
import com.asakusafw.operator.model.OperatorDescription.Node.Kind;
import com.asakusafw.operator.model.OperatorDescription.ParameterReference;
import com.asakusafw.operator.model.OperatorDescription.Reference;
import com.asakusafw.operator.model.OperatorElement;
import com.asakusafw.operator.util.AnnotationHelper;
import com.asakusafw.operator.util.ElementHelper;
import com.asakusafw.operator.util.TypeHelper;
/**
* Analyzes flow-part classes.
*/
public class FlowPartAnalyzer {
private final CompileEnvironment environment;
private final TypeElement annotationDecl;
private final Map<TypeElement, AnnotationMirror> flowpartClasses;
/**
* Creates a new instance.
* @param environment current compiling environment
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public FlowPartAnalyzer(CompileEnvironment environment) {
this.environment = Objects.requireNonNull(environment, "environment must not be null"); //$NON-NLS-1$
this.annotationDecl = environment.findTypeElement(Constants.TYPE_FLOW_PART);
this.flowpartClasses = new HashMap<>();
}
/**
* Registers a flow-part class declaration to this analyzer.
* @param typeDecl flow-part type declaration
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public void register(TypeElement typeDecl) {
Objects.requireNonNull(typeDecl, "typeDecl must not be null"); //$NON-NLS-1$
AnnotationMirror annotation = AnnotationHelper.findAnnotation(environment, annotationDecl, typeDecl);
if (annotation == null) {
environment.getProcessingEnvironment().getMessager().printMessage(Diagnostic.Kind.ERROR,
MessageFormat.format(
Messages.getString("FlowPartAnalyzer.errorFailExtractAnnotation"), //$NON-NLS-1$
typeDecl.getSimpleName(),
annotationDecl.getSimpleName()),
typeDecl);
return;
}
this.flowpartClasses.put(typeDecl, annotation);
}
/**
* Resolves previously {@link #register(TypeElement) registered} flow-part classes.
* @return resolved operator classes, or {@code null} if no valid flow-part classes are registered
*/
public Collection<OperatorClass> resolve() {
List<OperatorClass> results = new ArrayList<>();
for (Map.Entry<TypeElement, AnnotationMirror> entry : flowpartClasses.entrySet()) {
OperatorClass resolved = resolve(entry.getValue(), entry.getKey());
if (resolved != null) {
results.add(resolved);
}
}
return results;
}
private OperatorClass resolve(AnnotationMirror annotation, TypeElement classDecl) {
assert annotation != null;
assert classDecl != null;
if (validateClass(classDecl) == false) {
return null;
}
List<ExecutableElement> validConstructors = selectValidConstructors(classDecl);
List<OperatorElement> elements = new ArrayList<>();
for (ExecutableElement constructor : validConstructors) {
OperatorDescription description = analyze(annotation, constructor);
if (description != null && ElementHelper.validate(environment, constructor, description) == false) {
description = null;
}
if (elements.isEmpty()) {
elements.add(new OperatorElement(annotation, constructor, description));
}
}
return new OperatorClass(classDecl, elements);
}
private boolean validateClass(TypeElement type) {
assert type != null;
boolean valid = true;
DeclaredType superType = environment.findDeclaredType(Constants.TYPE_FLOW_DESCRIPTION);
if (environment.getProcessingEnvironment().getTypeUtils().isSubtype(type.asType(), superType) == false) {
error(type, Messages.getString("FlowPartAnalyzer.errorClassNotSubtype"), //$NON-NLS-1$
type.getSimpleName(),
Constants.TYPE_FLOW_DESCRIPTION);
valid = false;
}
if (type.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
error(type, Messages.getString("FlowPartAnalyzer.errorClassNotTopLevel"), //$NON-NLS-1$
type.getSimpleName());
valid = false;
}
if (type.getModifiers().contains(Modifier.PUBLIC) == false) {
error(type, Messages.getString("FlowPartAnalyzer.errorClassNotPublic"), //$NON-NLS-1$
type.getSimpleName());
valid = false;
}
if (type.getModifiers().contains(Modifier.ABSTRACT)) {
error(type, Messages.getString("FlowPartAnalyzer.errorClassAbstract"), //$NON-NLS-1$
type.getSimpleName());
valid = false;
}
return valid;
}
private List<ExecutableElement> selectValidConstructors(TypeElement type) {
assert type != null;
List<ExecutableElement> results = new LinkedList<>();
for (ExecutableElement element : ElementFilter.constructorsIn(type.getEnclosedElements())) {
if (element.getModifiers().contains(Modifier.PUBLIC)) {
results.add(element);
}
}
if (results.isEmpty()) {
error(type, Messages.getString("FlowPartAnalyzer.errorConstructorMissing"), type.getSimpleName()); //$NON-NLS-1$
}
if (results.size() >= 2) {
for (ExecutableElement element : results) {
error(element, Messages.getString("FlowPartAnalyzer.errorConstructorAmbiguous"), type.getSimpleName()); //$NON-NLS-1$
}
}
for (Iterator<ExecutableElement> iter = results.iterator(); iter.hasNext();) {
ExecutableElement ctor = iter.next();
if (validateConstructor(ctor) == false) {
iter.remove();
}
}
return results;
}
private boolean validateConstructor(ExecutableElement ctor) {
boolean valid = true;
if (ctor.getThrownTypes().isEmpty() == false) {
error(ctor, Messages.getString("FlowPartAnalyzer.errorConstructorException")); //$NON-NLS-1$
valid = false;
}
if (ctor.getTypeParameters().isEmpty() == false) {
error(ctor, Messages.getString("FlowPartAnalyzer.errorConstructorGeneric")); //$NON-NLS-1$
valid = false;
}
boolean sawIn = false;
boolean sawOut = false;
boolean sawFlowIn = false;
boolean sawFlowOut = false;
TypeElement importType = environment.findTypeElement(Constants.TYPE_IMPORT);
TypeElement exportType = environment.findTypeElement(Constants.TYPE_EXPORT);
for (VariableElement param : ctor.getParameters()) {
TypeMirror type = param.asType();
AnnotationMirror importer = AnnotationHelper.findAnnotation(environment, importType, param);
AnnotationMirror exporter = AnnotationHelper.findAnnotation(environment, exportType, param);
if (environment.isFlowpartExternalIo() == false) {
if (importer != null) {
error(param, Messages.getString("FlowPartAnalyzer.errorConstructorImportAnnoattion")); //$NON-NLS-1$
valid = false;
continue;
}
if (exporter != null) {
error(param, Messages.getString("FlowPartAnalyzer.errorConstructorExportAnnotation")); //$NON-NLS-1$
valid = false;
continue;
}
}
boolean in = TypeHelper.isIn(environment, type);
boolean out = TypeHelper.isOut(environment, type);
if (in) {
sawIn = true;
TypeMirror component = TypeHelper.getInType(environment, type);
if (component == null) {
error(param, Messages.getString("FlowPartAnalyzer.errorInputRawInType")); //$NON-NLS-1$
valid = false;
continue;
}
DataModelMirror model = environment.findDataModel(component);
if (model == null) {
error(param, Messages.getString("FlowPartAnalyzer.errorInputNotDataModelType"), component); //$NON-NLS-1$
valid = false;
continue;
}
sawFlowIn |= (importer == null);
valid &= validateImport(param, component, importer);
} else {
if (importer != null) {
error(param, Messages.getString("FlowPartAnalyzer.errorImportNotIn"), type); //$NON-NLS-1$
valid = false;
continue;
}
}
if (out) {
sawOut = true;
TypeMirror component = TypeHelper.getOutType(environment, type);
if (component == null) {
error(param, Messages.getString("FlowPartAnalyzer.errorOutputRawOutType")); //$NON-NLS-1$
valid = false;
continue;
}
DataModelMirror model = environment.findDataModel(component);
if (model == null) {
error(param, Messages.getString("FlowPartAnalyzer.errorOutputNotDataModelType"), component); //$NON-NLS-1$
valid = false;
continue;
}
sawFlowOut |= (exporter == null);
valid &= validateExport(param, component, exporter);
} else {
if (exporter != null) {
error(param, Messages.getString("FlowPartAnalyzer.errorExportNotOut"), type); //$NON-NLS-1$
valid = false;
continue;
}
}
}
if (sawIn == false) {
error(ctor, Messages.getString("FlowPartAnalyzer.errorInMissing")); //$NON-NLS-1$
valid = false;
} else if (sawFlowIn == false) {
error(ctor, Messages.getString("FlowPartAnalyzer.errorInWithoutExternMissing")); //$NON-NLS-1$
valid = false;
}
if (sawOut == false) {
error(ctor, Messages.getString("FlowPartAnalyzer.errorOutMissing")); //$NON-NLS-1$
valid = false;
} else if (sawFlowOut == false) {
error(ctor, Messages.getString("FlowPartAnalyzer.errorOutWithoutExternMissing")); //$NON-NLS-1$
valid = false;
}
return valid;
}
private boolean validateImport(VariableElement param, TypeMirror component, AnnotationMirror extern) {
assert param != null;
assert component != null;
boolean valid = true;
if (extern != null) {
if (component.getKind() == TypeKind.TYPEVAR) {
error(param, Messages.getString("FlowPartAnalyzer.errorImportTypeVariable"), component); //$NON-NLS-1$
valid = false;
}
AnnotationValue value = AnnotationHelper.getValue(environment, extern, "description"); //$NON-NLS-1$
if (value.getValue() instanceof TypeMirror) {
TypeMirror desc = (TypeMirror) value.getValue();
Types types = environment.getProcessingEnvironment().getTypeUtils();
if (types.isSubtype(desc, environment.findDeclaredType(Constants.TYPE_IMPORTER_DESC)) == false) {
error(param, Messages.getString("FlowPartAnalyzer.errorImportInvalidDescriptionType"), desc); //$NON-NLS-1$
valid = false;
}
}
}
return valid;
}
private boolean validateExport(VariableElement param, TypeMirror component, AnnotationMirror extern) {
assert param != null;
assert component != null;
boolean valid = true;
if (extern != null) {
if (component.getKind() == TypeKind.TYPEVAR) {
error(param, Messages.getString("FlowPartAnalyzer.errorExportTypeVariable"), component); //$NON-NLS-1$
valid = false;
}
AnnotationValue value = AnnotationHelper.getValue(environment, extern, "description"); //$NON-NLS-1$
if (value.getValue() instanceof TypeMirror) {
TypeMirror desc = (TypeMirror) value.getValue();
Types types = environment.getProcessingEnvironment().getTypeUtils();
if (types.isSubtype(desc, environment.findDeclaredType(Constants.TYPE_EXPORTER_DESC)) == false) {
error(param, Messages.getString("FlowPartAnalyzer.errorExportInvalidDescriptionType"), desc); //$NON-NLS-1$
valid = false;
}
}
}
return valid;
}
private OperatorDescription analyze(AnnotationMirror annotation, ExecutableElement constructor) {
assert annotation != null;
assert constructor != null;
List<Node> parameters = new ArrayList<>();
List<Node> outputs = new ArrayList<>();
TypeElement importType = environment.findTypeElement(Constants.TYPE_IMPORT);
TypeElement exportType = environment.findTypeElement(Constants.TYPE_EXPORT);
int index = -1;
for (VariableElement param : constructor.getParameters()) {
index++;
ParameterReference reference = Reference.parameter(index);
String name = param.getSimpleName().toString();
TypeMirror type = param.asType();
if (TypeHelper.isIn(environment, type)) {
AnnotationMirror importer = AnnotationHelper.findAnnotation(environment, importType, param);
ExternMirror extern = importer == null ? null : ExternMirror.parse(environment, importer, param);
TypeMirror component = TypeHelper.getInType(environment, type);
parameters.add(new Node(Kind.INPUT, name, Document.reference(reference), component, reference)
.withExtern(extern));
} else if (TypeHelper.isOut(environment, type)) {
AnnotationMirror exporter = AnnotationHelper.findAnnotation(environment, exportType, param);
ExternMirror extern = exporter == null ? null : ExternMirror.parse(environment, exporter, param);
TypeMirror component = TypeHelper.getOutType(environment, type);
outputs.add(new Node(Kind.OUTPUT, name, Document.reference(reference), component, reference)
.withExtern(extern));
} else {
parameters.add(new Node(Kind.DATA, name, Document.reference(reference), type, reference));
}
}
OperatorDescription description = new OperatorDescription(
Document.reference(Reference.method()), parameters, outputs);
return description;
}
private void error(Element element, String pattern, Object... arguments) {
assert element != null;
assert pattern != null;
assert arguments != null;
String message = arguments.length == 0 ? pattern : MessageFormat.format(pattern, arguments);
environment.getProcessingEnvironment().getMessager().printMessage(Diagnostic.Kind.ERROR, message, element);
}
}