diff --git a/annotations/src/main/java/com/siimkinks/sqlitemagic/annotation/Database.java b/annotations/src/main/java/com/siimkinks/sqlitemagic/annotation/Database.java index 94fd4cc..eaf53bd 100644 --- a/annotations/src/main/java/com/siimkinks/sqlitemagic/annotation/Database.java +++ b/annotations/src/main/java/com/siimkinks/sqlitemagic/annotation/Database.java @@ -31,4 +31,11 @@ * @return List of submodules database configurations. */ Class[] submodules() default {}; + + /** + * Transformers from external modules. + * + * @return External transformers containing classes + */ + Class[] externalTransformers() default {}; } diff --git a/annotations/src/main/java/com/siimkinks/sqlitemagic/annotation/SubmoduleDatabase.java b/annotations/src/main/java/com/siimkinks/sqlitemagic/annotation/SubmoduleDatabase.java index d90eac1..8853b27 100644 --- a/annotations/src/main/java/com/siimkinks/sqlitemagic/annotation/SubmoduleDatabase.java +++ b/annotations/src/main/java/com/siimkinks/sqlitemagic/annotation/SubmoduleDatabase.java @@ -17,4 +17,11 @@ * @return Submodule name */ String value(); + + /** + * Transformers from external modules. + * + * @return External transformers containing classes + */ + Class[] externalTransformers() default {}; } diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index 4343b5c..9578203 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -15,7 +15,7 @@ ext { compileSdkVersion = 27 targetSdkVersion = compileSdkVersion - androidGradleVersion = '3.2.0-alpha09' + androidGradleVersion = '3.2.0-alpha10' androidSupportLibsVersion = '27.1.0' androidVersion = '4.0.1.2' // do not increment! autoCommonVersion = '0.10' diff --git a/compiler-core/src/main/java/com/siimkinks/sqlitemagic/Environment.java b/compiler-core/src/main/java/com/siimkinks/sqlitemagic/Environment.java index e1459dd..365d846 100644 --- a/compiler-core/src/main/java/com/siimkinks/sqlitemagic/Environment.java +++ b/compiler-core/src/main/java/com/siimkinks/sqlitemagic/Environment.java @@ -18,6 +18,7 @@ import com.siimkinks.sqlitemagic.element.TableElement; import com.siimkinks.sqlitemagic.element.TransformerElement; import com.siimkinks.sqlitemagic.element.ViewElement; +import com.siimkinks.sqlitemagic.util.ConditionCallback; import com.siimkinks.sqlitemagic.util.Dual; import com.squareup.javapoet.TypeName; @@ -130,7 +131,7 @@ public Environment(Messager messager, Elements elementUtils, Types typeUtils, Fi private void addDefaultTransformers() { for (String transformerName : Const.DEFAULT_TRANSFORMERS) { - final TransformerElement transformer = new TransformerElement(this); + final TransformerElement transformer = new TransformerElement(this, false); final Element transformerElement = elementUtils.getTypeElement(transformerName); for (Element enclosedElement : transformerElement.getEnclosedElements()) { if (enclosedElement.getKind() == ElementKind.METHOD) { @@ -443,8 +444,18 @@ public ImmutableSet getLocalAndInheritedColumnMethods(TypeEle @NonNull public Set getLocalAndInheritedMethods(TypeElement type) { + return getLocalAndInheritedMethods(type, new ConditionCallback() { + @Override + public boolean call(ExecutableElement method) { + return !method.getModifiers().contains(Modifier.STATIC); + } + }); + } + + @NonNull + public Set getLocalAndInheritedMethods(TypeElement type, ConditionCallback includeMethodCallback) { SetMultimap methodMap = LinkedHashMultimap.create(); - getLocalAndInheritedMethods(getPackage(type), type, methodMap); + getLocalAndInheritedMethods(getPackage(type), type, methodMap, includeMethodCallback); // Find methods that are overridden. We do this using `Elements.overrides`, which means // that it is inherently a quadratic operation, since we have to compare every method against // every other method. We reduce the performance impact by (a) grouping methods by name, since @@ -472,21 +483,22 @@ public Set getLocalAndInheritedMethods(TypeElement type) { private static void getLocalAndInheritedMethods(PackageElement pkg, TypeElement type, - SetMultimap methods) { + SetMultimap methods, + ConditionCallback includeMethodCallback) { for (TypeMirror superInterface : type.getInterfaces()) { final TypeElement superInterfaceElement = asTypeElement(superInterface); final String interfaceName = superInterfaceElement.getSimpleName().toString(); if (interfaceName.startsWith("Parcelable")) continue; - getLocalAndInheritedMethods(pkg, superInterfaceElement, methods); + getLocalAndInheritedMethods(pkg, superInterfaceElement, methods, includeMethodCallback); } if (type.getSuperclass().getKind() != TypeKind.NONE) { // Visit the superclass after superinterfaces so we will always see the implementation of a // method after any interfaces that declared it. - getLocalAndInheritedMethods(pkg, asTypeElement(type.getSuperclass()), methods); + getLocalAndInheritedMethods(pkg, asTypeElement(type.getSuperclass()), methods, includeMethodCallback); } for (ExecutableElement method : ElementFilter.methodsIn(type.getEnclosedElements())) { - if (!method.getModifiers().contains(Modifier.STATIC) + if (includeMethodCallback.call(method) && visibleFromPackage(method, pkg)) { methods.put(method.getSimpleName().toString(), method); } diff --git a/compiler-core/src/main/java/com/siimkinks/sqlitemagic/element/TransformerElement.java b/compiler-core/src/main/java/com/siimkinks/sqlitemagic/element/TransformerElement.java index 0b9218e..991550b 100644 --- a/compiler-core/src/main/java/com/siimkinks/sqlitemagic/element/TransformerElement.java +++ b/compiler-core/src/main/java/com/siimkinks/sqlitemagic/element/TransformerElement.java @@ -26,6 +26,8 @@ public class TransformerElement { private final Environment environment; + @Getter + private final boolean external; private TypeElement deserializedTypeTransformerElement; private ClassName deserializedTypeTransformerClassName; @@ -48,20 +50,23 @@ public class TransformerElement { private boolean hasObjectToDb = false; private boolean hasDbToObject = false; - public TransformerElement(Environment environment) { + public TransformerElement(Environment environment, boolean external) { this.environment = environment; + this.external = external; } public static TransformerElement fromObjectToDbValue(Environment environment, - ExecutableElement objectToDbValue) { - final TransformerElement transformer = new TransformerElement(environment); + ExecutableElement objectToDbValue, + boolean external) { + final TransformerElement transformer = new TransformerElement(environment, external); transformer.addObjectToDbValueMethod(objectToDbValue); return transformer; } public static TransformerElement fromDbValueToObject(Environment environment, - ExecutableElement dbValueToObject) { - final TransformerElement transformer = new TransformerElement(environment); + ExecutableElement dbValueToObject, + boolean external) { + final TransformerElement transformer = new TransformerElement(environment, external); transformer.addDbValueToObjectMethod(dbValueToObject); return transformer; } diff --git a/compiler-core/src/main/java/com/siimkinks/sqlitemagic/processing/TransformerCollectionStep.java b/compiler-core/src/main/java/com/siimkinks/sqlitemagic/processing/TransformerCollectionStep.java index 39b8c56..0004cce 100644 --- a/compiler-core/src/main/java/com/siimkinks/sqlitemagic/processing/TransformerCollectionStep.java +++ b/compiler-core/src/main/java/com/siimkinks/sqlitemagic/processing/TransformerCollectionStep.java @@ -2,12 +2,19 @@ import com.siimkinks.sqlitemagic.BaseProcessor; import com.siimkinks.sqlitemagic.Environment; +import com.siimkinks.sqlitemagic.annotation.Database; +import com.siimkinks.sqlitemagic.annotation.SubmoduleDatabase; import com.siimkinks.sqlitemagic.annotation.transformer.DbValueToObject; import com.siimkinks.sqlitemagic.annotation.transformer.ObjectToDbValue; import com.siimkinks.sqlitemagic.element.TransformerElement; +import com.siimkinks.sqlitemagic.util.ConditionCallback; import com.siimkinks.sqlitemagic.validator.TransformerValidator; +import java.lang.annotation.Annotation; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -15,8 +22,13 @@ import javax.inject.Inject; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.MirroredTypesException; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; + +import static java.util.Collections.emptyList; public class TransformerCollectionStep implements ProcessingStep { @@ -34,31 +46,116 @@ public boolean process(Set annotations, RoundEnvironment boolean isSuccessfulProcessing = true; final Map transformers = new HashMap<>(); - for (Element element : roundEnv.getElementsAnnotatedWith(ObjectToDbValue.class)) { + final Set objectToDbValueElements = roundEnv.getElementsAnnotatedWith(ObjectToDbValue.class); + final Set dbValueToObjectElements = roundEnv.getElementsAnnotatedWith(DbValueToObject.class); + collectTransformers( + objectToDbValueElements, + dbValueToObjectElements, + transformers, + false + ); + + final List externalTransformers = getExternalTransformers(roundEnv); + final Set methods = getAllStaticMethods(externalTransformers); + collectTransformers( + filterMethodsWithAnnotation(methods, ObjectToDbValue.class), + filterMethodsWithAnnotation(methods, DbValueToObject.class), + transformers, + true + ); + + for (TransformerElement transformer : transformers.values()) { + if (!validator.isTransformerValid(transformer)) { + isSuccessfulProcessing = false; + } else { + environment.addTransformerElement(transformer); + } + } + return isSuccessfulProcessing; + } + + private void collectTransformers( + Set objectToDbValueElements, + Set dbValueToObjectElements, + Map transformers, + boolean external + ) { + for (Element element : objectToDbValueElements) { final ExecutableElement objectToDbValue = (ExecutableElement) element; - final TransformerElement transformerElement = TransformerElement.fromObjectToDbValue(environment, objectToDbValue); + final TransformerElement transformerElement = TransformerElement.fromObjectToDbValue(environment, objectToDbValue, external); transformers.put(transformerElement.getRawDeserializedType().toString(), transformerElement); } - for (Element element : roundEnv.getElementsAnnotatedWith(DbValueToObject.class)) { + for (Element element : dbValueToObjectElements) { final ExecutableElement dbValueToObject = (ExecutableElement) element; final TypeMirror rawDeserializedType = dbValueToObject.getReturnType(); final TransformerElement transformer = transformers.get(rawDeserializedType.toString()); if (transformer != null) { transformer.addDbValueToObjectMethod(dbValueToObject); } else { - final TransformerElement incompleteTransformer = TransformerElement.fromDbValueToObject(environment, dbValueToObject); + final TransformerElement incompleteTransformer = TransformerElement.fromDbValueToObject(environment, dbValueToObject, external); transformers.put(incompleteTransformer.getRawDeserializedType().toString(), incompleteTransformer); } } + } - for (TransformerElement transformer : transformers.values()) { - if (!validator.isTransformerValid(transformer)) { - isSuccessfulProcessing = false; + private List getExternalTransformers(RoundEnvironment roundEnv) { + final Elements elementUtils = environment.getElementUtils(); + final List result = new ArrayList<>(); + final Class[] externalTransformers; + try { + if (environment.isSubmodule()) { + final Set elements = roundEnv.getElementsAnnotatedWith(SubmoduleDatabase.class); + if (elements.isEmpty()) { + return emptyList(); + } + final Element element = elements.iterator().next(); + final SubmoduleDatabase annotation = element.getAnnotation(SubmoduleDatabase.class); + externalTransformers = annotation.externalTransformers(); } else { - environment.addTransformerElement(transformer); + final Set elements = roundEnv.getElementsAnnotatedWith(Database.class); + if (elements.isEmpty()) { + return emptyList(); + } + final Element element = elements.iterator().next(); + final Database annotation = element.getAnnotation(Database.class); + externalTransformers = annotation.externalTransformers(); } + } catch (MirroredTypesException e) { + for (TypeMirror typeMirror : e.getTypeMirrors()) { + final TypeElement typeElement = elementUtils.getTypeElement(typeMirror.toString()); + result.add(typeElement); + } + return result; } - return isSuccessfulProcessing; + for (Class clazz : externalTransformers) { + final TypeElement typeElement = environment.getTypeElement(clazz); + result.add(typeElement); + } + return result; + } + + private Set getAllStaticMethods(List externalTransformers) { + final HashSet result = new HashSet<>(); + for (TypeElement typeElement : externalTransformers) { + final Set methods = environment.getLocalAndInheritedMethods(typeElement, new ConditionCallback() { + @Override + public boolean call(ExecutableElement method) { + return method.getModifiers().contains(Modifier.STATIC); + } + }); + result.addAll(methods); + } + return result; + } + + private Set filterMethodsWithAnnotation(Set methods, Class annotation) { + final HashSet result = new HashSet<>(); + for (ExecutableElement method : methods) { + if (method.getAnnotation(annotation) != null) { + result.add(method); + } + } + return result; } } diff --git a/compiler-core/src/main/java/com/siimkinks/sqlitemagic/writer/TransformerWriter.java b/compiler-core/src/main/java/com/siimkinks/sqlitemagic/writer/TransformerWriter.java index 979bc02..55e69b7 100644 --- a/compiler-core/src/main/java/com/siimkinks/sqlitemagic/writer/TransformerWriter.java +++ b/compiler-core/src/main/java/com/siimkinks/sqlitemagic/writer/TransformerWriter.java @@ -23,7 +23,7 @@ public void writeSource(Environment environment) throws IOException { private void writeTransformerColumns(Environment environment) throws IOException { final Filer filer = environment.getFiler(); for (TransformerElement transformerElement : environment.getTransformerElements().values()) { - if (transformerElement.isDefaultTransformer()) { + if (transformerElement.isDefaultTransformer() || transformerElement.isExternal()) { continue; } ColumnClassWriter diff --git a/sqlitemagic-tests/.gitignore b/sqlitemagic-tests/.gitignore index ec193ba..d2191a2 100644 --- a/sqlitemagic-tests/.gitignore +++ b/sqlitemagic-tests/.gitignore @@ -6,4 +6,4 @@ /captures **/db /db -**/debug/assets/*.sql \ No newline at end of file +**/*debug/assets/*.sql \ No newline at end of file diff --git a/sqlitemagic-tests/another-submodule/build.gradle b/sqlitemagic-tests/another-submodule/build.gradle index 9b367c6..5416c75 100644 --- a/sqlitemagic-tests/another-submodule/build.gradle +++ b/sqlitemagic-tests/another-submodule/build.gradle @@ -42,6 +42,8 @@ android { } dependencies { + implementation project(':submodule') + implementation libraries.kotlinStdLib implementation libraries.rxJava2 implementation libraries.supportSqliteFramework diff --git a/sqlitemagic-tests/another-submodule/src/main/java/com/siimkinks/sqlitemagic/another/AnotherSubmoduleDatabaseConfig.kt b/sqlitemagic-tests/another-submodule/src/main/java/com/siimkinks/sqlitemagic/another/AnotherSubmoduleDatabaseConfig.kt index 6d429ea..4989f30 100644 --- a/sqlitemagic-tests/another-submodule/src/main/java/com/siimkinks/sqlitemagic/another/AnotherSubmoduleDatabaseConfig.kt +++ b/sqlitemagic-tests/another-submodule/src/main/java/com/siimkinks/sqlitemagic/another/AnotherSubmoduleDatabaseConfig.kt @@ -1,6 +1,7 @@ package com.siimkinks.sqlitemagic.another import com.siimkinks.sqlitemagic.annotation.SubmoduleDatabase +import com.siimkinks.sqlitemagic.submodule.DbTransformers -@SubmoduleDatabase("another") +@SubmoduleDatabase("another", externalTransformers = [DbTransformers::class]) interface AnotherSubmoduleDatabaseConfig \ No newline at end of file diff --git a/sqlitemagic-tests/another-submodule/src/main/java/com/siimkinks/sqlitemagic/another/ModelWithExternalTransformers.kt b/sqlitemagic-tests/another-submodule/src/main/java/com/siimkinks/sqlitemagic/another/ModelWithExternalTransformers.kt new file mode 100644 index 0000000..5500db4 --- /dev/null +++ b/sqlitemagic-tests/another-submodule/src/main/java/com/siimkinks/sqlitemagic/another/ModelWithExternalTransformers.kt @@ -0,0 +1,11 @@ +package com.siimkinks.sqlitemagic.another + +import com.siimkinks.sqlitemagic.annotation.Id +import com.siimkinks.sqlitemagic.annotation.Table +import java.util.* + +@Table +data class ModelWithExternalTransformers( + @Id val localId: Long?, + val date: Date +) \ No newline at end of file diff --git a/sqlitemagic-tests/multimodule-app/src/test/java/com/siimkinks/sqlitemagic/multimodule/GenClassesManagerTest.kt b/sqlitemagic-tests/multimodule-app/src/test/java/com/siimkinks/sqlitemagic/multimodule/GenClassesManagerTest.kt index fed5b38..266c2b3 100644 --- a/sqlitemagic-tests/multimodule-app/src/test/java/com/siimkinks/sqlitemagic/multimodule/GenClassesManagerTest.kt +++ b/sqlitemagic-tests/multimodule-app/src/test/java/com/siimkinks/sqlitemagic/multimodule/GenClassesManagerTest.kt @@ -18,7 +18,9 @@ class GenClassesManagerTest { assertThat(allChangedTables).isEqualTo(StringArraySet(arrayOf( "immutable_value_with_nullable_fields", "immutable_value", - "author" + "author", + "model_with_transformers", + "model_with_external_transformers" ))) } @@ -31,7 +33,7 @@ class GenClassesManagerTest { @Test fun tableCountIsAddedForAllTableCount() { assertThat(getNrOfTables(null)) - .isEqualTo(3) + .isEqualTo(5) } @Test @@ -43,7 +45,7 @@ class GenClassesManagerTest { @Test fun tableCountOfSubmodule() { assertThat(getNrOfTables("Submodule")) - .isEqualTo(2) + .isEqualTo(3) } @Test diff --git a/sqlitemagic-tests/submodule/src/main/java/com/siimkinks/sqlitemagic/submodule/DbTransformers.kt b/sqlitemagic-tests/submodule/src/main/java/com/siimkinks/sqlitemagic/submodule/DbTransformers.kt new file mode 100644 index 0000000..4e23223 --- /dev/null +++ b/sqlitemagic-tests/submodule/src/main/java/com/siimkinks/sqlitemagic/submodule/DbTransformers.kt @@ -0,0 +1,15 @@ +package com.siimkinks.sqlitemagic.submodule + +import com.siimkinks.sqlitemagic.annotation.transformer.DbValueToObject +import com.siimkinks.sqlitemagic.annotation.transformer.ObjectToDbValue +import java.util.* + +object DbTransformers { + @JvmStatic + @DbValueToObject + fun dbValueToDate(dbValue: Long?): Date? = dbValue?.let(::Date) + + @JvmStatic + @ObjectToDbValue + fun dateToDbValue(date: Date?): Long? = date?.time +} \ No newline at end of file diff --git a/sqlitemagic-tests/submodule/src/main/java/com/siimkinks/sqlitemagic/submodule/ModelWithTransformers.kt b/sqlitemagic-tests/submodule/src/main/java/com/siimkinks/sqlitemagic/submodule/ModelWithTransformers.kt new file mode 100644 index 0000000..353b0ea --- /dev/null +++ b/sqlitemagic-tests/submodule/src/main/java/com/siimkinks/sqlitemagic/submodule/ModelWithTransformers.kt @@ -0,0 +1,11 @@ +package com.siimkinks.sqlitemagic.submodule + +import com.siimkinks.sqlitemagic.annotation.Id +import com.siimkinks.sqlitemagic.annotation.Table +import java.util.* + +@Table +data class ModelWithTransformers( + @Id val localId: Long?, + val date: Date +) \ No newline at end of file diff --git a/sqlitemagic-tests/submodule/src/test/java/com/siimkinks/sqlitemagic/submodule/SubmoduleGenClassesManagerTest.kt b/sqlitemagic-tests/submodule/src/test/java/com/siimkinks/sqlitemagic/submodule/SubmoduleGenClassesManagerTest.kt index 5d78be8..cad574f 100644 --- a/sqlitemagic-tests/submodule/src/test/java/com/siimkinks/sqlitemagic/submodule/SubmoduleGenClassesManagerTest.kt +++ b/sqlitemagic-tests/submodule/src/test/java/com/siimkinks/sqlitemagic/submodule/SubmoduleGenClassesManagerTest.kt @@ -12,25 +12,29 @@ class SubmoduleGenClassesManagerTest { fun clearDataReturnsAllModuleTables() { val allChangedTables = clearData(mock()) assertThat(allChangedTables.size).isEqualTo(getNrOfTables(null)) - assertThat(allChangedTables).isEqualTo(StringArraySet(arrayOf("immutable_value_with_nullable_fields", "immutable_value"))) + assertThat(allChangedTables).isEqualTo(StringArraySet(arrayOf( + "immutable_value_with_nullable_fields", + "immutable_value", + "model_with_transformers" + ))) } @Test fun tableCountIsNotChangedForAllTableCount() { assertThat(getNrOfTables(null)) - .isEqualTo(2) + .isEqualTo(3) } @Test fun tableCountIsNotChangedForNativeModule() { assertThat(getNrOfTables("")) - .isEqualTo(2) + .isEqualTo(3) } @Test fun tableCountIsNotChangedForNativeModuleWithSubmoduleName() { assertThat(getNrOfTables("Submodule")) - .isEqualTo(2) + .isEqualTo(3) } @Test