Skip to content

Commit

Permalink
Add support for cross-module object transformers
Browse files Browse the repository at this point in the history
  • Loading branch information
SiimKinks committed Apr 14, 2018
1 parent d4512eb commit 9867cbe
Show file tree
Hide file tree
Showing 15 changed files with 205 additions and 31 deletions.
Expand Up @@ -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 {};
}
Expand Up @@ -17,4 +17,11 @@
* @return Submodule name
*/
String value();

/**
* Transformers from external modules.
*
* @return External transformers containing classes
*/
Class<?>[] externalTransformers() default {};
}
2 changes: 1 addition & 1 deletion buildsystem/dependencies.gradle
Expand Up @@ -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'
Expand Down
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -443,8 +444,18 @@ public ImmutableSet<ExecutableElement> getLocalAndInheritedColumnMethods(TypeEle

@NonNull
public Set<ExecutableElement> getLocalAndInheritedMethods(TypeElement type) {
return getLocalAndInheritedMethods(type, new ConditionCallback<ExecutableElement>() {
@Override
public boolean call(ExecutableElement method) {
return !method.getModifiers().contains(Modifier.STATIC);
}
});
}

@NonNull
public Set<ExecutableElement> getLocalAndInheritedMethods(TypeElement type, ConditionCallback<ExecutableElement> includeMethodCallback) {
SetMultimap<String, ExecutableElement> 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
Expand Down Expand Up @@ -472,21 +483,22 @@ public Set<ExecutableElement> getLocalAndInheritedMethods(TypeElement type) {

private static void getLocalAndInheritedMethods(PackageElement pkg,
TypeElement type,
SetMultimap<String, ExecutableElement> methods) {
SetMultimap<String, ExecutableElement> methods,
ConditionCallback<ExecutableElement> 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);
}
Expand Down
Expand Up @@ -26,6 +26,8 @@

public class TransformerElement {
private final Environment environment;
@Getter
private final boolean external;

private TypeElement deserializedTypeTransformerElement;
private ClassName deserializedTypeTransformerClassName;
Expand All @@ -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;
}
Expand Down
Expand Up @@ -2,21 +2,33 @@

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;

import javax.annotation.processing.RoundEnvironment;
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 {

Expand All @@ -34,31 +46,116 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
boolean isSuccessfulProcessing = true;
final Map<String, TransformerElement> transformers = new HashMap<>();

for (Element element : roundEnv.getElementsAnnotatedWith(ObjectToDbValue.class)) {
final Set<? extends Element> objectToDbValueElements = roundEnv.getElementsAnnotatedWith(ObjectToDbValue.class);
final Set<? extends Element> dbValueToObjectElements = roundEnv.getElementsAnnotatedWith(DbValueToObject.class);
collectTransformers(
objectToDbValueElements,
dbValueToObjectElements,
transformers,
false
);

final List<TypeElement> externalTransformers = getExternalTransformers(roundEnv);
final Set<ExecutableElement> 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<? extends Element> objectToDbValueElements,
Set<? extends Element> dbValueToObjectElements,
Map<String, TransformerElement> 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<TypeElement> getExternalTransformers(RoundEnvironment roundEnv) {
final Elements elementUtils = environment.getElementUtils();
final List<TypeElement> result = new ArrayList<>();
final Class<?>[] externalTransformers;
try {
if (environment.isSubmodule()) {
final Set<? extends Element> 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<? extends Element> 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<ExecutableElement> getAllStaticMethods(List<TypeElement> externalTransformers) {
final HashSet<ExecutableElement> result = new HashSet<>();
for (TypeElement typeElement : externalTransformers) {
final Set<ExecutableElement> methods = environment.getLocalAndInheritedMethods(typeElement, new ConditionCallback<ExecutableElement>() {
@Override
public boolean call(ExecutableElement method) {
return method.getModifiers().contains(Modifier.STATIC);
}
});
result.addAll(methods);
}
return result;
}

private Set<ExecutableElement> filterMethodsWithAnnotation(Set<ExecutableElement> methods, Class<? extends Annotation> annotation) {
final HashSet<ExecutableElement> result = new HashSet<>();
for (ExecutableElement method : methods) {
if (method.getAnnotation(annotation) != null) {
result.add(method);
}
}
return result;
}
}
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion sqlitemagic-tests/.gitignore
Expand Up @@ -6,4 +6,4 @@
/captures
**/db
/db
**/debug/assets/*.sql
**/*debug/assets/*.sql
2 changes: 2 additions & 0 deletions sqlitemagic-tests/another-submodule/build.gradle
Expand Up @@ -42,6 +42,8 @@ android {
}

dependencies {
implementation project(':submodule')

implementation libraries.kotlinStdLib
implementation libraries.rxJava2
implementation libraries.supportSqliteFramework
Expand Down
@@ -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
@@ -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
)
Expand Up @@ -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"
)))
}

Expand All @@ -31,7 +33,7 @@ class GenClassesManagerTest {
@Test
fun tableCountIsAddedForAllTableCount() {
assertThat(getNrOfTables(null))
.isEqualTo(3)
.isEqualTo(5)
}

@Test
Expand All @@ -43,7 +45,7 @@ class GenClassesManagerTest {
@Test
fun tableCountOfSubmodule() {
assertThat(getNrOfTables("Submodule"))
.isEqualTo(2)
.isEqualTo(3)
}

@Test
Expand Down
@@ -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
}

0 comments on commit 9867cbe

Please sign in to comment.