/**
* 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.dmdl.java.emitter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.asakusafw.dmdl.java.Configuration;
import com.asakusafw.dmdl.java.spi.JavaDataModelDriver;
import com.asakusafw.dmdl.java.util.JavaName;
import com.asakusafw.dmdl.semantics.DmdlSemantics;
import com.asakusafw.dmdl.semantics.ModelDeclaration;
import com.asakusafw.dmdl.semantics.PropertyDeclaration;
import com.asakusafw.dmdl.semantics.PropertyReferenceDeclaration;
import com.asakusafw.dmdl.semantics.PropertySymbol;
import com.asakusafw.runtime.model.DataModel;
import com.asakusafw.runtime.value.ValueOptionList;
import com.asakusafw.runtime.value.ValueOptionMap;
import com.asakusafw.utils.java.model.syntax.Attribute;
import com.asakusafw.utils.java.model.syntax.Expression;
import com.asakusafw.utils.java.model.syntax.FieldDeclaration;
import com.asakusafw.utils.java.model.syntax.FormalParameterDeclaration;
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.syntax.UnaryOperator;
import com.asakusafw.utils.java.model.util.AttributeBuilder;
import com.asakusafw.utils.java.model.util.ExpressionBuilder;
import com.asakusafw.utils.java.model.util.JavadocBuilder;
import com.asakusafw.utils.java.model.util.Models;
import com.asakusafw.utils.java.model.util.TypeBuilder;
/**
* Abstract super class which emits a record/joined/summarized model as a Java model class.
* @since 0.2.0
* @version 0.9.2
*/
public class ConcreteModelEmitter {
private final ModelDeclaration model;
private final EmitContext context;
private final JavaDataModelDriver driver;
private final ModelFactory f;
/**
* Creates and returns a new instance.
* @param semantics the semantic model root
* @param config the current configuration
* @param model the model to emit
* @param driver the emitter driver
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public ConcreteModelEmitter(
DmdlSemantics semantics,
Configuration config,
ModelDeclaration model,
JavaDataModelDriver driver) {
if (semantics == null) {
throw new IllegalArgumentException("semantics must not be null"); //$NON-NLS-1$
}
if (config == null) {
throw new IllegalArgumentException("config must not be null"); //$NON-NLS-1$
}
if (driver == null) {
throw new IllegalArgumentException("driver must not be null"); //$NON-NLS-1$
}
if (model == null) {
throw new IllegalArgumentException("model must not be null"); //$NON-NLS-1$
}
this.model = model;
this.context = new EmitContext(
semantics,
config,
model,
NameConstants.CATEGORY_DATA_MODEL,
NameConstants.PATTERN_DATA_MODEL);
this.driver = driver;
this.f = config.getFactory();
}
/**
* Emits the projective model.
* @throws IOException if failed to emit a source program
*/
public void emit() throws IOException {
driver.generateResources(context, model);
context.emit(f.newClassDeclaration(
new JavadocBuilder(f)
.text(Messages.getString("ConcreteModelEmitter.javadocClass"), //$NON-NLS-1$
context.getDescription(model))
.toJavadoc(),
createModifiers(),
context.getTypeName(),
null,
createSuperInterfaces(),
createMembers()));
}
private List<Attribute> createModifiers() throws IOException {
List<Attribute> results = new ArrayList<>();
results.addAll(driver.getTypeAnnotations(context, model));
results.addAll(new AttributeBuilder(f)
.Public()
.toAttributes());
return results;
}
private List<Type> createSuperInterfaces() throws IOException {
List<Type> results = new ArrayList<>();
results.add(f.newParameterizedType(
context.resolve(DataModel.class),
context.resolve(context.getQualifiedTypeName())));
results.addAll(driver.getInterfaces(context, model));
return results;
}
private List<TypeBodyDeclaration> createMembers() throws IOException {
List<TypeBodyDeclaration> results = new ArrayList<>();
results.addAll(createPropertyFields());
results.addAll(createReferenceFields());
results.addAll(driver.getFields(context, model));
results.addAll(createDataModelMethods());
results.addAll(createPropertyAccessors());
results.addAll(createReferenceAccessors());
results.addAll(driver.getMethods(context, model));
return results;
}
private List<FieldDeclaration> createPropertyFields() {
List<FieldDeclaration> results = new ArrayList<>();
for (PropertyDeclaration property : model.getDeclaredProperties()) {
Type type = context.getFieldType(property);
SimpleName name = context.getFieldName(property);
results.add(f.newFieldDeclaration(
null,
new AttributeBuilder(f)
.Private()
.Final()
.toAttributes(),
type,
name,
context.getFieldInitializer(property)));
}
return results;
}
private List<FieldDeclaration> createReferenceFields() {
List<FieldDeclaration> results = new ArrayList<>();
for (PropertyReferenceDeclaration reference : model.getDeclaredPropertyReferences()) {
switch (reference.getReference().getKind()) {
case LIST:
results.add(createReferenceListField(reference));
break;
case MAP:
results.add(createReferenceMapKeyField(reference));
results.add(createReferenceMapField(reference));
break;
default:
throw new AssertionError(reference.getReference().getKind());
}
}
return results;
}
private FieldDeclaration createReferenceMapKeyField(PropertyReferenceDeclaration reference) {
Map<String, PropertySymbol> map = reference.getReference().asMap();
List<Expression> keys = map.keySet().stream()
.sequential()
.map(it -> Models.toLiteral(f, it))
.collect(Collectors.toList());
return f.newFieldDeclaration(
null,
new AttributeBuilder(f)
.Static()
.Final()
.toAttributes(),
f.newParameterizedType(context.resolve(Set.class), context.resolve(String.class)),
f.newSimpleName(getKeysFieldName(reference)),
new TypeBuilder(f, context.resolve(ValueOptionMap.class))
.method("keys", keys) //$NON-NLS-1$
.toExpression());
}
private static String getKeysFieldName(PropertyReferenceDeclaration reference) {
// -> KEYSET_XXX
JavaName name = JavaName.of(reference.getName());
name.addFirst("keys"); //$NON-NLS-1$
name.addFirst("list"); //$NON-NLS-1$
return name.toConstantName();
}
private FieldDeclaration createReferenceListField(PropertyReferenceDeclaration reference) {
return f.newFieldDeclaration(
null,
new AttributeBuilder(f)
.Private()
.Final()
.toAttributes(),
context.getContainerType(reference),
context.getFieldName(reference),
new TypeBuilder(f, context.resolve(ValueOptionList.class))
.parameterize(context.getElementType(reference))
.newObject(Collections.emptyList(), f.newClassBody(Arrays.asList(
createReferenceListGetMethod(reference),
createReferenceListSizeMethod(reference))))
.toExpression());
}
private FieldDeclaration createReferenceMapField(PropertyReferenceDeclaration reference) {
return f.newFieldDeclaration(
null,
new AttributeBuilder(f)
.Private()
.Final()
.toAttributes(),
context.getContainerType(reference),
context.getFieldName(reference),
new TypeBuilder(f, context.resolve(ValueOptionMap.class))
.parameterize(context.resolve(String.class), context.getElementType(reference))
.newObject(Collections.emptyList(), f.newClassBody(Arrays.asList(
createReferenceMapGetMethod(reference),
createReferenceMapKeySetMethod(reference))))
.toExpression());
}
private MethodDeclaration createReferenceListGetMethod(PropertyReferenceDeclaration reference) {
SimpleName index = f.newSimpleName("_i"); //$NON-NLS-1$
List<Statement> cases = new ArrayList<>();
int caseIndex = 0;
for (PropertySymbol ref : reference.getReference().getAllReferences()) {
PropertyDeclaration decl = ref.findDeclaration();
assert decl != null;
cases.add(f.newSwitchCaseLabel(Models.toLiteral(f, caseIndex++)));
cases.add(new TypeBuilder(f, f.newNamedType(context.getTypeName()))
.dotThis()
.method(context.getOptionGetterName(decl))
.toReturnStatement());
}
cases.add(f.newSwitchDefaultLabel());
cases.add(new TypeBuilder(f, context.resolve(IndexOutOfBoundsException.class))
.newObject()
.toThrowStatement());
return f.newMethodDeclaration(
null,
new AttributeBuilder(f)
.annotation(context.resolve(Override.class))
.Public()
.toAttributes(),
context.getElementType(reference),
f.newSimpleName("get"), //$NON-NLS-1$
Arrays.asList(f.newFormalParameterDeclaration(context.resolve(int.class), index)),
Arrays.asList(f.newSwitchStatement(index, cases)));
}
private MethodDeclaration createReferenceListSizeMethod(PropertyReferenceDeclaration reference) {
int size = reference.getReference().getAllReferences().size();
return f.newMethodDeclaration(
null,
new AttributeBuilder(f)
.annotation(context.resolve(Override.class))
.Public()
.toAttributes(),
context.resolve(int.class),
f.newSimpleName("size"), //$NON-NLS-1$
Arrays.asList(),
Arrays.asList(new ExpressionBuilder(f, Models.toLiteral(f, size))
.toReturnStatement()));
}
private MethodDeclaration createReferenceMapKeySetMethod(PropertyReferenceDeclaration reference) {
return f.newMethodDeclaration(
null,
new AttributeBuilder(f)
.annotation(context.resolve(Override.class))
.Public()
.toAttributes(),
f.newParameterizedType(context.resolve(Set.class), context.resolve(String.class)),
f.newSimpleName("keySet"), //$NON-NLS-1$
Arrays.asList(),
Arrays.asList(new TypeBuilder(f, f.newNamedType(context.getTypeName()))
.field(getKeysFieldName(reference))
.toReturnStatement()));
}
private MethodDeclaration createReferenceMapGetMethod(PropertyReferenceDeclaration reference) {
SimpleName key = f.newSimpleName("_k"); //$NON-NLS-1$
List<Statement> cases = new ArrayList<>();
reference.getReference().asMap().forEach((k, v) -> {
PropertyDeclaration decl = v.findDeclaration();
assert decl != null;
cases.add(f.newSwitchCaseLabel(Models.toLiteral(f, k)));
cases.add(new TypeBuilder(f, f.newNamedType(context.getTypeName()))
.dotThis()
.method(context.getOptionGetterName(decl))
.toReturnStatement());
});
cases.add(f.newSwitchDefaultLabel());
cases.add(f.newReturnStatement(Models.toNullLiteral(f)));
List<Statement> statements = new ArrayList<>();
statements.add(f.newIfStatement(
f.newUnaryExpression(UnaryOperator.NOT, f.newInstanceofExpression(key, context.resolve(String.class))),
f.newBlock(f.newReturnStatement(Models.toNullLiteral(f)))));
statements.add(f.newSwitchStatement(
f.newCastExpression(context.resolve(String.class), key),
cases));
return f.newMethodDeclaration(
null,
new AttributeBuilder(f)
.annotation(context.resolve(Override.class))
.Public()
.toAttributes(),
context.getElementType(reference),
f.newSimpleName("get"), //$NON-NLS-1$
Arrays.asList(f.newFormalParameterDeclaration(context.resolve(Object.class), key)),
statements);
}
private List<MethodDeclaration> createDataModelMethods() {
List<MethodDeclaration> results = new ArrayList<>();
results.add(createResetMethod());
results.add(createCopyMethod());
return results;
}
private MethodDeclaration createResetMethod() {
List<Statement> statements = new ArrayList<>();
for (PropertyDeclaration property : model.getDeclaredProperties()) {
statements.add(new ExpressionBuilder(f, f.newThis())
.field(context.getFieldName(property))
.method("setNull") //$NON-NLS-1$
.toStatement());
}
return f.newMethodDeclaration(
null,
new AttributeBuilder(f)
.annotation(context.resolve(Override.class))
.annotation(context.resolve(SuppressWarnings.class),
Models.toLiteral(f, "deprecation")) //$NON-NLS-1$
.Public()
.toAttributes(),
context.resolve(void.class),
f.newSimpleName("reset"), //$NON-NLS-1$
Collections.emptyList(),
statements);
}
private MethodDeclaration createCopyMethod() {
SimpleName other = context.createVariableName("other"); //$NON-NLS-1$
List<Statement> statements = new ArrayList<>();
for (PropertyDeclaration property : model.getDeclaredProperties()) {
statements.add(new ExpressionBuilder(f, f.newThis())
.field(context.getFieldName(property))
.method("copyFrom", new ExpressionBuilder(f, other) //$NON-NLS-1$
.field(context.getFieldName(property))
.toExpression())
.toStatement());
}
return f.newMethodDeclaration(
null,
new AttributeBuilder(f)
.annotation(context.resolve(Override.class))
.annotation(context.resolve(SuppressWarnings.class),
Models.toLiteral(f, "deprecation")) //$NON-NLS-1$
.Public()
.toAttributes(),
context.resolve(void.class),
f.newSimpleName("copyFrom"), //$NON-NLS-1$
Collections.singletonList(f.newFormalParameterDeclaration(
context.resolve(context.getQualifiedTypeName()),
other)),
statements);
}
private List<MethodDeclaration> createPropertyAccessors() throws IOException {
List<MethodDeclaration> results = new ArrayList<>();
for (PropertyDeclaration property : model.getDeclaredProperties()) {
results.add(createValueGetter(property));
results.add(createValueSetter(property));
results.add(createOptionGetter(property));
results.add(createOptionSetter(property));
}
return results;
}
private List<MethodDeclaration> createReferenceAccessors() throws IOException {
List<MethodDeclaration> results = new ArrayList<>();
for (PropertyReferenceDeclaration reference : model.getDeclaredPropertyReferences()) {
results.add(createReferenceGetter(reference));
}
return results;
}
private MethodDeclaration createValueGetter(PropertyDeclaration property) {
assert property != null;
List<Attribute> attributes = new ArrayList<>();
attributes.addAll(new AttributeBuilder(f)
.Public()
.toAttributes());
return f.newMethodDeclaration(
new JavadocBuilder(f)
.text(Messages.getString("ConcreteModelEmitter.javadocGetter"), //$NON-NLS-1$
context.getDescription(property))
.returns()
.text(context.getDescription(property))
.exception(context.resolve(NullPointerException.class))
.text(Messages.getString(
"ConcreteModelEmitter.javadocGetterNullPointerException"), //$NON-NLS-1$
context.getDescription(property))
.toJavadoc(),
attributes,
context.getValueType(property),
context.getValueGetterName(property),
Collections.emptyList(),
Collections.singletonList(new ExpressionBuilder(f, f.newThis())
.field(context.getFieldName(property))
.method("get") //$NON-NLS-1$
.toReturnStatement()));
}
private MethodDeclaration createValueSetter(PropertyDeclaration property) {
assert property != null;
SimpleName paramName = context.createVariableName("value"); //$NON-NLS-1$
Type valueType = context.getValueType(property);
return f.newMethodDeclaration(
new JavadocBuilder(f)
.text(Messages.getString("ConcreteModelEmitter.javadocSetter"), //$NON-NLS-1$
context.getDescription(property))
.param(paramName)
.text(Messages.getString("ConcreteModelEmitter.javadocSetterParameter"), //$NON-NLS-1$
context.getDescription(property))
.toJavadoc(),
new AttributeBuilder(f)
.annotation(
context.resolve(SuppressWarnings.class),
Models.toLiteral(f, "deprecation")) //$NON-NLS-1$
.Public()
.toAttributes(),
context.resolve(void.class),
context.getValueSetterName(property),
Arrays.asList(new FormalParameterDeclaration[] {
f.newFormalParameterDeclaration(valueType, paramName)
}),
Collections.singletonList(new ExpressionBuilder(f, f.newThis())
.field(context.getFieldName(property))
.method("modify", paramName) //$NON-NLS-1$
.toStatement()));
}
private MethodDeclaration createOptionGetter(PropertyDeclaration property) throws IOException {
assert property != null;
List<Attribute> attributes = new ArrayList<>();
attributes.addAll(driver.getMemberAnnotations(context, property));
attributes.addAll(new AttributeBuilder(f)
.Public()
.toAttributes());
return f.newMethodDeclaration(
new JavadocBuilder(f)
.text(Messages.getString("ConcreteModelEmitter.javadocOptionGetter"), //$NON-NLS-1$
context.getDescription(property))
.returns()
.text(context.getDescription(property))
.toJavadoc(),
attributes,
context.getFieldType(property),
context.getOptionGetterName(property),
Collections.emptyList(),
Collections.singletonList(new ExpressionBuilder(f, f.newThis())
.field(context.getFieldName(property))
.toReturnStatement()));
}
private MethodDeclaration createOptionSetter(PropertyDeclaration property) {
assert property != null;
SimpleName paramName = context.createVariableName("option"); //$NON-NLS-1$
Type optionType = context.getFieldType(property);
return f.newMethodDeclaration(
new JavadocBuilder(f)
.text(Messages.getString("ConcreteModelEmitter.javadocOptionSetter"), //$NON-NLS-1$
context.getDescription(property))
.param(paramName)
.text(Messages.getString("ConcreteModelEmitter.javadocOptionSetterParameter"), //$NON-NLS-1$
context.getDescription(property))
.toJavadoc(),
new AttributeBuilder(f)
.annotation(
context.resolve(SuppressWarnings.class),
Models.toLiteral(f, "deprecation")) //$NON-NLS-1$
.Public()
.toAttributes(),
context.resolve(void.class),
context.getOptionSetterName(property),
Arrays.asList(new FormalParameterDeclaration[] {
f.newFormalParameterDeclaration(optionType, paramName)
}),
Collections.singletonList(new ExpressionBuilder(f, f.newThis())
.field(context.getFieldName(property))
.method("copyFrom", paramName) //$NON-NLS-1$
.toStatement()));
}
private MethodDeclaration createReferenceGetter(PropertyReferenceDeclaration reference) throws IOException {
List<Attribute> attributes = new ArrayList<>();
attributes.addAll(driver.getMemberAnnotations(context, reference));
attributes.addAll(new AttributeBuilder(f)
.Public()
.toAttributes());
return f.newMethodDeclaration(
new JavadocBuilder(f)
.text(Messages.getString("ConcreteModelEmitter.javadocReferenceGetter"), //$NON-NLS-1$
context.getDescription(reference))
.returns()
.text(context.getDescription(reference))
.toJavadoc(),
attributes,
context.getContainerType(reference),
context.getReferenceGetterName(reference),
Collections.emptyList(),
Arrays.asList(new ExpressionBuilder(f, f.newThis())
.field(context.getFieldName(reference))
.toReturnStatement()));
}
}