From 64424e91972838b84217da0878a2ecac074c76a7 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 21 Aug 2018 23:40:19 +0300 Subject: [PATCH 1/3] Removed necessary "libraries" nesting, removed old json generator --- documentation/JSON Models Generation.md | 124 --- gradle/commonStaticAnalysis.gradle | 10 +- gradle/jsonModelsGeneration.gradle | 1141 ----------------------- gradle/staticAnalysis.gradle | 4 +- 4 files changed, 6 insertions(+), 1273 deletions(-) delete mode 100644 documentation/JSON Models Generation.md delete mode 100644 gradle/jsonModelsGeneration.gradle diff --git a/documentation/JSON Models Generation.md b/documentation/JSON Models Generation.md deleted file mode 100644 index 6e56893..0000000 --- a/documentation/JSON Models Generation.md +++ /dev/null @@ -1,124 +0,0 @@ -## Описание генератора JSON моделей - -Генератор JSON моделей - это скрипт, который автоматически создает классы JSON моделей на основе описания их схемы в файле формата YAML. - -Генерируется 2 типа классов: -- классы, описывающие enum; -- классы, описывающие модели. - -Классы моделей размечаются для работы с библиотекой LoganSquare и наследуется от абстракного класса `ApiModel`, содержащего логику валидации. - -Классы моделей содержат в себе: -- конструктор без параметров; -- конструктор со всеми возможными параметрами в случае, если модель не наследуется ни от какой другой модели; -- приватные поля, описывающие параметры модели; -- публичный `getter` и `setter` для каждого поля модели; -- метод `validate` для валидации модели; -- метод `equals` и `hashCode` на основе всех содержащихся полей; -- метод `writeObject` и `readObject` для стандартной сериализации объекта; -- метод `copy` для создания копии объекта. Создается только в случае, если модель не наследуется ни от какой другой модели. - -**ВАЖНО!** Копирование через метод `copy` не гарантирует копирование всех объектов, содержащихся во всех полях. - -## Подключение и использование генератора - -Подключаем сабмодуль [BuildScripts](https://github.com/TouchInstinct/BuildScripts) в папку libraries. - -Чтобы подключить скрипт, добавьте в конец файл `build.gradle` модуля следующую строку: -```gradle - apply from: "${rootDir}/libraries/BuildScripts/gradle/jsonModelsGeneration.gradle" -``` - -Чтобы зарегистрировать YAML файлы для генерации моделей, в секцию `android` надо добавить: -```gradle -android { - extensions.jsonModelsMapping = ["schemes/api_models.yaml -> ${defaulConfig.applicationId}.logic.api.model", - "schemes/internal_models.yaml -> ${defaulConfig.applicationId}.logic.model"] - } -``` -- `schemes/api_models.yaml` - путь к файлу схемы. По умолчанию относительно папки модуля, но можно указать и абсолютный путь; -- `->` - разделитель; -- `${applicationId}.logic.api.model` - package-name моделей, которые будут сгенерированы на основе этого файла. - -## Структура файла YAML - -YAML файл может содержать 3 типа объектов: описание модели, описание enum, импортированные классы. - -### Описание модели -Описание начинается с `class MyModel:`, где `MyModel` - это имя класса, который будет сгенерирован. - -Затем для класса идет описание параметров класса: -- параметр `typeArguments: TResponse, TData` добавляет к классу указанные аргументы: `class MyModel`; -- параметр `extends: BaseResponse` наследует класс от указанного класса: `class MyModel extends BaseResponse`; -- параметры, описывающие поля класса, например `name: string` добавляет поле типа `String` с названием `name`. - -### Описание параметра поля класса -В простом виде поле добавляется, как `name: string`. То есть, указывается имя поля и его тип. - -Вообще, поле может быть описано следующими параметрами: -1. Название параметра - это имя поля; -2. `jsonName`, необязательное - имя параметра в разметке JSON, соответсвующее полю. По умолчанию берется ; -3. `type`, обязательное - тип поля. Разрешенные типы: - - строки `string`, `String`; - - целые числа `int`, `Integer`, `long`, `Long`; - - дробные числа `float`, `Float`, `double`, `Double`; - - буленовские значения `boolean`, `Boolean`; - - дата/время `date`, `datetime`, `DateTime`; - - списки `List<*>`; - - мапы `Map; - - описанные в этом же файле enum'ы; - - описанные в этом же файле импортированные классы; -4. `flags`, необязательное - флаги для валидации объекта. Перечисляются через запятую, могут быть: - - `nullable` - в поле может прийти null. По умолчанию все поля считаются `non-null`; - - `missable` - JSON может не содержать такого поля. По умолчанию все поля считаются `non-missable`; - - `solid` - если поле содержит коллекцию, то все элементы в ней обязаны быть валидными. По умолчанию не валидные элементы исключаются из коллекции на этапе валидации; - - `non-empty` - если поле содержит коллекцию, то она не должна быть пустой. По умолчанию пустые коллекции разрешены. - -### Пример описания моделей: -```yaml -class SimpleModel: - name: string - age: int - birthDate: date - -class ComplexModel: - id: - type: long - jsonName: object_id - usersList: - type: List - flags: solid, non-empty - hasSomeInfo: - type: boolean - jsonName: has_info - flags: nullable -``` - -### Описание enum'a -Описание начинается с `enum MyEnum:`, где `MyEnum` - это имя enum'a, который будет сгенерирован. - -Затем идет перечисление всех возможных значений в формате `VALUE: json_value`, где `VALUE` - имя значения в enum'e, а `json_value` - соответствующая этому значению строка в JSON. - -Пример: -```yaml -enum SimpleEnum: - MALE: male_gender - FEMALE: female_gender -``` - -### Описание импортированных классов -Импортированные классы добавляются списком в разделе `imports`. Все классы должны быть указаны полным именем. - -Пример: -```yaml -imports: - - android.util.Pair - - com.myproject.model.UserExtension - -class MyModel: - pair: Pair - users: List -``` -Импортированные классы нужно использовать в двух случаях: -1. Для них заргеистрированы конвертеры в LoganSquare; -2. Это расширенные или кастомные модели, описанные для LoganSquare вручную. diff --git a/gradle/commonStaticAnalysis.gradle b/gradle/commonStaticAnalysis.gradle index 9c104af..9128d56 100644 --- a/gradle/commonStaticAnalysis.gradle +++ b/gradle/commonStaticAnalysis.gradle @@ -257,7 +257,7 @@ getPmdTask = { sources -> def taskName = "pmd_${project.name}" tasks.create(taskName, Pmd) { pmdClasspath = configurations.pmd.asFileTree - ruleSetFiles = files("${rootDir}/libraries/BuildScripts/pmd/rulesets/java/android.xml") + ruleSetFiles = files "$rootDir/BuildScripts/pmd/rulesets/java/android.xml" ruleSets = [] source files(sources) ignoreFailures = true @@ -294,9 +294,9 @@ getLintTasks = { android.lintOptions.checkAllWarnings = true android.lintOptions.warningsAsErrors = false android.lintOptions.xmlReport = true - android.lintOptions.xmlOutput = file("${project.buildDir}/reports/lint_report.xml") + android.lintOptions.xmlOutput = file "$project.buildDir/reports/lint_report.xml" android.lintOptions.htmlReport = false - android.lintOptions.lintConfig = file("${rootDir}/libraries/BuildScripts/lint/lint.xml") + android.lintOptions.lintConfig = file "$rootDir/BuildScripts/lint/lint.xml" return lintTaskNames } @@ -309,7 +309,7 @@ getCheckstyleTask = { sources -> ignoreFailures = true showViolations = false source files(sources) - configFile file("${rootDir}/libraries/BuildScripts/checkstyle/configuration/touchin_checkstyle.xml") + configFile file("$rootDir/BuildScripts/checkstyle/configuration/touchin_checkstyle.xml") checkstyleClasspath = configurations.checkstyle.asFileTree classpath = files(System.getenv("ANDROID_HOME") + "/platforms/" + android.compileSdkVersion + "/android.jar") + files(System.properties.'java.home' + "/lib/rt.jar") + @@ -332,7 +332,7 @@ getKotlinDetektTask = { isAndroidProject -> def input = "${rootDir}" def output = "${project.buildDir}/reports" def outputName = "kotlin-detekt" - def config = "${rootDir}/libraries/BuildScripts/kotlin/detekt-config.yml" + def config = "$rootDir/BuildScripts/kotlin/detekt-config.yml" // TODO add excludes from rootProject.extensions.findByName("staticAnalysisExcludes") def filters = ".*src/test.*,.*/resources/.*,.*/tmp/.*" diff --git a/gradle/jsonModelsGeneration.gradle b/gradle/jsonModelsGeneration.gradle deleted file mode 100644 index 0609bb1..0000000 --- a/gradle/jsonModelsGeneration.gradle +++ /dev/null @@ -1,1141 +0,0 @@ -/* - * Copyright (c) 2017 Touch Instinct - * - * This file is part of RoboSwag library. - * - * 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. - * - */ - -apply plugin: 'com.android.application' - -buildscript { - repositories { - jcenter() - } - dependencies { - classpath 'org.yaml:snakeyaml:1.8' - classpath 'com.squareup:javapoet:1.8.0' - } -} - -import com.squareup.javapoet.* -import org.yaml.snakeyaml.Yaml - -import javax.lang.model.element.Modifier -import java.util.Map.Entry - - -//TODO: optional in future -//TODO: NUMBER/BOOLEAN enums in future -//TODO: maybe save md5-hashes to check if files/scheme changed - -class Types { - - static final TypeName LOGAN_SQUARE_ENUM = ClassName.bestGuess("ru.touchin.templates.logansquare.LoganSquareEnum") - static final TypeName LOGAN_SQUARE_ENUM_CONVERTER = ClassName.bestGuess("ru.touchin.templates.logansquare.LoganSquareEnumConverter") - static final TypeName NULLABLE = ClassName.bestGuess("android.support.annotation.Nullable") - static final TypeName NON_NULL = ClassName.bestGuess("android.support.annotation.NonNull") - 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") - static final TypeName OBJECT_OUTPUT_STREAM = ClassName.bestGuess("java.io.ObjectOutputStream") - static final TypeName OBJECT_INPUT_STREAM = ClassName.bestGuess("java.io.ObjectInputStream") - -} - -class Pair { - - public final TKey key; - public final TValue value; - - public Pair(final TKey key, final TValue value) { - this.key = key - this.value = value - } - -} - -/** - * Abstract object of scheme for generation. Includes: - * - models of objects (classes); - * - enums; - * - imports to resolve external types usages. - */ -abstract class SchemeObject { - - /** - * Override to write scheme object code into *.java file. - * @param directory Directory to store *.java file; - * @param objects Other objects to resolve types etc.; - * @param packageName Package name of models/enums classes. - */ - abstract void writeToFile(File directory, Map objects, String packageName) - -} - -/** - * Object that's describing import of external type which is not generating by generator. - * - * Registerer it in scheme file like: - * - * imports: - * - android.support.v4.util.Pair - * - java.util.Date - */ -class ImportObject extends SchemeObject { - - enum Type { - MODEL, - ENUM, - EXTERNAL - } - - static final String GROUP_NAME = "imports" - - final String name - final String fullName - final Type type - final ClassObject relatedModel - - 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 - void writeToFile(final File directory, final Map objects, final String packageName) { - //do nothing - imports are only to resolve external types in other models - } - -} - -/** - * Object that's describing enum model. Where enum values are associating with values storing in JSON. - * Associating JSON values could be of types: string, boolean and int/long (numbers). - * - * Registerer it in scheme file like: - * - * enum MyEnum: - * VALUE_ONE: value_one - * VALUE_TWO: value_two - */ -class EnumObject extends SchemeObject { - - enum Type { - STRING("\$S"), - NUMBER("\$LL"), - BOOLEAN("\$L") - - final String format - - Type(final String format) { - this.format = format - } - } - - static final String PREFIX = "enum " - - static Type typeOf(final String jsonValue) { - if (jsonValue.equals("true") || jsonValue.equals("false")) { - //TODO: BOOLEAN in future - return Type.STRING - } - try { - Integer.parseInt(jsonValue) - //TODO: NUMBER in future - return Type.STRING - } catch (final NumberFormatException ignored) { - return Type.STRING - } - } - - final String name - Type type - - final Map values = new HashMap<>() - - EnumObject(final String enumName, final Map jsonValues) { - this.name = enumName.trim() - if (this.name.isEmpty()) { - throw new Exception("Name of enum is empty") - } - - for (final Entry entry : jsonValues) { - final enumValue = entry.key.trim() - final jsonValue = entry.value.trim() - if (jsonValue.isEmpty() || enumValue.isEmpty()) { - throw new Exception("Value of enum is empty") - } - if (this.values.containsKey(enumValue)) { - throw new Exception("Value '" + enumValue + "' already registered into enum") - } - final Type type = typeOf(jsonValue) - if (this.type == null) { - this.type = type - } else if (this.type != type) { - throw new Exception("Type of value '" + jsonValues + "' conflicts with previous value type: " + this.type) - } - this.values.put(enumValue, jsonValue) - } - } - - @Override - void writeToFile(final File directory, final Map objects, final String packageName) { - final TypeSpec.Builder enumBuilder = TypeSpec.enumBuilder(name).addModifiers(Modifier.PUBLIC) - .addSuperinterface(Types.LOGAN_SQUARE_ENUM) - - .addField(FieldSpec.builder(ClassName.get(String.class), "valueName", Modifier.PRIVATE, Modifier.FINAL) - .addAnnotation(Types.NON_NULL) - .build()) - - .addMethod(MethodSpec.constructorBuilder() - .addParameter(ClassName.get(String.class), "valueName", Modifier.FINAL) - .addStatement("this.valueName = valueName") - .build()) - - .addMethod(MethodSpec.methodBuilder("getValueName") - .returns(ClassName.get(String.class)) - .addModifiers(Modifier.PUBLIC) - .addAnnotation(ClassName.get(Override.class)) - .addAnnotation(Types.NON_NULL) - .addStatement("return valueName") - .build()) - - .addType(TypeSpec.classBuilder("LoganSquareConverter").addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .superclass(ParameterizedTypeName.get(Types.LOGAN_SQUARE_ENUM_CONVERTER, ClassName.bestGuess(name))) - .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC) - .addStatement("super(values())") - .build()) - .build()) - - for (final Entry enumValue : values) { - enumBuilder.addEnumConstant(enumValue.key, TypeSpec.anonymousClassBuilder(type.format, enumValue.value).build()) - } - - JavaFile.builder(packageName, enumBuilder.build()) - .indent(" ") - .build() - .writeTo(directory) - } - -} - -/** - * Type of field of class. - */ -enum FieldType { - - 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), - ENUM(false), - MODEL(true), - IMPORTED_CLASS(true), - TYPE_ARGUMENT(true) - - // primitive type for using if use in Non-Null case - final TypeName primitiveTypeName - 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, null, null) - } - - FieldType(final TypeName typeName, final boolean ableToInnerValidate) { - this(typeName, typeName, ableToInnerValidate, null, null) - } - - 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 - } - - /** - * Returning type of field by string of type. - * @param typeString String to get type from; - * @param objects Map of registered objects to check if such type is object from that map; - * @return Typeof field. - */ - static FieldType get(final String typeString, final Map objects) { - switch (typeString) { - case "string": case "String": return STRING - case "List": case "Collection": case "LinkedList": case "ArrayList": return LIST - case "Map": case "HashMap": case "TreeMap": case "LinkedHashMap": return MAP - case "int": case "Integer": return INT - case "boolean": case "Boolean": return BOOLEAN - case "long": case "Long": return LONG - case "float": case "Float": return FLOAT - case "double": case "Double": return DOUBLE - case "date": case "datetime": case "DateTime": return DATE_TIME - default: - final SchemeObject object = objects.get(typeString) - if (object instanceof ImportObject) { - 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 - } - if (object instanceof ClassObject) { - return MODEL - } - return TYPE_ARGUMENT - } - } - -} - -class TypeNameUtils { - - /** - * Returns type arguments part from full string that represents type. - * Sample: 'List' -> '' or 'Map' -> ''. - * @param fullTypeString Full string represents type. E.g. 'Map'; - * @return Type arguments part of string. Like '' - */ - private static String extractTypeArgumentsString(final String fullTypeString) { - final int startOfTypeArguments = fullTypeString.indexOf("<"); - return startOfTypeArguments > 0 ? fullTypeString.substring(startOfTypeArguments).replace(" ", "") : null - } - - /** - * Returns base type name without package and type arguments. - * Sample: 'java.lang.List' -> 'List' - * @param fullTypeString Full type name string; - * @return Base type name. - */ - static String extractBaseTypeString(final String fullTypeString) { - String result = fullTypeString.replace(" ", "") - if (result.indexOf('<') > 0) { - result = result.substring(0, result.indexOf('<')) - } - if (result.indexOf('.') > 0) { - result = result.substring(result.lastIndexOf('.') + 1) - } - return result - } - - /** - * Resolving type name of base type from string. - * Sample: 'List' -> 'java.util.List' typeName. - * @param typeString String represents type; - * @param objects Objects to resolve some external imported classes; - * @return Type name of base type. - */ - private static TypeName resolveBaseTypeName(final String typeString, final Map objects) { - final String baseTypeString = extractBaseTypeString(typeString) - final SchemeObject associatedObject = objects.get(baseTypeString) - if (associatedObject instanceof ImportObject) { - return ClassName.bestGuess(associatedObject.fullName) - } - return ClassName.bestGuess(baseTypeString) - } - - private static Pair getTypeNameWithArguments(final TypeName parentTypeName, String typeArgumentsString, - final Map objects) { - final List typeArguments = new ArrayList<>() - while (!typeArgumentsString.isEmpty()) { - final int nextComma = typeArgumentsString.indexOf(',') - final int nextLeft = typeArgumentsString.indexOf('<') - final int nextRight = typeArgumentsString.indexOf('>') - if (nextLeft == 0) { - throw new Exception("Unexpected symbol '<'") - } - if (nextRight == -1) { - throw new Exception("Missed symbol '>'") - } - if (nextRight == 0) { - throw new Exception("Argument missed") - } - - if (nextComma > 0 && nextComma < nextRight && (nextLeft == -1 || nextComma < nextLeft)) { - // when there are several types divided by comma and current have no arguments - // Samples: String, Integer>; String, List> - typeArguments.add(resolveBaseTypeName(typeArgumentsString.substring(0, nextComma), objects)) - typeArgumentsString = typeArgumentsString.substring(nextComma + 1) - continue - } - if (nextLeft == -1 || nextRight < nextLeft) { - // when there it is last argument in scope - // Samples: String>; String>, Integer>; String>> - typeArguments.add(resolveBaseTypeName(typeArgumentsString.substring(0, nextRight), objects)) - // stop parsing if there is last argument - typeArgumentsString = nextRight < typeArgumentsString.length() - 1 ? typeArgumentsString.substring(nextRight + 1) : "" - break - } - // when it is element with type arguments - // Sample: List> - final TypeName baseTypeName = resolveBaseTypeName(typeArgumentsString.substring(0, nextLeft), objects) - if (typeArgumentsString.length() < nextLeft + 2) { - throw new Exception("No data after '<'") - } - typeArgumentsString = typeArgumentsString.substring(nextLeft + 1) - final Pair innerArgs = getTypeNameWithArguments(baseTypeName, typeArgumentsString, objects) - typeArgumentsString = innerArgs.key.substring(1) - typeArguments.add(innerArgs.value) - } - return new Pair(typeArgumentsString, ParameterizedTypeName.get(parentTypeName, (TypeName[]) typeArguments.toArray())) - } - - /** - * Resolves TypeName from raw type string. - * @param typeString String describes type. E.g. 'java.lang.List' - * @param objects Map of registered objects. - * @return Resolved TypeName. - */ - static TypeName resolveTypeName(final String typeString, final Map objects) { - final TypeName baseTypeName = resolveBaseTypeName(typeString, objects) - final String typeArgumentsString = extractTypeArgumentsString(typeString) - if (typeArgumentsString == null) { - return baseTypeName - } - - try { - final Pair result = getTypeNameWithArguments(baseTypeName, typeArgumentsString.substring(1), objects) - if (!result.key.isEmpty()) { - throw new Exception("Useless symbols '" + result.key + "'") - } - return result.value - } catch (final Exception exception) { - throw new Exception("Error resolving type of '" + typeString + "' : " + exception.getMessage()) - } - } -} - -/** - * Represents info about field of JSON model. - * - name - is field actual name; - * - 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; - * - 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. - */ -class FieldInfo { - - private static final String DEFAULT_GETTER_PREFIX = "get" - - private static upperStartName(final String name) { - if (name.isEmpty()) { - throw new Exception("Empty name of field") - } - if (name.length() == 1) { - return name.charAt(0).toUpperCase() - } - return name.charAt(0).toUpperCase().toString() + name.substring(1) - } - - private static boolean checkNameStartsWithPrefix(final String name, final String prefix) { - return name.length() > prefix.length() && name.startsWith(prefix) && name.charAt(prefix.length()).isUpperCase() - } - - final String name - final String jsonName - boolean nullable - boolean optional - boolean nonEmptyCollection - boolean solidCollection - final FieldType type - final TypeName typeName - - FieldInfo(final String fieldName, final Map parameters, final Map objects) { - this.name = fieldName.trim() - if (name.isEmpty()) { - throw new Exception("name is empty") - } - jsonName = parameters.containsKey("jsonName") ? parameters.get("jsonName") : fieldName - if (jsonName.isEmpty()) { - throw new Exception("jsonName is empty") - } - - final String flagsString = parameters.get("flags") - if (flagsString != null) { - final List flags = Arrays.asList(flagsString.replace(" ", "").split(",")) - for (final String flag : flags) { - switch (flag) { - case "nullable": - nullable = true - break - case "optional": - optional = true - break - case "non-empty": - nonEmptyCollection = true - break - case "solid": - solidCollection = true - break - default: throw new Exception("Unexpected flag: " + flag) - } - } - } - - if (parameters.get("type") == null) { - throw new Exception("Missed type") - } - final String typeString = parameters.get("type").trim() - if (typeString.isEmpty()) { - throw new Exception("Empty type") - } - type = FieldType.get(TypeNameUtils.extractBaseTypeString(typeString), objects) - if (type.nonPrimitiveTypeName != null) { - if (type == FieldType.LIST) { - typeName = TypeNameUtils.resolveTypeName(typeString, objects) - if (!typeName.toString().startsWith("java.util.List")) { - throw new Exception("Unsupported list type '" + typeName.toString() + "' of field: " + fieldName + ". Supports only List<*>") - } - } else if (type == FieldType.MAP) { - typeName = TypeNameUtils.resolveTypeName(typeString, objects) - if (!typeName.toString().startsWith("java.util.Map") - } - } else { - typeName = couldContainsNull() ? type.nonPrimitiveTypeName : type.primitiveTypeName - } - } else if (type != FieldType.TYPE_ARGUMENT) { - typeName = TypeNameUtils.resolveTypeName(typeString, objects) - } else { - // generic - typeName = ClassName.bestGuess(typeString) - } - } - - boolean couldContainsNull() { - return nullable || optional - } - - FieldSpec generateFieldCode() { - return FieldSpec.builder(typeName, name, Modifier.PRIVATE) - .addAnnotation(AnnotationSpec.builder(Types.JSON_FIELD) - .addMember("name", "\$S", jsonName) - .build()) - .build() - } - - private String getGetterPrefix() { - if (type != FieldType.BOOLEAN) { - return DEFAULT_GETTER_PREFIX - } - if (checkNameStartsWithPrefix(name, "is")) { - return "is" - } else if (checkNameStartsWithPrefix(name, "has")) { - return "has" - } else if (checkNameStartsWithPrefix(name, "have")) { - return "have" - } - return DEFAULT_GETTER_PREFIX - } - - MethodSpec generateGetterCode() { - final String getterPrefix = getGetterPrefix() - final MethodSpec.Builder builder = MethodSpec.methodBuilder(getterPrefix.equals(DEFAULT_GETTER_PREFIX) - ? getterPrefix + upperStartName(name) - : getterPrefix + upperStartName(name.substring(getterPrefix.length()))) - .addModifiers(Modifier.PUBLIC) - .returns(typeName) - - if (!typeName.isPrimitive()) { - builder.addAnnotation(AnnotationSpec.builder(couldContainsNull() ? Types.NULLABLE : Types.NON_NULL).build()) - } - - if (type == FieldType.LIST) { - if (couldContainsNull()) { - builder.addStatement("return \$L != null ? \$T.unmodifiableList(\$L) : null", name, Types.COLLECTIONS, name) - } else { - builder.addStatement("return \$T.unmodifiableList(\$L)", Types.COLLECTIONS, name) - } - } else if (type == FieldType.MAP) { - if (couldContainsNull()) { - builder.addStatement("return \$L != null ? \\$T.unmodifiableMap(\\$L) : null", name, Types.COLLECTIONS, name) - } else { - builder.addStatement("return \$T.unmodifiableMap(\$L)", name, Types.COLLECTIONS, name) - } - } else { - builder.addStatement("return \$L", name) - } - - return builder.build() - } - - MethodSpec generateSetterCode() { - final ParameterSpec.Builder parameterBuilder = ParameterSpec.builder(typeName, name, Modifier.FINAL) - if (!typeName.isPrimitive()) { - parameterBuilder.addAnnotation(AnnotationSpec.builder(couldContainsNull() ? Types.NULLABLE : Types.NON_NULL).build()) - } - - final MethodSpec.Builder builder = MethodSpec.methodBuilder("set" + upperStartName(name)) - .addModifiers(Modifier.PUBLIC) - .addParameter(parameterBuilder.build()) - - if (type == FieldType.LIST) { - if (couldContainsNull()) { - builder.addStatement("this.\$L = \$L !=null ? new \$T<>(\$L) : null", name, name, Types.ARRAY_LIST, name) - } else { - builder.addStatement("this.\$L = new \$T<>(\$L)", name, Types.ARRAY_LIST, name) - } - } else if (type == FieldType.MAP) { - if (couldContainsNull()) { - builder.addStatement("this.\$L = \$L !=null ? new \$T<>(\$L) : null", name, name, Types.HASH_MAP, name) - } else { - builder.addStatement("this.\$L = new \$T<>(\$L)", name, name, Types.HASH_MAP, name) - } - } else { - builder.addStatement("this.\$L = \$L", name, name) - } - - return builder.build() - } - - void generateValidationCode(MethodSpec.Builder validateMethod) { - if (!couldContainsNull()) { - validateMethod.addStatement("validateNotNull(\$L)", name) - } - if (!type.ableToInnerValidate) { - return - } - if (type == FieldType.TYPE_ARGUMENT || type == FieldType.IMPORTED_CLASS) { - validateMethod - .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.MAP, name) - .endControlFlow() - return - } - if (couldContainsNull()) { - validateMethod.beginControlFlow("if (\$L != null)", name) - } - if (type == FieldType.LIST) { - if (nonEmptyCollection) { - validateMethod.addStatement("validateCollectionNotEmpty(\$L)", name) - } - if (solidCollection) { - validateMethod.addStatement("validateCollection(\$L, CollectionValidationRule.EXCEPTION_IF_ANY_INVALID)", name) - } else if (nonEmptyCollection) { - validateMethod.addStatement("validateCollection(\$L, CollectionValidationRule.EXCEPTION_IF_ALL_INVALID)", name) - } else { - validateMethod.addStatement("validateCollection(\$L, CollectionValidationRule.REMOVE_INVALID_ITEMS)", name) - } - } else if (type == FieldType.MAP) { - if (nonEmptyCollection) { - validateMethod.addStatement("validateCollectionNotEmpty(\$L.values())", name) - } - validateMethod.addStatement("validateCollection(\$L.values(), CollectionValidationRule.EXCEPTION_IF_ANY_INVALID)", name) - } else if (type == FieldType.MODEL) { - validateMethod.addStatement("\$L.validate()", name) - } else { - throw new Exception("Unexpected able to validate field type '" + type + "' of field " + name) - } - if (couldContainsNull()) { - validateMethod.endControlFlow() - } - } - -} - -/** - * Object that's describing class model. - * Contains name and info about each field. Field parameters are describing in FieldInfo class. - * Samples: - * - * class MySimpleClass: - * id: int - * name: string - * - * class MyNonSimpleClass: - * typeArguments: TItem, TResponse - * extends: BaseResponse - * id: - * jsonName: _id - * type: int - * name: - * type: string - * flags: nullable - * items: - * type: List - * flags: non-empty, solid - */ -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 typeArguments = new ArrayList<>() - TypeName superclass - ClassObject parentModel - - ClassObject(final String name, final Map fieldsInfo) { - this.name = name.trim() - this.fieldsInfo = fieldsInfo - } - - void resolveFieldsInfo(final Map objects) { - final Set fieldNames = new HashSet<>() - for (final Entry entry : fieldsInfo.entrySet()) { - if (fieldNames.contains(entry.key)) { - throw new Exception("Duplicate field name: " + name) - } - - if (entry.key.equals("typeArguments")) { - for (String typeVariable : entry.value.replace(" ", "").split(",")) { - typeArguments.add(typeVariable) - } - continue - } - - if (entry.key.equals("extends")) { - superclass = TypeNameUtils.resolveTypeName(entry.value, objects) - parentModel = resolveBaseTypeModel(entry.value, objects) - continue - } - - fieldNames.add(entry.key) - - try { - if (entry.value instanceof Map) { - fields.add(new FieldInfo(entry.key, (Map) entry.value, objects)) - } else { - final Map parameters = new HashMap<>() - parameters.put("type", entry.value) - fields.add(new FieldInfo(entry.key, parameters, objects)) - } - } catch (final Exception exception) { - throw new Exception("Error on parsing field '" + entry.key + "' : " + exception.getMessage()) - } - } - } - - 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) { - final ParameterSpec.Builder paramBuilder = ParameterSpec - .builder(ClassName.bestGuess(childTypeArguments.get(argIndex)), field.name, Modifier.FINAL) - if (!field.typeName.primitive) { - paramBuilder.addAnnotation(field.couldContainsNull() ? Types.NULLABLE : Types.NON_NULL) - } - result.addParameter(paramBuilder.build()) - } else { - final ParameterSpec.Builder paramBuilder = ParameterSpec.builder(field.typeName, field.name, Modifier.FINAL) - if (!field.typeName.primitive) { - paramBuilder.addAnnotation(field.couldContainsNull() ? Types.NULLABLE : Types.NON_NULL) - } - result.addParameter(paramBuilder.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) - .addJavadoc("This class is autogenerated by Touchin tools") - .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 (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()) - - // 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 - 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 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) - .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() - - for (final FieldInfo field : fields) { - classBuilder.addField(field.generateFieldCode()) - classBuilder.addMethod(field.generateGetterCode()) - classBuilder.addMethod(field.generateSetterCode()) - field.generateValidationCode(validateMethod) - final String serializeMethodName = (!field.couldContainsNull() && field.type.serializationMethodName != null - ? field.type.serializationMethodName : "writeObject"); - 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")) { - deserializeMethod.addStatement("\$L = (\$T) inputStream.\$L()", field.name, field.typeName, deserializeMethodName) - } else { - deserializeMethod.addStatement("\$L = inputStream.\$L()", field.name, deserializeMethodName) - } - - if (fullConstructorBuilder != null) { - final ParameterSpec.Builder paramBuilder = ParameterSpec.builder(field.typeName, field.name, Modifier.FINAL) - if (!field.typeName.primitive) { - paramBuilder.addAnnotation(field.couldContainsNull() ? Types.NULLABLE : Types.NON_NULL) - } - fullConstructorBuilder.addParameter(paramBuilder.build()) - } - if (field.type == FieldType.LIST) { - if (fullConstructorBuilder != null) { - fullConstructorBuilder.addStatement("this.\$L = new \$T<>(\$L)", field.name, Types.ARRAY_LIST, field.name) - } - 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) - } - 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) { - if (superclass == null) { - hashCodeStatement.add("return \$T.hashCode(", Types.OBJECT_UTILS) - equalsStatement.add("return ", Types.OBJECT_UTILS) - } else { - hashCodeStatement.add("return \$T.hashCode(super.hashCode(), ", Types.OBJECT_UTILS) - equalsStatement.add("return super.equals(that) && ", Types.OBJECT_UTILS, field.name, field.name) - } - } else { - hashCodeStatement.add(", ") - equalsStatement.add("\n\t\t&& ") - } - - if (field.type == FieldType.MAP) { - equalsStatement.add("\$T.isMapsEquals(this.\$L, that.\$L)", Types.OBJECT_UTILS, field.name, field.name) - } else if (field.type == FieldType.LIST) { - equalsStatement.add("\$T.isCollectionsEquals(this.\$L, that.\$L)", Types.OBJECT_UTILS, field.name, field.name) - } else { - equalsStatement.add("\$T.equals(this.\$L, that.\$L)", Types.OBJECT_UTILS, field.name, field.name) - } - hashCodeStatement.add("this.\$L", field.name) - first = false - } - equalsStatement.add(";\n") - hashCodeStatement.add(");\n") - - // 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) - .returns(TypeName.BOOLEAN) - .addParameter(ParameterSpec.builder(ClassName.get(Object.class), "object", Modifier.FINAL).addAnnotation(Types.NULLABLE).build()) - .beginControlFlow("if (this == object)").addStatement("return true").endControlFlow() - .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()) - // 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()) - } - JavaFile.builder(packageName, classBuilder.build()) - .indent(" ") - .build() - .writeTo(directory) - } - -} - -class FileUtils { - - static void purgeDirectory(final File directory) { - for (File file : directory.listFiles()) { - if (file.isDirectory()) { - purgeDirectory(file) - } - file.delete() - } - } - - static Map getObjectsMap(final String schemeFilePath, final String projectDir) { - if (schemeFilePath == null) { - return - } - - File schemeFile = new File(schemeFilePath) - if (!schemeFile.exists()) { - schemeFile = new File(projectDir, schemeFilePath) - } - if (!schemeFile.exists()) { - throw new Exception("JSON models scheme file not found at '" + schemeFilePath) - } - - final Yaml yaml = new Yaml() - final Map schemeObjects = new HashMap<>() - 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)) { - throw new Exception("Yaml file '" + schemeFile + "' is invalid") - } - - 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, 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) - } - } - } - - return schemeObjects - } - -} - -android.applicationVariants.all { - variant -> - 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}") << { - 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) { - packageName = android.defaultConfig.applicationId + '.logic.model' - objects = FileUtils.getObjectsMap(jsonMapping.trim(), "${project.projectDir}") - } else { - 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 (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, null)) - } else if (externalObject instanceof ClassObject) { - schemeObjects.put(externalObject.name, - new ImportObject(externalObjects.key + '.' + externalObject.name, ImportObject.Type.MODEL, externalObject)) - } - } - } - - 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()) - } - } - } - } - - for (final Entry> fileObjects : overallObjects.entrySet()) { - final String packageName = fileObjects.key - final Map schemeObjects = new HashMap<>(fileObjects.value) - for (final SchemeObject schemeObject : schemeObjects.values()) { - try { - schemeObject.writeToFile(generatedModelsDirectory, schemeObjects, packageName) - } catch (final Exception exception) { - throw new Exception("Error on generating code for '" + schemeObject.name + "' : " + exception.getMessage()) - } - } - } - } - - generateJsonModelsTask.description = 'Generates Java classes for JSON models' - variant.registerJavaGeneratingTask generateJsonModelsTask, generatedModelsDirectory -} \ No newline at end of file diff --git a/gradle/staticAnalysis.gradle b/gradle/staticAnalysis.gradle index f48d267..a14df6f 100644 --- a/gradle/staticAnalysis.gradle +++ b/gradle/staticAnalysis.gradle @@ -1,7 +1,7 @@ def getServerProjectSources def getAndroidProjectSources -apply from: "${rootDir}/libraries/BuildScripts/gradle/commonStaticAnalysis.gradle" +apply from: "$rootDir/BuildScripts/gradle/commonStaticAnalysis.gradle" gradle.projectsEvaluated { @@ -104,5 +104,3 @@ getAndroidProjectSources = { excludes -> } return sources } - - From 3fe4b2a1aea149050b34c60ec7e2a876dfd8c0b2 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Wed, 22 Aug 2018 00:47:22 +0300 Subject: [PATCH 2/3] Configuring path for static --- gradle/commonStaticAnalysis.gradle | 8 ++++---- gradle/staticAnalysis.gradle | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/commonStaticAnalysis.gradle b/gradle/commonStaticAnalysis.gradle index 9128d56..31589bc 100644 --- a/gradle/commonStaticAnalysis.gradle +++ b/gradle/commonStaticAnalysis.gradle @@ -257,7 +257,7 @@ getPmdTask = { sources -> def taskName = "pmd_${project.name}" tasks.create(taskName, Pmd) { pmdClasspath = configurations.pmd.asFileTree - ruleSetFiles = files "$rootDir/BuildScripts/pmd/rulesets/java/android.xml" + ruleSetFiles = files "$buildScriptsDir/pmd/rulesets/java/android.xml" ruleSets = [] source files(sources) ignoreFailures = true @@ -296,7 +296,7 @@ getLintTasks = { android.lintOptions.xmlReport = true android.lintOptions.xmlOutput = file "$project.buildDir/reports/lint_report.xml" android.lintOptions.htmlReport = false - android.lintOptions.lintConfig = file "$rootDir/BuildScripts/lint/lint.xml" + android.lintOptions.lintConfig = file "$buildScriptsDir/lint/lint.xml" return lintTaskNames } @@ -309,7 +309,7 @@ getCheckstyleTask = { sources -> ignoreFailures = true showViolations = false source files(sources) - configFile file("$rootDir/BuildScripts/checkstyle/configuration/touchin_checkstyle.xml") + configFile file("$buildScriptsDir/checkstyle/configuration/touchin_checkstyle.xml") checkstyleClasspath = configurations.checkstyle.asFileTree classpath = files(System.getenv("ANDROID_HOME") + "/platforms/" + android.compileSdkVersion + "/android.jar") + files(System.properties.'java.home' + "/lib/rt.jar") + @@ -332,7 +332,7 @@ getKotlinDetektTask = { isAndroidProject -> def input = "${rootDir}" def output = "${project.buildDir}/reports" def outputName = "kotlin-detekt" - def config = "$rootDir/BuildScripts/kotlin/detekt-config.yml" + def config = "$buildScriptsDir/kotlin/detekt-config.yml" // TODO add excludes from rootProject.extensions.findByName("staticAnalysisExcludes") def filters = ".*src/test.*,.*/resources/.*,.*/tmp/.*" diff --git a/gradle/staticAnalysis.gradle b/gradle/staticAnalysis.gradle index a14df6f..222fd92 100644 --- a/gradle/staticAnalysis.gradle +++ b/gradle/staticAnalysis.gradle @@ -1,7 +1,7 @@ def getServerProjectSources def getAndroidProjectSources -apply from: "$rootDir/BuildScripts/gradle/commonStaticAnalysis.gradle" +apply from: "$buildScriptsDir/gradle/commonStaticAnalysis.gradle" gradle.projectsEvaluated { From cde76d7d189f5381e98dbd0535c4ca3c7c9c709c Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Sat, 20 Oct 2018 13:18:37 +0300 Subject: [PATCH 3/3] Api-generator 1.2.3 --- gradle/apiGenerator.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/apiGenerator.gradle b/gradle/apiGenerator.gradle index 1343927..d1ba435 100644 --- a/gradle/apiGenerator.gradle +++ b/gradle/apiGenerator.gradle @@ -7,7 +7,7 @@ configurations { } dependencies { - apigenerator 'ru.touchin:api-generator:1.2.2' + apigenerator 'ru.touchin:api-generator:1.2.3' } android.applicationVariants.all { variant ->