/**
* 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.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import com.asakusafw.operator.CompileEnvironment;
import com.asakusafw.operator.Constants;
import com.asakusafw.operator.description.ClassDescription;
import com.asakusafw.operator.model.JavaName;
import com.asakusafw.operator.model.OperatorClass;
import com.asakusafw.operator.model.OperatorDescription.Node;
import com.asakusafw.operator.model.OperatorElement;
import com.asakusafw.operator.util.DescriptionHelper;
import com.asakusafw.operator.util.ElementHelper;
import com.asakusafw.operator.util.JavadocHelper;
import com.asakusafw.operator.util.Logger;
import com.asakusafw.utils.java.jsr269.bridge.Jsr269;
import com.asakusafw.utils.java.model.syntax.ClassDeclaration;
import com.asakusafw.utils.java.model.syntax.ClassLiteral;
import com.asakusafw.utils.java.model.syntax.CompilationUnit;
import com.asakusafw.utils.java.model.syntax.ConstructorDeclaration;
import com.asakusafw.utils.java.model.syntax.Expression;
import com.asakusafw.utils.java.model.syntax.FieldDeclaration;
import com.asakusafw.utils.java.model.syntax.Javadoc;
import com.asakusafw.utils.java.model.syntax.MethodDeclaration;
import com.asakusafw.utils.java.model.syntax.ModelFactory;
import com.asakusafw.utils.java.model.syntax.SimpleName;
import com.asakusafw.utils.java.model.syntax.Statement;
import com.asakusafw.utils.java.model.syntax.Type;
import com.asakusafw.utils.java.model.syntax.TypeBodyDeclaration;
import com.asakusafw.utils.java.model.util.AttributeBuilder;
import com.asakusafw.utils.java.model.util.ImportBuilder;
import com.asakusafw.utils.java.model.util.ImportBuilder.Strategy;
import com.asakusafw.utils.java.model.util.JavadocBuilder;
import com.asakusafw.utils.java.model.util.Models;
import com.asakusafw.utils.java.model.util.TypeBuilder;
/**
* Emits operator factory class for flow-part class.
*/
public class FlowPartFactoryEmitter {
static final Logger LOG = Logger.get(FlowPartFactoryEmitter.class);
private final CompileEnvironment environment;
/**
* Creates a new instance.
* @param environment current compiling environment
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public FlowPartFactoryEmitter(CompileEnvironment environment) {
this.environment = Objects.requireNonNull(environment, "environment must not be null"); //$NON-NLS-1$
}
/**
* Emits an operator implementation class.
* @param operatorClass target class description
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public void emit(OperatorClass operatorClass) {
Objects.requireNonNull(operatorClass, "operatorClass must not be null"); //$NON-NLS-1$
if (operatorClass.getElements().size() >= 2) {
throw new IllegalArgumentException();
}
ClassDescription key = Constants.getFactoryClass(operatorClass.getDeclaration().getQualifiedName());
if (environment.isResourceGenerated(key)) {
LOG.debug("class is already generated: {}", key.getClassName()); //$NON-NLS-1$
return;
}
CompilationUnit unit = Generator.generate(environment, operatorClass);
try {
environment.emit(unit, operatorClass.getDeclaration());
environment.setResourceGenerated(key);
} catch (IOException e) {
environment.getProcessingEnvironment().getMessager().printMessage(Diagnostic.Kind.ERROR,
MessageFormat.format(
Messages.getString("FlowPartFactoryEmitter.errorFailEmit"), //$NON-NLS-1$
e.toString()),
operatorClass.getDeclaration());
LOG.error(MessageFormat.format(
Messages.getString("FlowPartFactoryEmitter.logFailEmit"), //$NON-NLS-1$
operatorClass.getDeclaration().getQualifiedName()), e);
}
}
private static final class Generator {
private final CompileEnvironment environment;
private final ModelFactory f;
private final OperatorClass operatorClass;
private final Jsr269 converter;
private final ImportBuilder imports;
private final TypeElement typeElement;
private Generator(CompileEnvironment environment, OperatorClass operatorClass) {
assert environment != null;
assert operatorClass != null;
this.environment = environment;
this.f = Models.getModelFactory();
this.converter = new Jsr269(f);
this.operatorClass = operatorClass;
this.imports = new ImportBuilder(
f,
converter.convert((PackageElement) operatorClass.getDeclaration().getEnclosingElement()),
Strategy.TOP_LEVEL);
this.typeElement = operatorClass.getDeclaration();
}
static CompilationUnit generate(CompileEnvironment environment, OperatorClass operatorClass) {
Generator generator = new Generator(environment, operatorClass);
return generator.generate();
}
CompilationUnit generate() {
reserveNameSpace();
ClassDeclaration typeDecl = generateClass();
return f.newCompilationUnit(
imports.getPackageDeclaration(),
imports.toImportDeclarations(),
Collections.singletonList(typeDecl));
}
private ClassDeclaration generateClass() {
Types types = environment.getProcessingEnvironment().getTypeUtils();
DeclaredType originalClass = types.getDeclaredType(operatorClass.getDeclaration());
SimpleName className = generateClassName();
List<TypeBodyDeclaration> members = new ArrayList<>();
members.add(generateConstructor());
members.addAll(generateMembers());
return f.newClassDeclaration(
new JavadocBuilder(f)
.inline(Messages.getString("FlowPartFactoryEmitter.javadocClassSynopsis"), //$NON-NLS-1$
d -> d.linkType(imports.resolve(converter.convert(originalClass))))
.toJavadoc(),
new AttributeBuilder(f)
.annotation(DescriptionHelper.resolveAnnotation(imports, Constants.getGenetedAnnotation()))
.annotation(ElementHelper.toOperatorFactoryAnnotation(environment, operatorClass, imports))
.Public()
.Final()
.toAttributes(),
className,
null,
Collections.emptyList(),
members);
}
private void reserveNameSpace() {
SimpleName className = generateClassName();
imports.resolvePackageMember(className);
for (OperatorElement element : operatorClass.getElements()) {
if (element.getDescription() == null) {
continue;
}
imports.resolvePackageMember(f.newQualifiedName(className, generateNodeClassName(element)));
}
}
private List<TypeBodyDeclaration> generateMembers() {
List<TypeBodyDeclaration> results = new ArrayList<>();
for (OperatorElement element : operatorClass.getElements()) {
if (element.getDescription() == null) {
continue;
}
ClassDeclaration node = generateNodeClass(element);
Type type = imports.resolvePackageMember(f.newQualifiedName(generateClassName(), node.getName()));
MethodDeclaration factory = generateFactoryMethod(element, type);
results.add(node);
results.add(factory);
}
return results;
}
private ClassDeclaration generateNodeClass(OperatorElement element) {
assert element != null;
List<TypeBodyDeclaration> members = new ArrayList<>();
members.addAll(generateOutputFields(element));
members.add(generateNodeConstructor(element));
return f.newClassDeclaration(
generateNodeClassComment(element),
new AttributeBuilder(f)
.Public()
.Static()
.Final()
.toAttributes(),
generateNodeClassName(element),
ElementHelper.toTypeParameters(environment, typeElement.getTypeParameters(), imports),
null,
Collections.emptyList(),
members);
}
private List<FieldDeclaration> generateOutputFields(OperatorElement element) {
List<FieldDeclaration> results = new ArrayList<>();
for (Node node : element.getDescription().getOutputs()) {
Type type = new TypeBuilder(f, DescriptionHelper.resolve(imports, Constants.TYPE_SOURCE))
.parameterize(imports.resolve(converter.convert(node.getType())))
.toType();
results.add(f.newFieldDeclaration(
generateOutputFieldComment(element, node),
new AttributeBuilder(f)
.Public()
.Final()
.toAttributes(),
type,
f.newSimpleName(node.getName()),
null));
}
return results;
}
private ConstructorDeclaration generateNodeConstructor(OperatorElement element) {
Type builderType = DescriptionHelper.resolve(imports, Constants.TYPE_ELEMENT_BUILDER);
List<Statement> statements = new ArrayList<>();
SimpleName builderVar = f.newSimpleName("$builder$"); //$NON-NLS-1$
statements.add(new TypeBuilder(f, builderType)
.method("createFlow", toOperatorDeclaration(element)) //$NON-NLS-1$
.toLocalVariableDeclaration(builderType, builderVar));
statements.addAll(ElementHelper.toNodeConstructorStatements(environment, element, builderVar, imports));
return f.newConstructorDeclaration(
null,
new AttributeBuilder(f).toAttributes(),
generateNodeClassName(element),
ElementHelper.toParameters(environment, element, imports),
statements);
}
private List<Expression> toOperatorDeclaration(OperatorElement element) {
assert element != null;
List<Expression> results = new ArrayList<>();
results.add(toLiteral(operatorClass.getDeclaration().asType()));
for (VariableElement param : element.getDeclaration().getParameters()) {
TypeMirror type = param.asType();
results.add(toLiteral(type));
}
return results;
}
private ClassLiteral toLiteral(TypeMirror type) {
return f.newClassLiteral(imports.resolve(converter.convert(environment.getErasure(type))));
}
private SimpleName generateNodeClassName(OperatorElement element) {
assert element != null;
String name = JavaName.of(operatorClass.getDeclaration().getSimpleName().toString()).toTypeName();
return f.newSimpleName(name);
}
private MethodDeclaration generateFactoryMethod(OperatorElement element, Type rawNodeType) {
Type nodeType = ElementHelper.toParameterizedType(
environment, typeElement.getTypeParameters(), rawNodeType, imports);
TypeElement decl = operatorClass.getDeclaration();
return f.newMethodDeclaration(
generateFactoryMethodComment(element),
new AttributeBuilder(f)
.annotation(ElementHelper.toOperatorInfoAnnotation(environment, element, imports))
.Public()
.toAttributes(),
ElementHelper.toTypeParameters(environment, decl.getTypeParameters(), imports),
nodeType,
f.newSimpleName(Constants.NAME_FLOW_PART_FACTORY_METHOD),
ElementHelper.toParameters(environment, element, imports),
0,
Collections.emptyList(),
f.newBlock(new TypeBuilder(f, nodeType)
.newObject(ElementHelper.toArguments(environment, element, imports))
.toReturnStatement()));
}
private TypeBodyDeclaration generateConstructor() {
return f.newConstructorDeclaration(
new JavadocBuilder(f)
.text(Messages.getString("FlowPartFactoryEmitter.javadocConstructorSynopsis")) //$NON-NLS-1$
.toJavadoc(),
new AttributeBuilder(f)
.Public()
.toAttributes(),
generateClassName(),
Collections.emptyList(),
Collections.singletonList(f.newReturnStatement()));
}
private SimpleName generateClassName() {
ClassDescription aClass = Constants.getFactoryClass(operatorClass.getDeclaration().getQualifiedName());
return f.newSimpleName(aClass.getSimpleName());
}
private Javadoc generateFactoryMethodComment(OperatorElement element) {
assert element != null;
JavadocHelper source = new JavadocHelper(environment);
source.put(operatorClass.getDeclaration());
source.put(element.getDeclaration());
JavadocBuilder javadoc = new JavadocBuilder(f);
javadoc.inline(source.get(element.getDescription().getDocument()));
appendTypeParameterDocs(element, javadoc, source);
for (Node node : element.getDescription().getParameters()) {
javadoc.param(node.getName());
javadoc.inline(source.get(node.getDocument()));
}
javadoc.returns().text(Messages.getString("FlowPartFactoryEmitter.javadocFactoryMethodReturn")); //$NON-NLS-1$
return javadoc.toJavadoc();
}
private Javadoc generateOutputFieldComment(OperatorElement element, Node output) {
assert element != null;
assert output != null;
JavadocHelper source = new JavadocHelper(environment);
source.put(operatorClass.getDeclaration());
source.put(element.getDeclaration());
JavadocBuilder javadoc = new JavadocBuilder(f);
javadoc.inline(source.get(output.getDocument()));
return javadoc.toJavadoc();
}
private Javadoc generateNodeClassComment(OperatorElement element) {
assert element != null;
JavadocHelper source = new JavadocHelper(environment);
source.put(operatorClass.getDeclaration());
source.put(element.getDeclaration());
JavadocBuilder javadoc = new JavadocBuilder(f);
javadoc.inline(source.get(element.getDescription().getDocument()));
appendTypeParameterDocs(element, javadoc, source);
return javadoc.toJavadoc();
}
private void appendTypeParameterDocs(OperatorElement element, JavadocBuilder javadoc, JavadocHelper source) {
assert element != null;
assert javadoc != null;
assert source != null;
for (TypeParameterElement param : element.getDeclaration().getTypeParameters()) {
javadoc.typeParam(param.getSimpleName().toString());
javadoc.inline(source.getTypeParameter(param.getSimpleName().toString()));
}
}
}
}