From 920cd85f2c25b7f4854b2fab74ed6f5a8064d0eb Mon Sep 17 00:00:00 2001 From: Gavriil Sitnikov Date: Sun, 30 Apr 2017 18:01:18 +0300 Subject: [PATCH] serialization/deserialization added --- gradle/apiGeneration.gradle | 126 ++++++++++++++++++++++++------------ 1 file changed, 86 insertions(+), 40 deletions(-) diff --git a/gradle/apiGeneration.gradle b/gradle/apiGeneration.gradle index 5b2927a..ccc05f4 100644 --- a/gradle/apiGeneration.gradle +++ b/gradle/apiGeneration.gradle @@ -38,8 +38,8 @@ import javax.lang.model.element.Modifier //TODO: missable in future //TODO: NUMBER/BOOLEAN enums in future +//TODO: maybe save md5-hashes to check if files/scheme changed -//TODO: move out of allvariants - too much //TODO: serialization/deserialization //TODO: setup generation by map yaml->package @@ -55,6 +55,8 @@ class Types { static final TypeName API_MODEL = ClassName.bestGuess("ru.touchin.templates.ApiModel") static final TypeName LOGAN_SQUARE_JSON_MODEL = ClassName.bestGuess("ru.touchin.templates.logansquare.LoganSquareJsonModel") static final TypeName OBJECT_UTILS = ClassName.bestGuess("ru.touchin.roboswag.core.utils.ObjectUtils") + static final TypeName OBJECT_OUTPUT_STREAM = ClassName.bestGuess("java.io.ObjectOutputStream") + static final TypeName OBJECT_INPUT_STREAM = ClassName.bestGuess("java.io.ObjectInputStream") } @@ -220,12 +222,12 @@ class EnumObject extends SchemeObject { */ enum FieldType { - BOOLEAN(TypeName.BOOLEAN, ClassName.get(Boolean.class), false), - INT(TypeName.INT, ClassName.get(Integer.class), false), - LONG(TypeName.LONG, ClassName.get(Long.class), false), - FLOAT(TypeName.FLOAT, ClassName.get(Float.class), false), - DOUBLE(TypeName.DOUBLE, ClassName.get(Double.class), false), - STRING(ClassName.get(String.class), false), + BOOLEAN(TypeName.BOOLEAN, ClassName.get(Boolean.class), false, "writeBoolean", "readBoolean"), + INT(TypeName.INT, ClassName.get(Integer.class), false, "writeInt", "readInt"), + LONG(TypeName.LONG, ClassName.get(Long.class), false, "writeLong", "readLong"), + FLOAT(TypeName.FLOAT, ClassName.get(Float.class), false, "writeFloat", "readFloat"), + DOUBLE(TypeName.DOUBLE, ClassName.get(Double.class), false, "writeDouble", "readDouble"), + STRING(ClassName.get(String.class), false, "writeUTF", "readUTF"), LIST(ClassName.get(List.class), true), MAP(ClassName.get(Map.class), true), DATE_TIME(ClassName.bestGuess("org.joda.time.DateTime"), false), @@ -239,19 +241,29 @@ enum FieldType { final TypeName nonPrimitiveTypeName // flag to check if such type could be validate via ApiModel class methods in some way final boolean ableToInnerValidate + final String serializationMethodName + final String deserializationMethodName FieldType(final boolean ableToInnerValidate) { - this(null, null, ableToInnerValidate) + this(null, null, ableToInnerValidate, null, null) } FieldType(final TypeName typeName, final boolean ableToInnerValidate) { - this(typeName, typeName, ableToInnerValidate) + this(typeName, typeName, ableToInnerValidate, null, null) } - FieldType(final TypeName primitiveTypeName, final TypeName nonPrimitiveTypeName, final boolean ableToInnerValidate) { + FieldType(final TypeName typeName, final boolean ableToInnerValidate, + final String serializationMethodName, final String deserializationMethodName) { + this(typeName, typeName, ableToInnerValidate, serializationMethodName, deserializationMethodName) + } + + FieldType(final TypeName primitiveTypeName, final TypeName nonPrimitiveTypeName, final boolean ableToInnerValidate, + final String serializationMethodName, final String deserializationMethodName) { this.primitiveTypeName = primitiveTypeName this.nonPrimitiveTypeName = nonPrimitiveTypeName this.ableToInnerValidate = ableToInnerValidate + this.serializationMethodName = serializationMethodName + this.deserializationMethodName = deserializationMethodName } /** @@ -679,26 +691,42 @@ class ClassObject extends SchemeObject { .addAnnotation(AnnotationSpec.builder(Types.JSON_OBJECT).addMember("serializeNullObjects", "true").build()) .superclass(superclass != null ? superclass : Types.LOGAN_SQUARE_JSON_MODEL) - // add type variables + // adds type variables for (String typeVariable : typeVariables) { classBuilder.addTypeVariable(TypeVariableName.get(typeVariable)) } - // add default constructor + // adds default constructor classBuilder.addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC).addStatement("super()").build()) - // create full constructor only if it is extends from LoganSquareJsonModel, + // creates full constructor only if it is extends from LoganSquareJsonModel, // else we can't create constructor as parent constructor could also have parameters final MethodSpec.Builder fullConstructorBuilder = superclass == null ? MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC).addStatement("super()") : null - // create validate() method + // creates validate() method final MethodSpec.Builder validateMethod = MethodSpec.methodBuilder("validate").addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class) .addException(ClassName.bestGuess("ValidationException")) .addStatement("super.validate()") + // creates writeObject() method + final MethodSpec.Builder serializeMethod = MethodSpec.methodBuilder("writeObject").addModifiers(Modifier.PRIVATE) + .addException(ClassName.get(IOException.class)) + .addParameter(ParameterSpec.builder(Types.OBJECT_OUTPUT_STREAM, "outputStream", Modifier.FINAL) + .addAnnotation(Types.NON_NULL) + .build()) + + // creates readObject() method + final MethodSpec.Builder deserializeMethod = MethodSpec.methodBuilder("readObject").addModifiers(Modifier.PRIVATE) + .addAnnotation(AnnotationSpec.builder(SuppressWarnings.class).addMember("value", "\$S", "unchecked").build()) + .addException(ClassName.get(IOException.class)) + .addException(ClassName.get(ClassNotFoundException.class)) + .addParameter(ParameterSpec.builder(Types.OBJECT_INPUT_STREAM, "inputStream", Modifier.FINAL) + .addAnnotation(Types.NON_NULL) + .build()) + boolean first = true final CodeBlock.Builder equalsStatement = CodeBlock.builder() final CodeBlock.Builder hashCodeStatement = CodeBlock.builder() @@ -708,6 +736,16 @@ class ClassObject extends SchemeObject { classBuilder.addMethod(field.generateGetterCode()) classBuilder.addMethod(field.generateSetterCode()) field.generateValidationCode(validateMethod) + final String serializeMethodName = (!field.nullable && field.type.serializationMethodName != null + ? field.type.serializationMethodName : "writeObject"); + final String deserializeMethodName = (!field.nullable && field.type.deserializationMethodName != null + ? field.type.deserializationMethodName : "readObject"); + serializeMethod.addStatement("outputStream.\$L(\$L)", serializeMethodName, field.name) + if (deserializeMethodName.equals("readObject")) { + deserializeMethod.addStatement("\$L = (\$T) inputStream.\$L()", field.name, field.typeName, deserializeMethodName) + } else{ + deserializeMethod.addStatement("\$L = inputStream.\$L()", field.name, deserializeMethodName) + } if (fullConstructorBuilder != null) { fullConstructorBuilder.addParameter(ParameterSpec.builder(field.typeName, field.name, Modifier.FINAL) @@ -748,9 +786,9 @@ class ClassObject extends SchemeObject { equalsStatement.add(";\n") hashCodeStatement.add(");\n") - // create validate() method + // creates validate() method classBuilder.addMethod(validateMethod.build()) - // add equals() method + // adds equals() method classBuilder.addMethod(MethodSpec.methodBuilder("equals").addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class) .returns(TypeName.BOOLEAN) @@ -759,11 +797,15 @@ class ClassObject extends SchemeObject { .beginControlFlow("if (object == null || getClass() != object.getClass())").addStatement("return false").endControlFlow() .addStatement("final \$T that = (\$T) object", ClassName.bestGuess(name), ClassName.bestGuess(name)) .addCode(equalsStatement.build()).build()) - // add hashCode() method + // adds hashCode() method classBuilder.addMethod(MethodSpec.methodBuilder("hashCode").addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class) .returns(TypeName.INT) .addCode(hashCodeStatement.build()).build()) + // adds writeObject() method + classBuilder.addMethod(serializeMethod.build()) + // adds readObject() method + classBuilder.addMethod(deserializeMethod.build()) if (fullConstructorBuilder != null) { classBuilder.addMethod(fullConstructorBuilder.build()) @@ -791,26 +833,32 @@ class FileUtils { android.applicationVariants.all { variant -> - File generatedModels = new File("${project.buildDir}/generated/source/api/model/${variant.dirName}") - String modelsPackage = android.extensions.findByName("apiGeneratorModelsPackage") - String schemePath = android.extensions.findByName("apiGeneratorSchemePath") - if (modelsPackage == null) { - modelsPackage = android.defaultConfig.applicationId + '.logic.api.model' - } + final File generatedModelsDirectory = new File("${project.buildDir}/generated/source/jsonModels/${variant.dirName}") + /** + * Generating Java classes describing JSON models from specific YAML scheme. + */ + def generateJsonModelsTask = tasks.create("generateJsonModels${variant.name}") << { + String modelsPackage = android.extensions.findByName("apiGeneratorModelsPackage") + String schemeFilePath = android.extensions.findByName("apiGeneratorSchemePath") + if (modelsPackage == null) { + modelsPackage = android.defaultConfig.applicationId + '.logic.api.model' + } - if (schemePath == null) { - return - } + if (schemeFilePath == null) { + return + } - File schemeFile = new File(schemePath) - if (!schemeFile.exists()) { - schemeFile = new File("${project.projectDir}", schemePath) - } + File schemeFile = new File(schemeFilePath) + if (!schemeFile.exists()) { + schemeFile = new File("${project.projectDir}", schemeFilePath) + } + if (!schemeFile.exists()) { + throw new Exception("JSON models scheme file not found at '" + schemeFilePath + "' or at '${project.projectDir}/" + schemeFilePath + "'") + } + FileUtils.purgeDirectory(generatedModelsDirectory) - def apiModelsGenerationTask = tasks.create("apiModelsGeneration${variant.name}") << { - - Yaml yaml = new Yaml() - Map schemeObjects = new HashMap<>() + final Yaml yaml = new Yaml() + final Map schemeObjects = new HashMap<>() schemeObjects.put("Map", new ImportObject("java.util.Map")) schemeObjects.put("List", new ImportObject("java.util.List")) schemeObjects.put("DateTime", new ImportObject("org.joda.time.DateTime")) @@ -838,9 +886,7 @@ android.applicationVariants.all { } } - FileUtils.purgeDirectory(generatedModels) - - for (SchemeObject schemeObject : schemeObjects.values()) { + for (final SchemeObject schemeObject : schemeObjects.values()) { if (schemeObject instanceof ClassObject) { try { schemeObject.resolveFieldsInfo(schemeObjects) @@ -849,13 +895,13 @@ android.applicationVariants.all { } } try { - schemeObject.writeToFile(generatedModels, schemeObjects, modelsPackage) + schemeObject.writeToFile(generatedModelsDirectory, schemeObjects, modelsPackage) } catch (final Exception exception) { throw new Exception("Error on generating code for '" + schemeObject.name + "' : " + exception.getMessage()) } } } - apiModelsGenerationTask.description = 'Generates API models' - variant.registerJavaGeneratingTask apiModelsGenerationTask, generatedModels + generateJsonModelsTask.description = 'Generates Java classes for JSON models' + variant.registerJavaGeneratingTask generateJsonModelsTask, generatedModelsDirectory } \ No newline at end of file