Java tutorial
/** * Copyright 2015 Appvengers * * 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 aeon.compiler.generators.marshaller; import aeon.compiler.context.Context; import aeon.compiler.context.Field; import aeon.compiler.context.SqliteField; import aeon.compiler.generators.AbstractNestedClassGenerator; import aeon.compiler.generators.CodeBlocks; import aeon.compiler.generators.Names; import aeon.compiler.generators.TypeMap; import aeon.compiler.generators.idaccessor.PersisterIdAccessorGeneratorFactory; import aeon.compiler.utils.Utils; import aeon.internal.binder.Binders; import aeon.internal.marshall.AbstractMarshaller; import aeon.internal.marshall.Marshaller; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.FluentIterable; import com.google.common.collect.Lists; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import com.squareup.javapoet.*; import javax.lang.model.element.Modifier; import java.util.Collections; import static aeon.compiler.generators.ClassNames.Cursor; import static aeon.compiler.generators.ClassNames.SQLiteStatement; /** * Generator for the Persister.Marshaller * * @author Sven Jacobs */ class PersisterMarshallerGeneratorImpl extends AbstractNestedClassGenerator implements PersisterMarshallerGenerator { private static final String CLASS_NAME = "Marshaller"; private static final String OBJ_PARAM = "obj"; private static final String STMT_PARAM = "stmt"; private static final String CURSOR_PARAM = "cursor"; private final PersisterIdAccessorGeneratorFactory mIdAccessorGeneratorFactory; @Inject public PersisterMarshallerGeneratorImpl(final PersisterIdAccessorGeneratorFactory idAccessorGeneratorFactory, @Assisted final Context context) { super(context); mIdAccessorGeneratorFactory = idAccessorGeneratorFactory; } @Override public TypeSpec classSpec() { final ParameterizedTypeName superclass = ParameterizedTypeName.get(ClassName.get(AbstractMarshaller.class), getContext().getTargetClassName(), ClassName.get(Utils.getIdFieldType(getContext()))); return TypeSpec.classBuilder(CLASS_NAME).superclass(superclass) .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL).addMethod(constructorMethod()) .addMethod(unmarshallMethod()).addMethod(unmarshallWithInstanceMethod()) .addMethod(bindInsertStatementMethod()).addMethod(bindUpdateStatementMethod()) .addMethod(bindIdMethod()).addMethod(getInsertStatementMethod()) .addMethod(getUpdateStatementMethod()).build(); } @Override public MethodSpec createMarshallerMethod() { final ParameterizedTypeName returnType = ParameterizedTypeName.get(ClassName.get(Marshaller.class), getContext().getTargetClassName()); return MethodSpec.methodBuilder("createMarshaller").addAnnotation(Override.class) .addModifiers(Modifier.PROTECTED).returns(returnType).addStatement("return new $T()", className()) .build(); } @Override protected String getPeerClassName() { return CLASS_NAME; } private MethodSpec constructorMethod() { return MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE) .addStatement("super(new $T())", mIdAccessorGeneratorFactory.create(getContext()).className()) .build(); } private MethodSpec unmarshallMethod() { final ClassName targetClass = getContext().getTargetClassName(); return MethodSpec.methodBuilder("unmarshall").addAnnotation(Override.class).addModifiers(Modifier.PUBLIC) .addParameter(Cursor(), CURSOR_PARAM, Modifier.FINAL).returns(targetClass) .addStatement("return unmarshall(new $T(), $L)", targetClass, CURSOR_PARAM).build(); } private MethodSpec unmarshallWithInstanceMethod() { // TODO: Use Cursor#getColumnIndex()? But Persister is generated during every compile anyway... final ClassName targetClass = getContext().getTargetClassName(); final CodeBlock.Builder builder = CodeBlock.builder().add(CodeBlocks.checkNotNull(OBJ_PARAM)) .add(CodeBlocks.checkNotNull(CURSOR_PARAM)); int columnIndex = 0; for (final Field field : getContext().getFieldContext().getFields()) { final String bindersMethod = TypeMap.binderNameOf(field.getType()); builder.addStatement("$L.$L = $T.$L().unbind($L, $L, $L)", OBJ_PARAM, field.getName(), Binders.class, bindersMethod, CURSOR_PARAM, columnIndex, field.isNullable()); columnIndex++; } builder.addStatement("return $L", OBJ_PARAM); return MethodSpec.methodBuilder("unmarshall").addAnnotation(Override.class).addModifiers(Modifier.PUBLIC) .addParameter(targetClass, OBJ_PARAM, Modifier.FINAL) .addParameter(Cursor(), CURSOR_PARAM, Modifier.FINAL).returns(targetClass).addCode(builder.build()) .build(); } private MethodSpec bindInsertStatementMethod() { final CodeBlock.Builder builder = buildBindings(CodeBlock.builder(), getContext().getFieldContext().getFields(), 1, true).addStatement("return $L", STMT_PARAM); return MethodSpec.methodBuilder("bindInsertStatement").addAnnotation(Override.class) .addModifiers(Modifier.PROTECTED).returns(SQLiteStatement()) .addParameter(SQLiteStatement(), STMT_PARAM, Modifier.FINAL) .addParameter(getContext().getTargetClassName(), OBJ_PARAM, Modifier.FINAL).addCode(builder.build()) .build(); } private MethodSpec bindUpdateStatementMethod() { final FluentIterable<Field> fieldsWithoutId = getContext().getFieldContext().getFieldsWithoutId(); final CodeBlock.Builder builder = CodeBlock.builder(); if (fieldsWithoutId.size() == 0) { builder.addStatement("throw new $T($S)", UnsupportedOperationException.class, String.format("Class %s cannot be updated. Only has one field (the ID field).", getContext().getTargetClassName().simpleName())); } else { final Field idField = getContext().getFieldContext().getIdField(); if (idField.getType().isReferenceType()) { builder.beginControlFlow("if ($L.$L == null)", OBJ_PARAM, idField.getName()) .addStatement("throw new $T($S)", NullPointerException.class, "ID field cannot be null") .endControlFlow(); } buildBindings(builder, fieldsWithoutId, 1, false); buildBindings(builder, Lists.newArrayList(idField), fieldsWithoutId.size() + 1, false); builder.addStatement("return $L", STMT_PARAM); } return MethodSpec.methodBuilder("bindUpdateStatement").addAnnotation(Override.class) .addModifiers(Modifier.PROTECTED).returns(SQLiteStatement()) .addParameter(SQLiteStatement(), STMT_PARAM, Modifier.FINAL) .addParameter(getContext().getTargetClassName(), OBJ_PARAM, Modifier.FINAL).addCode(builder.build()) .build(); } private MethodSpec bindIdMethod() { final String ID_PARAM = "id"; final Field idField = getContext().getFieldContext().getIdField(); final Field.Type type = idField.getType(); final CodeBlock.Builder builder = CodeBlock.builder(); if (type == Field.Type.STRING) { builder.add("// Do nothing\n"); } else { idFieldDefaultValueControlFlow(builder, idField, OBJ_PARAM) .addStatement("getIdAccessor().setId($L, $L)", ID_PARAM, OBJ_PARAM).endControlFlow(); } return MethodSpec.methodBuilder("bindId").addAnnotation(Override.class).addModifiers(Modifier.PROTECTED) .addParameter(long.class, ID_PARAM, Modifier.FINAL) .addParameter(getContext().getTargetClassName(), OBJ_PARAM, Modifier.FINAL).addCode(builder.build()) .build(); } private MethodSpec getInsertStatementMethod() { final FluentIterable<SqliteField> fields = getSqliteContext().getFieldContext().getFields(); final String columns = Joiner.on(", ").join(Names.escapedNames(fields)); final String parameters = Joiner.on(", ").join(Collections.nCopies(fields.size(), "?")); final String stmt = String.format("INSERT INTO %s (%s) VALUES (%s)", getSqliteContext().getTableName().asEscapedName(), columns, parameters); return MethodSpec.methodBuilder("getInsertStatement").addAnnotation(Override.class) .addModifiers(Modifier.PROTECTED).returns(String.class).addStatement("return $S", stmt).build(); } private MethodSpec getUpdateStatementMethod() { final FluentIterable<SqliteField> fieldsWithoutId = getSqliteContext().getFieldContext() .getFieldsWithoutId(); final String stmt; if (fieldsWithoutId.size() == 0) { stmt = ""; } else { final FluentIterable<String> assignments = fieldsWithoutId .transform(new Function<SqliteField, String>() { @Override public String apply(final SqliteField input) { return input.getName().asEscapedName() + " = ?"; } }); stmt = String.format("UPDATE %s SET %s WHERE %s = ?", getSqliteContext().getTableName().asEscapedName(), Joiner.on(", ").join(assignments), getSqliteContext().getFieldContext().getIdField().getName().asEscapedName()); } return MethodSpec.methodBuilder("getUpdateStatement").addAnnotation(Override.class) .addModifiers(Modifier.PROTECTED).returns(String.class).addStatement("return $S", stmt).build(); } private static CodeBlock.Builder buildBindings(final CodeBlock.Builder builder, final Iterable<Field> fields, final int startIndex, final boolean permitIdWithDefault) { int index = startIndex; for (final Field field : fields) { final String bindersMethod = TypeMap.binderNameOf(field.getType()); if (field.isId() && permitIdWithDefault) { idFieldDefaultValueControlFlow(builder, field, OBJ_PARAM) .addStatement("$T.nullBinder().bind(null, $L, $L, true)", Binders.class, STMT_PARAM, index) .nextControlFlow("else"); } builder.addStatement("$T.$L().bind($L.$L, $L, $L, $L)", Binders.class, bindersMethod, OBJ_PARAM, field.getName(), STMT_PARAM, index, field.isNullable()); if (field.isId() && permitIdWithDefault) { builder.endControlFlow(); } index++; } return builder; } private static CodeBlock.Builder idFieldDefaultValueControlFlow(final CodeBlock.Builder builder, final Field idField, final String objVarName) { final Field.Type type = idField.getType(); final String name = idField.getName(); if (type == Field.Type.STRING || !type.isReferenceType()) { final String defaultValue = type == Field.Type.STRING ? "null" : "0"; builder.beginControlFlow("if ($L.$L == $L)", objVarName, name, defaultValue); } else { builder.beginControlFlow("if ($L.$L == null || $L.$L == 0)", objVarName, name, objVarName, name); } return builder; } }