From ac772d9d9238d39639440af278a3bb738350893f Mon Sep 17 00:00:00 2001 From: Gavriil Sitnikov Date: Fri, 5 May 2017 18:03:19 +0300 Subject: [PATCH 1/7] merging imports from all generated mapping --- gradle/jsonModelsGeneration.gradle | 73 ++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/gradle/jsonModelsGeneration.gradle b/gradle/jsonModelsGeneration.gradle index 20fb624..0acf49e 100644 --- a/gradle/jsonModelsGeneration.gradle +++ b/gradle/jsonModelsGeneration.gradle @@ -34,6 +34,7 @@ import javafx.util.Pair import org.yaml.snakeyaml.Yaml import javax.lang.model.element.Modifier +import java.util.Map.Entry //TODO: missable in future @@ -840,8 +841,7 @@ class FileUtils { } } - static void generateJsonModelsCode(final File generatedModelsDirectory, final String schemeFilePath, - final String modelsPackage, final String projectDir) { + static Map getObjectsMap(final String schemeFilePath, final String projectDir) { if (schemeFilePath == null) { return } @@ -883,20 +883,7 @@ class FileUtils { } } - for (final SchemeObject schemeObject : schemeObjects.values()) { - if (schemeObject instanceof ClassObject) { - try { - schemeObject.resolveFieldsInfo(schemeObjects) - } catch (final Exception exception) { - throw new Exception("Error on parsing class '" + schemeObject.name + "' : " + exception.getMessage()) - } - } - try { - schemeObject.writeToFile(generatedModelsDirectory, schemeObjects, modelsPackage) - } catch (final Exception exception) { - throw new Exception("Error on generating code for '" + schemeObject.name + "' : " + exception.getMessage()) - } - } + return schemeObjects } } @@ -911,18 +898,56 @@ android.applicationVariants.all { final List jsonModelsMapping = android.extensions.findByName("jsonModelsMapping") FileUtils.purgeDirectory(generatedModelsDirectory) + final Map> overallObjects = new HashMap<>() for (final String jsonMapping : jsonModelsMapping) { final int indexOfDivider = jsonMapping.indexOf('->') + final String packageName + final Map objects if (indexOfDivider == -1) { - FileUtils.generateJsonModelsCode(generatedModelsDirectory, - jsonMapping.trim(), - android.defaultConfig.applicationId + '.logic.model', - "${project.projectDir}") + packageName = android.defaultConfig.applicationId + '.logic.model' + objects = FileUtils.getObjectsMap(jsonMapping.trim(), "${project.projectDir}") } else { - FileUtils.generateJsonModelsCode(generatedModelsDirectory, - jsonMapping.substring(0, indexOfDivider).trim(), - jsonMapping.substring(indexOfDivider + 2).trim(), - "${project.projectDir}") + packageName = jsonMapping.substring(indexOfDivider + 2).trim() + objects = FileUtils.getObjectsMap(jsonMapping.substring(0, indexOfDivider).trim(), "${project.projectDir}") + } + if (overallObjects.containsKey(packageName)) { + overallObjects.get(packageName).putAll(objects) + } else { + overallObjects.put(packageName, objects) + } + } + + for (final Entry> fileObjects : overallObjects.entrySet()) { + final String packageName = fileObjects.key + final Map schemeObjects = new HashMap<>(fileObjects.value) + for (final Entry> externalObjects : overallObjects.entrySet()) { + if (externalObjects.key == packageName) { + continue + } + for (final SchemeObject externalObject : externalObjects.value.values()) { + if (externalObject instanceof ImportObject) { + schemeObjects.put(externalObject.name, externalObject) + } else if (externalObject instanceof EnumObject) { + schemeObjects.put(externalObject.name, new ImportObject(externalObjects.key + '.' + externalObject.name)) + } else if (externalObject instanceof ClassObject) { + schemeObjects.put(externalObject.name, new ImportObject(externalObjects.key + '.' + externalObject.name)) + } + } + } + + for (final SchemeObject schemeObject : schemeObjects.values()) { + if (schemeObject instanceof ClassObject) { + try { + schemeObject.resolveFieldsInfo(schemeObjects) + } catch (final Exception exception) { + throw new Exception("Error on parsing class '" + schemeObject.name + "' : " + exception.getMessage()) + } + } + try { + schemeObject.writeToFile(generatedModelsDirectory, schemeObjects, packageName) + } catch (final Exception exception) { + throw new Exception("Error on generating code for '" + schemeObject.name + "' : " + exception.getMessage()) + } } } } From 56aefeeab9f9802c1c11ca6753406d2ffb00b0d0 Mon Sep 17 00:00:00 2001 From: Gavriil Sitnikov Date: Fri, 5 May 2017 18:43:35 +0300 Subject: [PATCH 2/7] validation bug of imports fixed --- gradle/jsonModelsGeneration.gradle | 33 ++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/gradle/jsonModelsGeneration.gradle b/gradle/jsonModelsGeneration.gradle index 0acf49e..84ebfa7 100644 --- a/gradle/jsonModelsGeneration.gradle +++ b/gradle/jsonModelsGeneration.gradle @@ -87,14 +87,22 @@ abstract class SchemeObject { */ class ImportObject extends SchemeObject { + enum Type { + MODEL, + ENUM, + EXTERNAL + } + static final String GROUP_NAME = "imports" final String name final String fullName + final Type type - ImportObject(String value) { + ImportObject(final String value, final Type type) { fullName = value.trim() name = fullName.substring(fullName.lastIndexOf('.') + 1) + this.type = type } @Override @@ -284,7 +292,14 @@ enum FieldType { default: final SchemeObject object = objects.get(typeString) if (object instanceof ImportObject) { - return IMPORTED_CLASS + switch (object.type){ + case ImportObject.Type.MODEL: + return MODEL + case ImportObject.Type.ENUM: + return ENUM + case ImportObject.Type.EXTERNAL: + return IMPORTED_CLASS + } } if (object instanceof EnumObject) { return ENUM @@ -856,9 +871,9 @@ class FileUtils { 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")) + schemeObjects.put("Map", new ImportObject("java.util.Map", ImportObject.Type.EXTERNAL)) + schemeObjects.put("List", new ImportObject("java.util.List", ImportObject.Type.EXTERNAL)) + schemeObjects.put("DateTime", new ImportObject("org.joda.time.DateTime", ImportObject.Type.EXTERNAL)) for (final Object data : yaml.loadAll(new FileReader(schemeFile))) { if (!(data instanceof Map)) { @@ -868,7 +883,7 @@ class FileUtils { for (final Map.Entry entry : data.entrySet()) { if (entry.key.equals(ImportObject.GROUP_NAME)) { for (String importString : (Iterable) entry.value) { - final ImportObject importObject = new ImportObject(importString) + final ImportObject importObject = new ImportObject(importString, ImportObject.Type.EXTERNAL) schemeObjects.put(importObject.name, importObject) } } else if (entry.key.startsWith(EnumObject.PREFIX)) { @@ -928,9 +943,11 @@ android.applicationVariants.all { if (externalObject instanceof ImportObject) { schemeObjects.put(externalObject.name, externalObject) } else if (externalObject instanceof EnumObject) { - schemeObjects.put(externalObject.name, new ImportObject(externalObjects.key + '.' + externalObject.name)) + schemeObjects.put(externalObject.name, + new ImportObject(externalObjects.key + '.' + externalObject.name, ImportObject.Type.ENUM)) } else if (externalObject instanceof ClassObject) { - schemeObjects.put(externalObject.name, new ImportObject(externalObjects.key + '.' + externalObject.name)) + schemeObjects.put(externalObject.name, + new ImportObject(externalObjects.key + '.' + externalObject.name, ImportObject.Type.MODEL)) } } } From 37f2a663ca54a06c3b741049800d7fc17864e462 Mon Sep 17 00:00:00 2001 From: Gavriil Sitnikov Date: Wed, 10 May 2017 18:05:50 +0300 Subject: [PATCH 3/7] type arguments generation bug fixed --- gradle/jsonModelsGeneration.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/jsonModelsGeneration.gradle b/gradle/jsonModelsGeneration.gradle index 84ebfa7..7320f92 100644 --- a/gradle/jsonModelsGeneration.gradle +++ b/gradle/jsonModelsGeneration.gradle @@ -684,7 +684,7 @@ class ClassObject extends SchemeObject { throw new Exception("Duplicate field name: " + name) } - if (entry.key.equals("typeVariables")) { + if (entry.key.equals("typeArguments")) { for (String typeVariable : entry.value.replace(" ", "").split(",")) { typeVariables.add(typeVariable) } From bdd8b69c261fe8b9ceb0078cefdddddbfb7dc023 Mon Sep 17 00:00:00 2001 From: Gavriil Sitnikov Date: Wed, 10 May 2017 18:22:58 +0300 Subject: [PATCH 4/7] missable=nullable generation bug fixes --- gradle/jsonModelsGeneration.gradle | 52 +++++++++++++++++++----------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/gradle/jsonModelsGeneration.gradle b/gradle/jsonModelsGeneration.gradle index 7320f92..9f3a0d3 100644 --- a/gradle/jsonModelsGeneration.gradle +++ b/gradle/jsonModelsGeneration.gradle @@ -50,6 +50,10 @@ class Types { static final TypeName JSON_OBJECT = ClassName.bestGuess("com.bluelinelabs.logansquare.annotation.JsonObject") static final TypeName JSON_FIELD = ClassName.bestGuess("com.bluelinelabs.logansquare.annotation.JsonField") static final TypeName COLLECTIONS = ClassName.get(Collections.class) + static final TypeName COLLECTION = ClassName.get(Collection.class) + static final TypeName ARRAY_LIST = ClassName.get(ArrayList.class) + static final TypeName MAP = ClassName.get(Map.class) + static final TypeName HASH_MAP = ClassName.get(HashMap.class) 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") @@ -163,7 +167,7 @@ class EnumObject extends SchemeObject { throw new Exception("Name of enum is empty") } - for (final Map.Entry entry : jsonValues) { + for (final Entry entry : jsonValues) { final enumValue = entry.key.trim() final jsonValue = entry.value.trim() if (jsonValue.isEmpty() || enumValue.isEmpty()) { @@ -211,7 +215,7 @@ class EnumObject extends SchemeObject { .build()) .build()) - for (final Map.Entry enumValue : values) { + for (final Entry enumValue : values) { enumBuilder.addEnumConstant(enumValue.key, TypeSpec.anonymousClassBuilder(type.format, enumValue.value).build()) } @@ -292,7 +296,7 @@ enum FieldType { default: final SchemeObject object = objects.get(typeString) if (object instanceof ImportObject) { - switch (object.type){ + switch (object.type) { case ImportObject.Type.MODEL: return MODEL case ImportObject.Type.ENUM: @@ -519,7 +523,7 @@ class FieldInfo { throw new Exception("Unsupported map type of field: " + fieldName + ". Supports only Map") } } else { - typeName = nullable ? type.nonPrimitiveTypeName : type.primitiveTypeName + typeName = couldContainsNull() ? type.nonPrimitiveTypeName : type.primitiveTypeName } } else if (type != FieldType.TYPE_ARGUMENT) { typeName = TypeNameUtils.resolveTypeName(typeString, objects) @@ -529,6 +533,10 @@ class FieldInfo { } } + boolean couldContainsNull() { + return nullable || missable + } + FieldSpec generateFieldCode() { return FieldSpec.builder(typeName, name, Modifier.PRIVATE) .addAnnotation(AnnotationSpec.builder(Types.JSON_FIELD) @@ -560,7 +568,7 @@ class FieldInfo { .returns(typeName) if (!typeName.isPrimitive()) { - builder.addAnnotation(AnnotationSpec.builder(nullable ? Types.NULLABLE : Types.NON_NULL).build()) + builder.addAnnotation(AnnotationSpec.builder(couldContainsNull() ? Types.NULLABLE : Types.NON_NULL).build()) } if (type == FieldType.MAP) { @@ -577,7 +585,7 @@ class FieldInfo { MethodSpec generateSetterCode() { final ParameterSpec.Builder parameterBuilder = ParameterSpec.builder(typeName, name, Modifier.FINAL) if (!typeName.isPrimitive()) { - parameterBuilder.addAnnotation(AnnotationSpec.builder(nullable ? Types.NULLABLE : Types.NON_NULL).build()) + parameterBuilder.addAnnotation(AnnotationSpec.builder(couldContainsNull() ? Types.NULLABLE : Types.NON_NULL).build()) } final MethodSpec.Builder builder = MethodSpec.methodBuilder("set" + upperStartName(name)) @@ -585,9 +593,9 @@ class FieldInfo { .addParameter(parameterBuilder.build()) if (type == FieldType.MAP) { - builder.addStatement("this.\$L = \$T.unmodifiableMap(\$L)", name, ClassName.get(Collections.class), name) + builder.addStatement("this.\$L = new \$T(\$L)", name, Types.ARRAY_LIST, name) } else if (type == FieldType.LIST) { - builder.addStatement("this.\$L = \$T.unmodifiableList(\$L)", name, ClassName.get(Collections.class), name) + builder.addStatement("this.\$L = new \$T(\$L)", name, Types.HASH_MAP, name) } else { builder.addStatement("this.\$L = \$L", name, name) } @@ -596,7 +604,7 @@ class FieldInfo { } void generateValidationCode(MethodSpec.Builder validateMethod) { - if (!nullable) { + if (!couldContainsNull()) { validateMethod.addStatement("validateNotNull(\$L)", name) } if (!type.ableToInnerValidate) { @@ -607,9 +615,17 @@ class FieldInfo { .beginControlFlow("if (\$L instanceof \$T)", name, Types.API_MODEL) .addStatement("((\$T) \$L).validate()", Types.API_MODEL, name) .endControlFlow() + validateMethod + .beginControlFlow("if (\$L instanceof \$T)", name, Types.COLLECTION) + .addStatement("validateCollection((\$T) \$L, CollectionValidationRule.EXCEPTION_IF_ANY_INVALID)", Types.COLLECTION, name) + .endControlFlow() + validateMethod + .beginControlFlow("if (\$L instanceof \$T)", name, Types.MAP) + .addStatement("validateCollection(((\$T) \$L).values(), CollectionValidationRule.EXCEPTION_IF_ANY_INVALID)", Types.COLLECTION, name) + .endControlFlow() return } - if (nullable) { + if (couldContainsNull()) { validateMethod.beginControlFlow("if (\$L != null)", name) } if (type == FieldType.LIST) { @@ -633,7 +649,7 @@ class FieldInfo { } else { throw new Exception("Unexpected able to validate field type '" + type + "' of field " + name) } - if (nullable) { + if (couldContainsNull()) { validateMethod.endControlFlow() } } @@ -679,7 +695,7 @@ class ClassObject extends SchemeObject { void resolveFieldsInfo(final Map objects) { final Set fieldNames = new HashSet<>() - for (final Map.Entry entry : fieldsInfo.entrySet()) { + for (final Entry entry : fieldsInfo.entrySet()) { if (fieldNames.contains(entry.key)) { throw new Exception("Duplicate field name: " + name) } @@ -763,9 +779,9 @@ class ClassObject extends SchemeObject { classBuilder.addMethod(field.generateGetterCode()) classBuilder.addMethod(field.generateSetterCode()) field.generateValidationCode(validateMethod) - final String serializeMethodName = (!field.nullable && field.type.serializationMethodName != null + final String serializeMethodName = (!field.couldContainsNull() && field.type.serializationMethodName != null ? field.type.serializationMethodName : "writeObject"); - final String deserializeMethodName = (!field.nullable && field.type.deserializationMethodName != null + final String deserializeMethodName = (!field.couldContainsNull() && field.type.deserializationMethodName != null ? field.type.deserializationMethodName : "readObject"); serializeMethod.addStatement("outputStream.\$L(\$L)", serializeMethodName, field.name) if (deserializeMethodName.equals("readObject")) { @@ -776,12 +792,12 @@ class ClassObject extends SchemeObject { if (fullConstructorBuilder != null) { fullConstructorBuilder.addParameter(ParameterSpec.builder(field.typeName, field.name, Modifier.FINAL) - .addAnnotation(field.nullable ? Types.NULLABLE : Types.NON_NULL) + .addAnnotation(field.couldContainsNull() ? Types.NULLABLE : Types.NON_NULL) .build()) if (field.type == FieldType.LIST) { - fullConstructorBuilder.addStatement("this.\$L = \$T.unmodifiableList(\$L)", field.name, Types.COLLECTIONS, field.name) + fullConstructorBuilder.addStatement("this.\$L = new \$T(\$L)", field.name, Types.ARRAY_LIST, field.name) } else if (field.type == FieldType.MAP) { - fullConstructorBuilder.addStatement("this.\$L = \$T.unmodifiableMap(\$L)", field.name, Types.COLLECTIONS, field.name) + fullConstructorBuilder.addStatement("this.\$L = new \$T(\$L)", field.name, Types.HASH_MAP, field.name) } else { fullConstructorBuilder.addStatement("this.\$L = \$L", field.name, field.name) } @@ -880,7 +896,7 @@ class FileUtils { throw new Exception("Yaml file '" + schemeFile + "' is invalid") } - for (final Map.Entry entry : data.entrySet()) { + for (final Entry entry : data.entrySet()) { if (entry.key.equals(ImportObject.GROUP_NAME)) { for (String importString : (Iterable) entry.value) { final ImportObject importObject = new ImportObject(importString, ImportObject.Type.EXTERNAL) From 055cba0f7e79bb30b06e86f97abfb31a2647f419 Mon Sep 17 00:00:00 2001 From: Gavriil Sitnikov Date: Wed, 10 May 2017 18:33:19 +0300 Subject: [PATCH 5/7] map validation generator code fix --- gradle/jsonModelsGeneration.gradle | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/gradle/jsonModelsGeneration.gradle b/gradle/jsonModelsGeneration.gradle index 9f3a0d3..634d91c 100644 --- a/gradle/jsonModelsGeneration.gradle +++ b/gradle/jsonModelsGeneration.gradle @@ -571,10 +571,10 @@ class FieldInfo { builder.addAnnotation(AnnotationSpec.builder(couldContainsNull() ? Types.NULLABLE : Types.NON_NULL).build()) } - if (type == FieldType.MAP) { - builder.addStatement("return \$T.unmodifiableMap(\$L)", Types.COLLECTIONS, name) - } else if (type == FieldType.LIST) { + if (type == FieldType.LIST) { builder.addStatement("return \$T.unmodifiableList(\$L)", Types.COLLECTIONS, name) + } else if (type == FieldType.MAP) { + builder.addStatement("return \$T.unmodifiableMap(\$L)", Types.COLLECTIONS, name) } else { builder.addStatement("return \$L", name) } @@ -592,10 +592,10 @@ class FieldInfo { .addModifiers(Modifier.PUBLIC) .addParameter(parameterBuilder.build()) - if (type == FieldType.MAP) { - builder.addStatement("this.\$L = new \$T(\$L)", name, Types.ARRAY_LIST, name) - } else if (type == FieldType.LIST) { - builder.addStatement("this.\$L = new \$T(\$L)", name, Types.HASH_MAP, name) + if (type == FieldType.LIST) { + builder.addStatement("this.\$L = new \$T<>(\$L)", name, Types.ARRAY_LIST, name) + } else if (type == FieldType.MAP) { + builder.addStatement("this.\$L = new \$T<>(\$L)", name, Types.HASH_MAP, name) } else { builder.addStatement("this.\$L = \$L", name, name) } @@ -621,7 +621,7 @@ class FieldInfo { .endControlFlow() validateMethod .beginControlFlow("if (\$L instanceof \$T)", name, Types.MAP) - .addStatement("validateCollection(((\$T) \$L).values(), CollectionValidationRule.EXCEPTION_IF_ANY_INVALID)", Types.COLLECTION, name) + .addStatement("validateCollection(((\$T) \$L).values(), CollectionValidationRule.EXCEPTION_IF_ANY_INVALID)", Types.MAP, name) .endControlFlow() return } @@ -795,9 +795,9 @@ class ClassObject extends SchemeObject { .addAnnotation(field.couldContainsNull() ? Types.NULLABLE : Types.NON_NULL) .build()) if (field.type == FieldType.LIST) { - fullConstructorBuilder.addStatement("this.\$L = new \$T(\$L)", field.name, Types.ARRAY_LIST, field.name) + fullConstructorBuilder.addStatement("this.\$L = new \$T<>(\$L)", field.name, Types.ARRAY_LIST, field.name) } else if (field.type == FieldType.MAP) { - fullConstructorBuilder.addStatement("this.\$L = new \$T(\$L)", field.name, Types.HASH_MAP, field.name) + fullConstructorBuilder.addStatement("this.\$L = new \$T<>(\$L)", field.name, Types.HASH_MAP, field.name) } else { fullConstructorBuilder.addStatement("this.\$L = \$L", field.name, field.name) } From 273b56a3c8fdd6d98919ad64a1272058c9e9bdc5 Mon Sep 17 00:00:00 2001 From: Gavriil Sitnikov Date: Thu, 11 May 2017 18:15:32 +0300 Subject: [PATCH 6/7] generation of full constructor with parent added --- gradle/jsonModelsGeneration.gradle | 106 +++++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 13 deletions(-) diff --git a/gradle/jsonModelsGeneration.gradle b/gradle/jsonModelsGeneration.gradle index 634d91c..6f50a0a 100644 --- a/gradle/jsonModelsGeneration.gradle +++ b/gradle/jsonModelsGeneration.gradle @@ -102,11 +102,13 @@ class ImportObject extends SchemeObject { final String name final String fullName final Type type + final ClassObject relatedModel - ImportObject(final String value, final Type type) { + ImportObject(final String value, final Type type, final ClassObject relatedModel) { fullName = value.trim() name = fullName.substring(fullName.lastIndexOf('.') + 1) this.type = type + this.relatedModel = relatedModel } @Override @@ -682,11 +684,24 @@ class ClassObject extends SchemeObject { static final String PREFIX = "class " + private static ClassObject resolveBaseTypeModel(final String typeString, final Map objects) { + final String baseTypeString = TypeNameUtils.extractBaseTypeString(typeString) + final SchemeObject associatedObject = objects.get(baseTypeString) + if (associatedObject instanceof ClassObject) { + return associatedObject + } + if (associatedObject instanceof ImportObject && associatedObject.relatedModel != null) { + return associatedObject.relatedModel + } + return null + } + final String name final Map fieldsInfo final List fields = new ArrayList<>() - final List typeVariables = new ArrayList<>() + final List typeArguments = new ArrayList<>() TypeName superclass + ClassObject parentModel ClassObject(final String name, final Map fieldsInfo) { this.name = name.trim() @@ -702,13 +717,14 @@ class ClassObject extends SchemeObject { if (entry.key.equals("typeArguments")) { for (String typeVariable : entry.value.replace(" ", "").split(",")) { - typeVariables.add(typeVariable) + typeArguments.add(typeVariable) } continue } if (entry.key.equals("extends")) { superclass = TypeNameUtils.resolveTypeName(entry.value, objects) + parentModel = resolveBaseTypeModel(entry.value, objects) continue } @@ -728,6 +744,40 @@ class ClassObject extends SchemeObject { } } + private MethodSpec.Builder createFullConstructorWithParent(final List parameters, final List childTypeArguments) { + final MethodSpec.Builder result + if (parentModel != null) { + final List resolvedTypeArguments = new ArrayList<>() + if (superclass instanceof ParameterizedTypeName) { + for (final TypeName typeName : superclass.typeArguments) { + final int argIndex = typeArguments.indexOf(typeName.toString()) + if (argIndex >= 0) { + resolvedTypeArguments.add(childTypeArguments.get(argIndex)) + } else { + resolvedTypeArguments.add(typeName.toString()) + } + } + } + result = parentModel.createFullConstructorWithParent(parameters, resolvedTypeArguments) + } else { + result = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC) + } + for (final FieldInfo field : fields) { + final int argIndex = typeArguments.indexOf(field.typeName.toString()) + if (argIndex >= 0) { + result.addParameter(ParameterSpec.builder(ClassName.bestGuess(childTypeArguments.get(argIndex)), field.name, Modifier.FINAL) + .addAnnotation(field.couldContainsNull() ? Types.NULLABLE : Types.NON_NULL) + .build()) + } else { + result.addParameter(ParameterSpec.builder(field.typeName, field.name, Modifier.FINAL) + .addAnnotation(field.couldContainsNull() ? Types.NULLABLE : Types.NON_NULL) + .build()) + } + parameters.add(field.name) + } + return result + } + @Override void writeToFile(final File directory, final Map objects, final String packageName) { final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(name).addModifiers(Modifier.PUBLIC) @@ -735,7 +785,7 @@ class ClassObject extends SchemeObject { .superclass(superclass != null ? superclass : Types.LOGAN_SQUARE_JSON_MODEL) // adds type variables - for (String typeVariable : typeVariables) { + for (String typeVariable : typeArguments) { classBuilder.addTypeVariable(TypeVariableName.get(typeVariable)) } @@ -744,9 +794,22 @@ class ClassObject extends SchemeObject { // 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 + final MethodSpec.Builder fullConstructorBuilder + if (superclass == null) { + fullConstructorBuilder = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC).addStatement("super()") + } else if (parentModel != null) { + final List parameters = new ArrayList<>() + final List typeArguments = new ArrayList<>() + if (superclass instanceof ParameterizedTypeName) { + for (final TypeName typeName : superclass.typeArguments) { + typeArguments.add(typeName.toString()) + } + } + fullConstructorBuilder = parentModel.createFullConstructorWithParent(parameters, typeArguments) + fullConstructorBuilder.addStatement("super(\$L)", parameters.join(", ")) + } else { + fullConstructorBuilder = null + } // creates validate() method final MethodSpec.Builder validateMethod = MethodSpec.methodBuilder("validate").addModifiers(Modifier.PUBLIC) @@ -887,9 +950,9 @@ class FileUtils { final Yaml yaml = new Yaml() final Map schemeObjects = new HashMap<>() - schemeObjects.put("Map", new ImportObject("java.util.Map", ImportObject.Type.EXTERNAL)) - schemeObjects.put("List", new ImportObject("java.util.List", ImportObject.Type.EXTERNAL)) - schemeObjects.put("DateTime", new ImportObject("org.joda.time.DateTime", ImportObject.Type.EXTERNAL)) + schemeObjects.put("Map", new ImportObject("java.util.Map", ImportObject.Type.EXTERNAL, null)) + schemeObjects.put("List", new ImportObject("java.util.List", ImportObject.Type.EXTERNAL, null)) + schemeObjects.put("DateTime", new ImportObject("org.joda.time.DateTime", ImportObject.Type.EXTERNAL, null)) for (final Object data : yaml.loadAll(new FileReader(schemeFile))) { if (!(data instanceof Map)) { @@ -899,14 +962,23 @@ class FileUtils { for (final Entry entry : data.entrySet()) { if (entry.key.equals(ImportObject.GROUP_NAME)) { for (String importString : (Iterable) entry.value) { - final ImportObject importObject = new ImportObject(importString, ImportObject.Type.EXTERNAL) + final ImportObject importObject = new ImportObject(importString, ImportObject.Type.EXTERNAL, null) + if (schemeObjects.containsKey(importObject.name)) { + throw new Exception("Duplicate import object with name '" + importObject.name + "' in file " + schemeFile.getPath()) + } schemeObjects.put(importObject.name, importObject) } } else if (entry.key.startsWith(EnumObject.PREFIX)) { final EnumObject enumObject = new EnumObject(entry.key.substring(EnumObject.PREFIX.length()), entry.value) + if (schemeObjects.containsKey(enumObject.name)) { + throw new Exception("Duplicate enum object with name '" + enumObject.name + "' in file " + schemeFile.getPath()) + } schemeObjects.put(enumObject.name, enumObject) } else if (entry.key.startsWith(ClassObject.PREFIX)) { final ClassObject classObject = new ClassObject(entry.key.substring(ClassObject.PREFIX.length()), entry.value) + if (schemeObjects.containsKey(classObject.name)) { + throw new Exception("Duplicate class object with name '" + classObject.name + "' in file " + schemeFile.getPath()) + } schemeObjects.put(classObject.name, classObject) } else { throw new Exception("Unexpected scheme object: " + entry.key) @@ -956,14 +1028,19 @@ android.applicationVariants.all { continue } for (final SchemeObject externalObject : externalObjects.value.values()) { + if (schemeObjects.containsKey(externalObject.name)) { + if (!(externalObject instanceof ImportObject) || externalObject.type != ImportObject.Type.EXTERNAL) { + throw new Exception("Duplicate model name '" + externalObject.name + "' for package " + packageName) + } + } if (externalObject instanceof ImportObject) { schemeObjects.put(externalObject.name, externalObject) } else if (externalObject instanceof EnumObject) { schemeObjects.put(externalObject.name, - new ImportObject(externalObjects.key + '.' + externalObject.name, ImportObject.Type.ENUM)) + new ImportObject(externalObjects.key + '.' + externalObject.name, ImportObject.Type.ENUM, null)) } else if (externalObject instanceof ClassObject) { schemeObjects.put(externalObject.name, - new ImportObject(externalObjects.key + '.' + externalObject.name, ImportObject.Type.MODEL)) + new ImportObject(externalObjects.key + '.' + externalObject.name, ImportObject.Type.MODEL, externalObject)) } } } @@ -976,6 +1053,9 @@ android.applicationVariants.all { throw new Exception("Error on parsing class '" + schemeObject.name + "' : " + exception.getMessage()) } } + } + + for (final SchemeObject schemeObject : schemeObjects.values()) { try { schemeObject.writeToFile(generatedModelsDirectory, schemeObjects, packageName) } catch (final Exception exception) { From a48ea057f7c6510de47ce1012f2fca6e1ea62c96 Mon Sep 17 00:00:00 2001 From: Gavriil Sitnikov Date: Thu, 11 May 2017 19:02:56 +0300 Subject: [PATCH 7/7] copy method generation added --- gradle/jsonModelsGeneration.gradle | 52 ++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/gradle/jsonModelsGeneration.gradle b/gradle/jsonModelsGeneration.gradle index 6f50a0a..bac0bcf 100644 --- a/gradle/jsonModelsGeneration.gradle +++ b/gradle/jsonModelsGeneration.gradle @@ -37,7 +37,7 @@ import javax.lang.model.element.Modifier import java.util.Map.Entry -//TODO: missable in future +//TODO: optional in future //TODO: NUMBER/BOOLEAN enums in future //TODO: maybe save md5-hashes to check if files/scheme changed @@ -442,7 +442,7 @@ class TypeNameUtils { * - jsonName - field name association with JSON parameter name. By default equals 'name' property; * - type - type of field; * - nullable - 'nullable' flag, true if field could contains null and associated JSON value could be null; - * - missable - 'missable' flag, true if JSON parameter associated with field could be missed in JSON object; + * - optional - 'optional' flag, true if JSON parameter associated with field could be missed in JSON object; * - nonEmptyCollection - 'non-empty' flag, true if JSON parameter could contains collection and that collection souldn't be empty; * - solidCollection - 'solid' flag, true if JSON parameter could contains collection and that collection can't contains any invalid element. */ @@ -467,7 +467,7 @@ class FieldInfo { final String name final String jsonName boolean nullable - boolean missable + boolean optional boolean nonEmptyCollection boolean solidCollection final FieldType type @@ -491,8 +491,8 @@ class FieldInfo { case "nullable": nullable = true break - case "missable": - missable = true + case "optional": + optional = true break case "non-empty": nonEmptyCollection = true @@ -536,7 +536,7 @@ class FieldInfo { } boolean couldContainsNull() { - return nullable || missable + return nullable || optional } FieldSpec generateFieldCode() { @@ -784,11 +784,16 @@ class ClassObject extends SchemeObject { .addAnnotation(AnnotationSpec.builder(Types.JSON_OBJECT).addMember("serializeNullObjects", "true").build()) .superclass(superclass != null ? superclass : Types.LOGAN_SQUARE_JSON_MODEL) + final TypeName[] arguments = new TypeName[typeArguments.size()] + int index = 0 // adds type variables - for (String typeVariable : typeArguments) { + for (final String typeVariable : typeArguments) { classBuilder.addTypeVariable(TypeVariableName.get(typeVariable)) + arguments[index++] = TypeVariableName.get(typeVariable) } + final TypeName thisTypeName = arguments.length > 0 ? ParameterizedTypeName.get(ClassName.bestGuess(name), arguments) : ClassName.bestGuess(name) + // adds default constructor classBuilder.addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC).addStatement("super()").build()) @@ -811,6 +816,13 @@ class ClassObject extends SchemeObject { fullConstructorBuilder = null } + // creates copy logic method + final MethodSpec.Builder copyToMethod = MethodSpec.methodBuilder("copyTo").addModifiers(Modifier.PROTECTED) + .addParameter(ParameterSpec.builder(thisTypeName, "destination", Modifier.FINAL).addAnnotation(Types.NON_NULL).build()) + if (parentModel != null) { + copyToMethod.addStatement("super.copyTo(destination)") + } + // creates validate() method final MethodSpec.Builder validateMethod = MethodSpec.methodBuilder("validate").addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class) @@ -857,13 +869,22 @@ class ClassObject extends SchemeObject { fullConstructorBuilder.addParameter(ParameterSpec.builder(field.typeName, field.name, Modifier.FINAL) .addAnnotation(field.couldContainsNull() ? Types.NULLABLE : Types.NON_NULL) .build()) - if (field.type == FieldType.LIST) { + } + if (field.type == FieldType.LIST) { + if (fullConstructorBuilder != null) { fullConstructorBuilder.addStatement("this.\$L = new \$T<>(\$L)", field.name, Types.ARRAY_LIST, field.name) - } else if (field.type == FieldType.MAP) { + } + copyToMethod.addStatement("destination.\$L = new \$T<>(\$L)", field.name, Types.ARRAY_LIST, field.name) + } else if (field.type == FieldType.MAP) { + if (fullConstructorBuilder != null) { fullConstructorBuilder.addStatement("this.\$L = new \$T<>(\$L)", field.name, Types.HASH_MAP, field.name) - } else { + } + copyToMethod.addStatement("destination.\$L = new \$T<>(\$L)", field.name, Types.HASH_MAP, field.name) + } else { + if (fullConstructorBuilder != null) { fullConstructorBuilder.addStatement("this.\$L = \$L", field.name, field.name) } + copyToMethod.addStatement("destination.\$L = \$L", field.name, field.name) } if (first) { @@ -894,6 +915,17 @@ class ClassObject extends SchemeObject { // creates validate() method classBuilder.addMethod(validateMethod.build()) + // creates copyTo() method + classBuilder.addMethod(copyToMethod.build()) + // creates copy() method + classBuilder.addMethod(MethodSpec.methodBuilder("copy").addModifiers(Modifier.PUBLIC) + .returns(thisTypeName) + .addAnnotation(Types.NON_NULL) + .addStatement("final \$T result = new \$T()", thisTypeName, thisTypeName) + .addStatement("this.copyTo(result)") + .addStatement("return result") + .addJavadoc("Beware! It is not copying objects stored in fields.") + .build()) // adds equals() method classBuilder.addMethod(MethodSpec.methodBuilder("equals").addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class)