/* * Copyright (C) 2018 Contentful GmbH * * 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.contentful.vault.compiler; import com.contentful.vault.FieldMeta; import com.contentful.vault.ModelHelper; import com.contentful.vault.SpaceHelper; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeSpec; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import static com.contentful.vault.Sql.RESOURCE_COLUMNS; final class ModelInjection extends Injection { final String sqlTableName; final Set<FieldMeta> fields; private FieldSpec specFields; public ModelInjection(String remoteId, ClassName className, TypeElement originatingElement, String sqlTableName, Set<FieldMeta> fields) { super(remoteId, className, originatingElement); this.sqlTableName = sqlTableName; this.fields = fields; } @Override TypeSpec.Builder getTypeSpecBuilder() { ParameterizedTypeName modelHelperType = ParameterizedTypeName.get(ClassName.get(ModelHelper.class), ClassName.get(originatingElement)); TypeSpec.Builder builder = TypeSpec.classBuilder(className.simpleName()) .superclass(modelHelperType) .addModifiers(Modifier.PUBLIC, Modifier.FINAL); appendFields(builder); appendTableName(builder); appendCreateStatements(builder); appendFromCursor(builder); appendSetField(builder); appendConstructor(builder); return builder; } @SuppressWarnings("unchecked") private void appendConstructor(TypeSpec.Builder builder) { MethodSpec.Builder ctor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC); for (FieldMeta f : fields) { CodeBlock.Builder block = CodeBlock.builder(); block.add("$N.add($T.builder()", specFields, ClassName.get(FieldMeta.class)) .add(".setId($S)", f.id()) .add(".setName($S)", f.name()); if (f.sqliteType() != null) { block.add(".setSqliteType($S)", f.sqliteType()); } if (f.isLink()) { block.add(".setLinkType($S)", f.linkType()); } if (f.isArray()) { block.add(".setArrayType($S)", f.arrayType()); } block.add(".build());\n"); ctor.addCode(block.build()); } builder.addMethod(ctor.build()); } private void appendSetField(TypeSpec.Builder builder) { MethodSpec.Builder method = MethodSpec.methodBuilder("setField") .addAnnotation(Override.class) .addAnnotation( AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "unchecked") .build()) .addModifiers(Modifier.PUBLIC) .returns(boolean.class) .addParameter(ParameterSpec.builder(ClassName.get(originatingElement), "resource").build()) .addParameter(ParameterSpec.builder(ClassName.get(String.class), "name").build()) .addParameter(ParameterSpec.builder(ClassName.get(Object.class), "value").build()); FieldMeta[] array = fields.toArray(new FieldMeta[fields.size()]); for (int i = 0; i < array.length; i++) { FieldMeta field = array[i]; if (i == 0) { method.beginControlFlow("if ($S.equals(name))", field.name()); } else { method.endControlFlow().beginControlFlow("else if ($S.equals(name))", field.name()); } method.addStatement("resource.$L = ($T) value", field.name(), field.type()); } method.endControlFlow() .beginControlFlow("else") .addStatement("return false") .endControlFlow() .addStatement("return true"); builder.addMethod(method.build()); } private void appendFromCursor(TypeSpec.Builder builder) { ClassName modelClassName = ClassName.get(originatingElement); MethodSpec.Builder method = MethodSpec.methodBuilder("fromCursor") .returns(modelClassName) .addAnnotation(Override.class) .addAnnotation( AnnotationSpec.builder(SuppressWarnings.class).addMember("value", "$S", "unchecked").build()) .addModifiers(Modifier.PUBLIC) .addParameter( ParameterSpec.builder(ClassName.get("android.database", "Cursor"), "cursor").build()); String result = "result"; method.addStatement("$T $N = new $T()", modelClassName, result, modelClassName) .addStatement("setContentType($N, $S)", result, remoteId); List<FieldMeta> nonLinkFields = extractNonLinkFields(); for (int i = 0; i < nonLinkFields.size(); i++) { FieldMeta field = nonLinkFields.get(i); int columnIndex = RESOURCE_COLUMNS.length + i; String fqClassName = field.type().toString(); String name = field.name(); if (String.class.getName().equals(fqClassName)) { method.addStatement("$N.$L = cursor.getString($L)", result, name, columnIndex); } else if (Boolean.class.getName().equals(fqClassName)) { method.addStatement("$N.$L = Integer.valueOf(1).equals(cursor.getInt($L))", result, name, columnIndex); } else if (Integer.class.getName().equals(fqClassName)) { method.addStatement("$N.$L = cursor.getInt($L)", result, name, columnIndex); } else if (Double.class.getName().equals(fqClassName)) { method.addStatement("$N.$L = cursor.getDouble($L)", result, name, columnIndex); } else if (Map.class.getName().equals(fqClassName)) { method.addStatement("$N.$L = fieldFromBlob($T.class, cursor, $L)", result, name, ClassName.get(HashMap.class), columnIndex); } else if (field.isArrayOfSymbols()) { method.addStatement("$N.$L = fieldFromBlob($T.class, cursor, $L)", result, name, ClassName.get(ArrayList.class), columnIndex); } } method.addStatement("return $N", result); builder.addMethod(method.build()); } private void appendCreateStatements(TypeSpec.Builder builder) { MethodSpec.Builder method = MethodSpec.methodBuilder("getCreateStatements") .returns(ParameterizedTypeName.get(List.class, String.class)) .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .addParameter(SpaceHelper.class, "spaceHelper"); method.addStatement("$T list = new $T()", ParameterizedTypeName.get(List.class, String.class), ParameterizedTypeName.get(ArrayList.class, String.class)); method.beginControlFlow("for (String code : spaceHelper.getLocales())"); for (String sql : getModelCreateStatements()) { method.addStatement("list.add($L)", sql); } method.endControlFlow(); method.addStatement("return list"); builder.addMethod(method.build()); } private void appendTableName(TypeSpec.Builder builder) { builder.addMethod(MethodSpec.methodBuilder("getTableName") .returns(ClassName.get(String.class)) .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .addStatement("return $S", sqlTableName) .build()); } private void appendFields(TypeSpec.Builder builder) { // Field specFields = createListWithInitializer("fields", ArrayList.class, ClassName.get(FieldMeta.class)).addModifiers(Modifier.FINAL).build(); builder.addField(specFields); // Getter builder.addMethod(createGetterImpl(specFields, "getFields").build()); } List<String> getModelCreateStatements() { List<String> statements = new ArrayList<>(); StringBuilder builder = new StringBuilder(); builder.append("\"CREATE TABLE `") .append(sqlTableName) .append("$\" + code + \"") .append("` ("); for (int i = 0; i < RESOURCE_COLUMNS.length; i++) { builder.append(RESOURCE_COLUMNS[i]); if (i < RESOURCE_COLUMNS.length - 1) { builder.append(", "); } } List<FieldMeta> list = extractNonLinkFields(); for (int i = 0; i < list.size(); i++) { FieldMeta f = list.get(i); builder.append(", `") .append(f.name()) .append("` ") .append(f.sqliteType()); } builder.append(");\""); statements.add(builder.toString()); return statements; } List<FieldMeta> extractNonLinkFields() { List<FieldMeta> result = new ArrayList<>(); for (FieldMeta f : fields) { // Skip links / arrays of links if (f.isLink() || f.isArrayOfLinks()) { continue; } result.add(f); } return result; } }