From f81f3b527f3b8f492f74923afc6986d750a6c9ac Mon Sep 17 00:00:00 2001 From: Yaroslav Heriatovych Date: Mon, 10 Oct 2016 20:00:05 +0100 Subject: [PATCH 1/5] change code generation for @CombineState to remove unecessary state allocation --- .../processor/CombinedStateProcessor.java | 39 +++++---- .../test/java/CombinedStateReducerTest.java | 87 ++++++++++++------- 2 files changed, 74 insertions(+), 52 deletions(-) diff --git a/compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateProcessor.java b/compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateProcessor.java index e61f6f4..e7ab090 100644 --- a/compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateProcessor.java +++ b/compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateProcessor.java @@ -106,8 +106,7 @@ private void emmitCombinedReducer(CombinedStateElement combinedStateElement, Cla MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder() .addModifiers(Modifier.PRIVATE); - for (int i = 0; i < properties.size(); i++) { - StateProperty property = properties.get(i); + for (StateProperty property : properties) { String reducerFieldName = property.name + REDUCER_SUFFIX; TypeName subReducerType = property.getReducerInterfaceTypeName(); FieldSpec subReducerField = FieldSpec.builder(subReducerType, reducerFieldName, Modifier.PRIVATE, Modifier.FINAL) @@ -122,7 +121,7 @@ private void emmitCombinedReducer(CombinedStateElement combinedStateElement, Cla @Override public CodeBlock.Builder call(CodeBlock.Builder builder, StateProperty property) { String reducerFieldName = property.name + REDUCER_SUFFIX; - return builder.addStatement("$T $N = $N.reduce(state.$N(), action)", property.stateType, property.name, reducerFieldName, property.name); + return builder.addStatement("$T $NNext = $N.reduce($N, action)", property.stateType, property.name, reducerFieldName, property.name); } }).build(); @@ -132,7 +131,7 @@ public CodeBlock.Builder call(CodeBlock.Builder builder, StateProperty property) .returns(combinedReducerReturnTypeName) .addParameter(combinedReducerReturnTypeName, stateParam) .addParameter(reducerActionType, actionParam) - .addCode(emitInitialValueBlock(stateClassName, properties)).addCode("\n") + .addCode(emitDestructuringBlock(properties)).addCode("\n") .addCode(dispatchingBlockBuilder).addCode("\n") .addCode(emitReturnBlock(stateClassName, properties)) .build(); @@ -161,18 +160,20 @@ public CodeBlock.Builder call(CodeBlock.Builder builder, StateProperty property) javaFile.writeTo(env.getFiler()); } - private CodeBlock emitInitialValueBlock(ClassName stateClassName, List properties) { - String defaultArgs = join(", ", map(properties, new Utils.Func1() { - @Override - public String call(StateProperty stateProperty) { - return Utils.getDefaultValue(stateProperty.stateType.getKind()); - } - })); - return CodeBlock.builder() - .beginControlFlow("if (state == null)") - .addStatement("state = new $T($N)", stateClassName, defaultArgs) - .endControlFlow() - .build(); + private CodeBlock emitDestructuringBlock(List properties) { + CodeBlock.Builder destructuringBlock = CodeBlock.builder(); + for (StateProperty property : properties) { + destructuringBlock.addStatement("$T $N = $L", property.stateType, property.name, Utils.getDefaultValue(property.stateType.getKind())); + } + + destructuringBlock.add("\n"); + + destructuringBlock.beginControlFlow("if (state != null)"); + for (StateProperty property : properties) { + destructuringBlock.addStatement("$N = state.$N()", property.name, property.name); + } + destructuringBlock.endControlFlow(); + return destructuringBlock.build(); } private CodeBlock emitReturnBlock(ClassName stateClassName, List properties) { @@ -180,18 +181,18 @@ private CodeBlock emitReturnBlock(ClassName stateClassName, List map(properties, new Utils.Func1() { @Override public String call(StateProperty property) { - return String.format("%s == state.%s()", property.name, property.name); + return String.format("%s == %sNext", property.name, property.name); } })); String args = join(", ", map(properties, new Utils.Func1() { @Override public String call(StateProperty property) { - return property.name; + return property.name + "Next"; } })); return CodeBlock.builder() .add("//If all values are the same there is no need to create an object\n") - .beginControlFlow("if (" + equalsCondition + ")") + .beginControlFlow("if (state != null\n && " + equalsCondition + ")") .addStatement("return state") .nextControlFlow("else") .addStatement("return new $T("+args + ")", stateClassName) diff --git a/compiller/src/test/java/CombinedStateReducerTest.java b/compiller/src/test/java/CombinedStateReducerTest.java index e8768f3..1fb04f9 100644 --- a/compiller/src/test/java/CombinedStateReducerTest.java +++ b/compiller/src/test/java/CombinedStateReducerTest.java @@ -42,19 +42,24 @@ public void testSimpleReducerGeneration() { "\n" + " @Override\n" + " public Foobar reduce(Foobar state, Action action) {\n" + - " if (state == null) {\n" + - " state = new FoobarImpl(null, null);\n" + + " String foo = null;\n" + + " Date bar = null;\n" + + "\n" + + " if (state != null) {\n" + + " foo = state.foo();\n" + + " bar = state.bar();\n" + " }\n" + "\n" + - " String foo = fooReducer.reduce(state.foo(), action);\n" + - " Date bar = barReducer.reduce(state.bar(), action);\n" + + " String fooNext = fooReducer.reduce(foo, action);\n" + + " Date barNext = barReducer.reduce(bar, action);\n" + "\n" + " //If all values are the same there is no need to create an object\n" + - " if (foo == state.foo()\n" + - " && bar == state.bar()) {\n" + + " if (state != null\n" + + " && foo == fooNext\n" + + " && bar == barNext) {\n" + " return state;\n" + " } else {\n" + - " return new FoobarImpl(foo, bar);\n" + + " return new FoobarImpl(fooNext, barNext);\n" + " }\n" + " }\n" + "\n" + @@ -132,19 +137,24 @@ public void testBoxPrimitives() { "\n" + " @Override\n" + " public Foobar reduce(Foobar state, Action action) {\n" + - " if (state == null) {\n" + - " state = new FoobarImpl(0, false);\n" + + " int foo = 0;\n" + + " boolean bar = false;\n" + + "\n" + + " if (state != null) {\n" + + " foo = state.foo();\n" + + " bar = state.bar();\n" + " }\n" + "\n" + - " int foo = fooReducer.reduce(state.foo(), action);\n" + - " boolean bar = barReducer.reduce(state.bar(), action);\n" + + " int fooNext = fooReducer.reduce(foo, action);\n" + + " boolean barNext = barReducer.reduce(bar, action);\n" + "\n" + " //If all values are the same there is no need to create an object\n" + - " if (foo == state.foo()\n" + - " && bar == state.bar()) {\n" + + " if (state != null\n" + + " && foo == fooNext\n" + + " && bar == barNext) {\n" + " return state;\n" + " } else {\n" + - " return new FoobarImpl(foo, bar);\n" + + " return new FoobarImpl(fooNext, barNext);\n" + " }\n" + " }\n" + "\n" + @@ -237,26 +247,37 @@ public void testInitialValues() { "\n" + " @Override\n" + " public Foobar reduce(Foobar state, Action action) {\n" + - " if (state == null) {\n" + - " state = new FoobarImpl(0, 0, false, '\\u0000', null);\n" + - " }\n" + - "\n" + - " int intValue = intValueReducer.reduce(state.intValue(), action);\n" + - " double doubleValue = doubleValueReducer.reduce(state.doubleValue(), action);\n" + - " boolean booleanValue = booleanValueReducer.reduce(state.booleanValue(), action);\n" + - " char charValue = charValueReducer.reduce(state.charValue(), action);\n" + - " Object objectValue = objectValueReducer.reduce(state.objectValue(), action);\n" + + " int intValue = 0;\n" + + " double doubleValue = 0;\n" + + " boolean booleanValue = false;\n" + + " char charValue = '\\u0000';\n" + + " Object objectValue = null;\n" + + "\n" + + " if (state != null) {\n" + + " intValue = state.intValue();\n" + + " doubleValue = state.doubleValue();\n" + + " booleanValue = state.booleanValue();\n" + + " charValue = state.charValue();\n" + + " objectValue = state.objectValue();\n" + + " }\n" + "\n" + - " //If all values are the same there is no need to create an object\n" + - " if (intValue == state.intValue()\n" + - " && doubleValue == state.doubleValue()\n" + - " && booleanValue == state.booleanValue()\n" + - " && charValue == state.charValue()\n" + - " && objectValue == state.objectValue()) {\n" + - " return state;\n" + - " } else {\n" + - " return new FoobarImpl(intValue, doubleValue, booleanValue, charValue, objectValue);\n" + - " }\n" + + " int intValueNext = intValueReducer.reduce(intValue, action);\n" + + " double doubleValueNext = doubleValueReducer.reduce(doubleValue, action);\n" + + " boolean booleanValueNext = booleanValueReducer.reduce(booleanValue, action);\n" + + " char charValueNext = charValueReducer.reduce(charValue, action);\n" + + " Object objectValueNext = objectValueReducer.reduce(objectValue, action);\n" + + "\n" + + " //If all values are the same there is no need to create an object\n" + + " if (state != null\n" + + " && intValue == intValueNext\n" + + " && doubleValue == doubleValueNext\n" + + " && booleanValue == booleanValueNext\n" + + " && charValue == charValueNext\n" + + " && objectValue == objectValueNext) {\n" + + " return state;\n" + + " } else {\n" + + " return new FoobarImpl(intValueNext, doubleValueNext, booleanValueNext, charValueNext, objectValueNext);\n" + + " }\n" + " }\n" + "\n" + " public static Builder builder() {\n" + From 374a86261d9c808001036081b5b239b0ca41e831 Mon Sep 17 00:00:00 2001 From: Yaroslav Heriatovych Date: Tue, 11 Oct 2016 09:38:09 +0100 Subject: [PATCH 2/5] generate @CombinedState reducer for @AutoValue classes --- compiller/build.gradle | 2 + .../CombinedStateAutoValueExtension.java | 43 +++++++++++++++++++ .../processor/CombinedStateProcessor.java | 22 +++++----- .../processor/model/CombinedStateElement.java | 39 +++++++++++++---- .../processor/model/StateProperty.java | 10 +++++ 5 files changed, 98 insertions(+), 18 deletions(-) create mode 100644 compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateAutoValueExtension.java diff --git a/compiller/build.gradle b/compiller/build.gradle index 10e8a4d..73dd1ed 100644 --- a/compiller/build.gradle +++ b/compiller/build.gradle @@ -11,6 +11,8 @@ dependencies { compile 'com.google.auto.service:auto-service:1.0-rc2' compile 'com.google.auto:auto-common:0.6' + compile 'com.google.auto.value:auto-value:1.3' + testCompile 'com.google.testing.compile:compile-testing:0.9' testCompile 'junit:junit:4.12' testCompile 'com.google.truth:truth:0.28' diff --git a/compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateAutoValueExtension.java b/compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateAutoValueExtension.java new file mode 100644 index 0000000..138c7db --- /dev/null +++ b/compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateAutoValueExtension.java @@ -0,0 +1,43 @@ +package com.yheriatovych.reductor.processor; + +import com.google.auto.service.AutoService; +import com.google.auto.value.extension.AutoValueExtension; +import com.squareup.javapoet.*; +import com.yheriatovych.reductor.annotations.CombinedState; +import com.yheriatovych.reductor.processor.model.CombinedStateElement; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import java.io.IOException; +import java.util.Map; + +@AutoService(AutoValueExtension.class) +public class CombinedStateAutoValueExtension extends AutoValueExtension { + @Override + public String generateClass(Context context, String className, String classToExtend, boolean isFinal) { + return null; + } + + @Override + public boolean applicable(Context context) { + TypeElement typeElement = context.autoValueClass(); + boolean isApplicable = typeElement.getAnnotation(CombinedState.class) != null; + if(isApplicable) { + try { + CombinedStateElement combinedStateElement = CombinedStateElement.parseAutoValueCombinedElement(typeElement, context.properties()); + ProcessingEnvironment processingEnvironment = context.processingEnvironment(); + Env env = new Env(processingEnvironment.getTypeUtils(), processingEnvironment.getElementUtils(), processingEnvironment.getMessager(), processingEnvironment.getFiler()); + CombinedStateProcessor.emmitCombinedReducer(env, combinedStateElement, ClassName.get(context.packageName(), "AutoValue_"+context.autoValueClass().getSimpleName().toString())); + } catch (IOException e) { + e.printStackTrace(); + } catch (ValidationException e) { + e.printStackTrace(); + } + } + + return false; + } + + +} diff --git a/compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateProcessor.java b/compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateProcessor.java index e7ab090..874c191 100644 --- a/compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateProcessor.java +++ b/compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateProcessor.java @@ -32,8 +32,10 @@ public boolean process(Set annotations, RoundEnvironment try { CombinedStateElement combinedStateElement = CombinedStateElement.parseCombinedElement(combinedStateTypeElement); + if(combinedStateElement == null) continue; + ClassName stateClassName = emmitCombinedStateImplementation(combinedStateElement); - emmitCombinedReducer(combinedStateElement, stateClassName); + emmitCombinedReducer(env, combinedStateElement, stateClassName); } catch (ValidationException ve) { env.printError(ve.getElement(), ve.getMessage()); } catch (Exception e) { @@ -83,7 +85,7 @@ private ClassName emmitCombinedStateImplementation(CombinedStateElement combined return ClassName.get(javaFile.packageName, typeSpec.name); } - private void emmitCombinedReducer(CombinedStateElement combinedStateElement, ClassName stateClassName) throws IOException { + public static void emmitCombinedReducer(final Env env, CombinedStateElement combinedStateElement, ClassName stateClassName) throws IOException { String stateParam = "state"; String actionParam = "action"; @@ -121,7 +123,7 @@ private void emmitCombinedReducer(CombinedStateElement combinedStateElement, Cla @Override public CodeBlock.Builder call(CodeBlock.Builder builder, StateProperty property) { String reducerFieldName = property.name + REDUCER_SUFFIX; - return builder.addStatement("$T $NNext = $N.reduce($N, action)", property.stateType, property.name, reducerFieldName, property.name); + return builder.addStatement("$T $NNext = $N.reduce($N, action)", property.boxedStateType(env), property.name, reducerFieldName, property.name); } }).build(); @@ -131,13 +133,13 @@ public CodeBlock.Builder call(CodeBlock.Builder builder, StateProperty property) .returns(combinedReducerReturnTypeName) .addParameter(combinedReducerReturnTypeName, stateParam) .addParameter(reducerActionType, actionParam) - .addCode(emitDestructuringBlock(properties)).addCode("\n") + .addCode(emitDestructuringBlock(properties, env)).addCode("\n") .addCode(dispatchingBlockBuilder).addCode("\n") - .addCode(emitReturnBlock(stateClassName, properties)) + .addCode(CombinedStateProcessor.emitReturnBlock(stateClassName, properties)) .build(); ClassName builderClassName = ClassName.get(combinedReducerClassName.packageName(), combinedReducerClassName.simpleName(), "Builder"); - TypeSpec reducerBuilderTypeSpec = createReducerBuilder(combinedStateElement, combinedReducerClassName, builderClassName); + TypeSpec reducerBuilderTypeSpec = CombinedStateProcessor.createReducerBuilder(combinedStateElement, combinedReducerClassName, builderClassName); MethodSpec builderFactoryMethod = MethodSpec.methodBuilder("builder") @@ -160,10 +162,10 @@ public CodeBlock.Builder call(CodeBlock.Builder builder, StateProperty property) javaFile.writeTo(env.getFiler()); } - private CodeBlock emitDestructuringBlock(List properties) { + private static CodeBlock emitDestructuringBlock(List properties, Env env) { CodeBlock.Builder destructuringBlock = CodeBlock.builder(); for (StateProperty property : properties) { - destructuringBlock.addStatement("$T $N = $L", property.stateType, property.name, Utils.getDefaultValue(property.stateType.getKind())); + destructuringBlock.addStatement("$T $N = null", property.boxedStateType(env), property.name); } destructuringBlock.add("\n"); @@ -176,7 +178,7 @@ private CodeBlock emitDestructuringBlock(List properties) { return destructuringBlock.build(); } - private CodeBlock emitReturnBlock(ClassName stateClassName, List properties) { + private static CodeBlock emitReturnBlock(ClassName stateClassName, List properties) { String equalsCondition = join("\n && ", map(properties, new Utils.Func1() { @Override @@ -200,7 +202,7 @@ public String call(StateProperty property) { .build(); } - private TypeSpec createReducerBuilder(CombinedStateElement combinedStateElement, ClassName combinedReducerClassName, ClassName builderClassName) { + private static TypeSpec createReducerBuilder(CombinedStateElement combinedStateElement, ClassName combinedReducerClassName, ClassName builderClassName) { TypeSpec.Builder builder = TypeSpec.classBuilder(builderClassName).addModifiers(Modifier.STATIC, Modifier.PUBLIC); builder.addMethod(MethodSpec.constructorBuilder() diff --git a/compiller/src/main/java/com/yheriatovych/reductor/processor/model/CombinedStateElement.java b/compiller/src/main/java/com/yheriatovych/reductor/processor/model/CombinedStateElement.java index de2db1a..31470cb 100644 --- a/compiller/src/main/java/com/yheriatovych/reductor/processor/model/CombinedStateElement.java +++ b/compiller/src/main/java/com/yheriatovych/reductor/processor/model/CombinedStateElement.java @@ -1,14 +1,17 @@ package com.yheriatovych.reductor.processor.model; +import com.google.auto.value.AutoValue; import com.squareup.javapoet.TypeName; import com.yheriatovych.reductor.Action; import com.yheriatovych.reductor.annotations.CombinedState; import com.yheriatovych.reductor.processor.ValidationException; import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import java.util.ArrayList; import java.util.List; +import java.util.Map; public class CombinedStateElement { public final TypeElement stateTypeElement; @@ -22,23 +25,43 @@ public CombinedStateElement(TypeElement stateTypeElement, List ge public static CombinedStateElement parseCombinedElement(TypeElement typeElement) throws ValidationException { - StateProperty stateProperty; - if (!typeElement.getKind().isInterface()) - throw new ValidationException(typeElement, "only interfaces supported as %s", CombinedState.class); - - List getters = new ArrayList<>(); + if (!typeElement.getKind().isInterface()) { + //We allow to implement @CombinedState either with interface or with @AutoValue class + if (typeElement.getAnnotation(AutoValue.class) != null) { + //Do nothing here. CombinedStateAutoValueExtension will handle this + return null; + } else { + throw new ValidationException(typeElement, "Only interfaces and @AutoValue classes are supported as @%s", CombinedState.class.getSimpleName()); + } + } + List properties = new ArrayList<>(); for (Element element : typeElement.getEnclosedElements()) { - stateProperty = StateProperty.parseStateProperty(element); + StateProperty stateProperty = StateProperty.parseStateProperty(element); if (stateProperty != null) { - getters.add(stateProperty); + properties.add(stateProperty); } } - return new CombinedStateElement(typeElement, getters); + return new CombinedStateElement(typeElement, properties); } public TypeName getCombinedReducerActionType() { return TypeName.get(Action.class); } + + public static CombinedStateElement parseAutoValueCombinedElement(TypeElement typeElement, Map autoValueProperties) throws ValidationException { + List properties = new ArrayList<>(); + + for (String propertyName : autoValueProperties.keySet()) { + ExecutableElement propertyElement = autoValueProperties.get(propertyName); + + StateProperty stateProperty = StateProperty.parseStateProperty(propertyElement); + if (stateProperty != null) { + properties.add(stateProperty); + } + } + + return new CombinedStateElement(typeElement, properties); + } } diff --git a/compiller/src/main/java/com/yheriatovych/reductor/processor/model/StateProperty.java b/compiller/src/main/java/com/yheriatovych/reductor/processor/model/StateProperty.java index 7e1fe70..9512f42 100644 --- a/compiller/src/main/java/com/yheriatovych/reductor/processor/model/StateProperty.java +++ b/compiller/src/main/java/com/yheriatovych/reductor/processor/model/StateProperty.java @@ -1,15 +1,18 @@ package com.yheriatovych.reductor.processor.model; +import com.google.auto.common.MoreTypes; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.yheriatovych.reductor.Reducer; +import com.yheriatovych.reductor.processor.Env; import com.yheriatovych.reductor.processor.ValidationException; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; +import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -24,6 +27,13 @@ private StateProperty(String name, TypeMirror stateType, ExecutableElement execu this.executableElement = executableElement; } + public TypeMirror boxedStateType(Env env) { + if(stateType.getKind().isPrimitive()) { + return env.getTypes().boxedClass(MoreTypes.asPrimitiveType(stateType)).asType(); + } + return stateType; + } + static StateProperty parseStateProperty(Element element) throws ValidationException { StateProperty stateProperty; if (element.getKind() != ElementKind.METHOD) return null; From 5bc107ac9356b608dcf51ca3e6910bbc3df2bbf6 Mon Sep 17 00:00:00 2001 From: Yaroslav Heriatovych Date: Tue, 11 Oct 2016 10:41:43 +0100 Subject: [PATCH 3/5] fix tests --- .../CombinedStateAutoValueExtension.java | 25 +-- .../reductor/processor/Utils.java | 18 -- .../test/java/CombinedStateReducerTest.java | 171 ++++++++++++++---- .../java/CombinedStateValidationTest.java | 2 +- 4 files changed, 151 insertions(+), 65 deletions(-) diff --git a/compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateAutoValueExtension.java b/compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateAutoValueExtension.java index 138c7db..0b2b724 100644 --- a/compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateAutoValueExtension.java +++ b/compiller/src/main/java/com/yheriatovych/reductor/processor/CombinedStateAutoValueExtension.java @@ -2,20 +2,22 @@ import com.google.auto.service.AutoService; import com.google.auto.value.extension.AutoValueExtension; -import com.squareup.javapoet.*; +import com.squareup.javapoet.ClassName; import com.yheriatovych.reductor.annotations.CombinedState; import com.yheriatovych.reductor.processor.model.CombinedStateElement; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; -import java.io.IOException; -import java.util.Map; @AutoService(AutoValueExtension.class) public class CombinedStateAutoValueExtension extends AutoValueExtension { @Override public String generateClass(Context context, String className, String classToExtend, boolean isFinal) { + //So yeah, we are generating code in `applicable` method, not here + //The reason for it: If we declare that some class is applicable for this extension, we need to contribute to + //value class hierarchy. But we don't need it actually. + //The only thing we need to do is to generate another class (Reducer) + return null; } @@ -23,15 +25,16 @@ public String generateClass(Context context, String className, String classToExt public boolean applicable(Context context) { TypeElement typeElement = context.autoValueClass(); boolean isApplicable = typeElement.getAnnotation(CombinedState.class) != null; - if(isApplicable) { + if (isApplicable) { + ProcessingEnvironment processingEnvironment = context.processingEnvironment(); + Env env = new Env(processingEnvironment.getTypeUtils(), processingEnvironment.getElementUtils(), processingEnvironment.getMessager(), processingEnvironment.getFiler()); try { CombinedStateElement combinedStateElement = CombinedStateElement.parseAutoValueCombinedElement(typeElement, context.properties()); - ProcessingEnvironment processingEnvironment = context.processingEnvironment(); - Env env = new Env(processingEnvironment.getTypeUtils(), processingEnvironment.getElementUtils(), processingEnvironment.getMessager(), processingEnvironment.getFiler()); - CombinedStateProcessor.emmitCombinedReducer(env, combinedStateElement, ClassName.get(context.packageName(), "AutoValue_"+context.autoValueClass().getSimpleName().toString())); - } catch (IOException e) { - e.printStackTrace(); - } catch (ValidationException e) { + CombinedStateProcessor.emmitCombinedReducer(env, combinedStateElement, ClassName.get(context.packageName(), "AutoValue_" + context.autoValueClass().getSimpleName())); + } catch (ValidationException ve) { + env.printError(ve.getElement(), ve.getMessage()); + } catch (Exception e) { + env.printError(typeElement, "Internal processor error:\n" + e.getMessage()); e.printStackTrace(); } } diff --git a/compiller/src/main/java/com/yheriatovych/reductor/processor/Utils.java b/compiller/src/main/java/com/yheriatovych/reductor/processor/Utils.java index 7d7cdd9..8dfb04a 100644 --- a/compiller/src/main/java/com/yheriatovych/reductor/processor/Utils.java +++ b/compiller/src/main/java/com/yheriatovych/reductor/processor/Utils.java @@ -57,22 +57,4 @@ public static R reduce(List list, R initialValue, Func2 func) return result; } - static String getDefaultValue(TypeKind kind) { - switch (kind) { - case BOOLEAN: - return "false"; - case BYTE: - case SHORT: - case INT: - case LONG: - case FLOAT: - case DOUBLE: - return "0"; - case CHAR: - return "'\\u0000'"; - default: - return "null"; - } - } - } diff --git a/compiller/src/test/java/CombinedStateReducerTest.java b/compiller/src/test/java/CombinedStateReducerTest.java index 1fb04f9..b973ddc 100644 --- a/compiller/src/test/java/CombinedStateReducerTest.java +++ b/compiller/src/test/java/CombinedStateReducerTest.java @@ -1,3 +1,4 @@ +import com.google.auto.value.processor.AutoValueProcessor; import com.google.testing.compile.JavaFileObjects; import com.yheriatovych.reductor.processor.CombinedStateProcessor; import org.junit.Test; @@ -137,16 +138,16 @@ public void testBoxPrimitives() { "\n" + " @Override\n" + " public Foobar reduce(Foobar state, Action action) {\n" + - " int foo = 0;\n" + - " boolean bar = false;\n" + + " Integer foo = null;\n" + + " Boolean bar = null;\n" + "\n" + " if (state != null) {\n" + " foo = state.foo();\n" + " bar = state.bar();\n" + " }\n" + "\n" + - " int fooNext = fooReducer.reduce(foo, action);\n" + - " boolean barNext = barReducer.reduce(bar, action);\n" + + " Integer fooNext = fooReducer.reduce(foo, action);\n" + + " Boolean barNext = barReducer.reduce(bar, action);\n" + "\n" + " //If all values are the same there is no need to create an object\n" + " if (state != null\n" + @@ -199,6 +200,105 @@ public void testBoxPrimitives() { .generatesSources(generatedPojo); } + @Test + public void testAutoValueReducerGeneration() { + JavaFileObject source = JavaFileObjects.forSourceString("test.Foobar", "package test;\n" + + "\n" + + "import com.google.auto.value.AutoValue;\n" + + "import com.yheriatovych.reductor.annotations.CombinedState;\n" + + "import java.util.Date;\n" + + "\n" + + "@CombinedState\n" + + "@AutoValue\n" + + "public abstract class Foobar {\n" + + " abstract String foo();\n" + + " abstract Date bar();\n" + + "}"); + + JavaFileObject generatedPojo = JavaFileObjects.forSourceString("test.Foobar", "package test;\n" + + "\n" + + "import com.yheriatovych.reductor.Action;\n" + + "import com.yheriatovych.reductor.Reducer;\n" + + "import java.lang.IllegalStateException;\n" + + "import java.lang.Override;\n" + + "import java.lang.String;\n" + + "import java.util.Date;\n" + + "\n" + + "public final class FoobarReducer implements Reducer {\n" + + " private final Reducer fooReducer;\n" + + "\n" + + " private final Reducer barReducer;\n" + + "\n" + + " private FoobarReducer(Reducer fooReducer, Reducer barReducer) {\n" + + " this.fooReducer = fooReducer;\n" + + " this.barReducer = barReducer;\n" + + " }\n" + + "\n" + + " @Override\n" + + " public Foobar reduce(Foobar state, Action action) {\n" + + " String foo = null;\n" + + " Date bar = null;\n" + + "\n" + + " if (state != null) {\n" + + " foo = state.foo();\n" + + " bar = state.bar();\n" + + " }\n" + + "\n" + + " String fooNext = fooReducer.reduce(foo, action);\n" + + " Date barNext = barReducer.reduce(bar, action);\n" + + "\n" + + " //If all values are the same there is no need to create an object\n" + + " if (state != null\n" + + " && foo == fooNext\n" + + " && bar == barNext) {\n" + + " return state;\n" + + " } else {\n" + + " return new AutoValue_Foobar(fooNext, barNext);\n" + + " }\n" + + " }\n" + + "\n" + + " public static Builder builder() {\n" + + " return new Builder();\n" + + " }\n" + + "\n" + + " public static class Builder {\n" + + " private Reducer fooReducer;\n" + + "\n" + + " private Reducer barReducer;\n" + + "\n" + + " private Builder() {\n" + + " }\n" + + "\n" + + " public Builder fooReducer(Reducer fooReducer) {\n" + + " this.fooReducer = fooReducer;\n" + + " return this;\n" + + " }\n" + + "\n" + + " public Builder barReducer(Reducer barReducer) {\n" + + " this.barReducer = barReducer;\n" + + " return this;\n" + + " }\n" + + "\n" + + " public FoobarReducer build() {\n" + + " if (fooReducer == null) {\n" + + " throw new IllegalStateException(\"fooReducer should not be null\");\n" + + " }\n" + + " if (barReducer == null) {\n" + + " throw new IllegalStateException(\"barReducer should not be null\");\n" + + " }\n" + + " return new FoobarReducer(fooReducer, barReducer);\n" + + " }\n" + + " }\n" + + "}"); + + assertAbout(javaSource()).that(source) + .withCompilerOptions("-Xlint:-processing") + .processedWith(new AutoValueProcessor()) + .compilesWithoutWarnings() + .and() + .generatesSources(generatedPojo); + } + @Test public void testInitialValues() { JavaFileObject source = JavaFileObjects.forSourceString("test.Foobar", "package test;\n" + @@ -247,37 +347,37 @@ public void testInitialValues() { "\n" + " @Override\n" + " public Foobar reduce(Foobar state, Action action) {\n" + - " int intValue = 0;\n" + - " double doubleValue = 0;\n" + - " boolean booleanValue = false;\n" + - " char charValue = '\\u0000';\n" + - " Object objectValue = null;\n" + - "\n" + - " if (state != null) {\n" + - " intValue = state.intValue();\n" + - " doubleValue = state.doubleValue();\n" + - " booleanValue = state.booleanValue();\n" + - " charValue = state.charValue();\n" + - " objectValue = state.objectValue();\n" + - " }\n" + + " Integer intValue = null;\n" + + " Double doubleValue = null;\n" + + " Boolean booleanValue = null;\n" + + " Character charValue = null;\n" + + " Object objectValue = null;\n" + "\n" + - " int intValueNext = intValueReducer.reduce(intValue, action);\n" + - " double doubleValueNext = doubleValueReducer.reduce(doubleValue, action);\n" + - " boolean booleanValueNext = booleanValueReducer.reduce(booleanValue, action);\n" + - " char charValueNext = charValueReducer.reduce(charValue, action);\n" + - " Object objectValueNext = objectValueReducer.reduce(objectValue, action);\n" + - "\n" + - " //If all values are the same there is no need to create an object\n" + - " if (state != null\n" + - " && intValue == intValueNext\n" + - " && doubleValue == doubleValueNext\n" + - " && booleanValue == booleanValueNext\n" + - " && charValue == charValueNext\n" + - " && objectValue == objectValueNext) {\n" + - " return state;\n" + - " } else {\n" + - " return new FoobarImpl(intValueNext, doubleValueNext, booleanValueNext, charValueNext, objectValueNext);\n" + - " }\n" + + " if (state != null) {\n" + + " intValue = state.intValue();\n" + + " doubleValue = state.doubleValue();\n" + + " booleanValue = state.booleanValue();\n" + + " charValue = state.charValue();\n" + + " objectValue = state.objectValue();\n" + + " }\n" + + "\n" + + " Integer intValueNext = intValueReducer.reduce(intValue, action);\n" + + " Double doubleValueNext = doubleValueReducer.reduce(doubleValue, action);\n" + + " Boolean booleanValueNext = booleanValueReducer.reduce(booleanValue, action);\n" + + " Character charValueNext = charValueReducer.reduce(charValue, action);\n" + + " Object objectValueNext = objectValueReducer.reduce(objectValue, action);\n" + + "\n" + + " //If all values are the same there is no need to create an object\n" + + " if (state != null\n" + + " && intValue == intValueNext\n" + + " && doubleValue == doubleValueNext\n" + + " && booleanValue == booleanValueNext\n" + + " && charValue == charValueNext\n" + + " && objectValue == objectValueNext) {\n" + + " return state;\n" + + " } else {\n" + + " return new FoobarImpl(intValueNext, doubleValueNext, booleanValueNext, charValueNext, objectValueNext);\n" + + " }\n" + " }\n" + "\n" + " public static Builder builder() {\n" + @@ -341,7 +441,8 @@ public void testInitialValues() { " }\n" + " return new FoobarReducer(intValueReducer, doubleValueReducer, booleanValueReducer, charValueReducer, objectValueReducer);\n" + " }\n" + - " }"); + " }\n" + + "}"); assertAbout(javaSource()).that(source) .processedWith(new CombinedStateProcessor()) diff --git a/compiller/src/test/java/CombinedStateValidationTest.java b/compiller/src/test/java/CombinedStateValidationTest.java index 87a1d3d..c1f19a6 100644 --- a/compiller/src/test/java/CombinedStateValidationTest.java +++ b/compiller/src/test/java/CombinedStateValidationTest.java @@ -87,7 +87,7 @@ public void testErrorOnClassAnnotated() { assertAbout(javaSource()).that(source) .processedWith(new CombinedStateProcessor()) .failsToCompile() - .withErrorContaining("only interfaces supported as ") + .withErrorContaining("Only interfaces and @AutoValue classes are supported as @CombinedState") .in(source) .onLine(6); } From 96f7aca3a0dadadc731882e2b23b8d95ef8f3f11 Mon Sep 17 00:00:00 2001 From: Yaroslav Heriatovych Date: Tue, 11 Oct 2016 11:11:02 +0100 Subject: [PATCH 4/5] use AutoValue class as @CombinedState in example project --- example/build.gradle | 10 ++++++++-- .../reductor/example/MyAdapterFactory.java | 11 +++++++++++ .../yheriatovych/reductor/example/ReductorApp.java | 9 ++++++--- .../reductor/example/model/AppState.java | 14 +++++++++++--- 4 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 example/src/main/java/com/yheriatovych/reductor/example/MyAdapterFactory.java diff --git a/example/build.gradle b/example/build.gradle index b17eba5..38d0d6d 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -40,11 +40,17 @@ dependencies { compile project(':lib') compile project(':rx-store') testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:24.2.0' - compile 'com.android.support:recyclerview-v7:24.2.0' + compile 'com.android.support:appcompat-v7:24.2.1' + compile 'com.android.support:recyclerview-v7:24.2.1' compile 'org.pcollections:pcollections:2.1.2' compile 'com.facebook.stetho:stetho:1.3.1' compile 'com.facebook.stetho:stetho-js-rhino:1.3.1' compile 'com.google.code.gson:gson:2.7' + + + provided 'com.google.auto.value:auto-value:1.3' + apt 'com.google.auto.value:auto-value:1.3' + provided 'com.ryanharter.auto.value:auto-value-gson:0.4.2' + apt 'com.ryanharter.auto.value:auto-value-gson:0.4.2' } diff --git a/example/src/main/java/com/yheriatovych/reductor/example/MyAdapterFactory.java b/example/src/main/java/com/yheriatovych/reductor/example/MyAdapterFactory.java new file mode 100644 index 0000000..4a72fc8 --- /dev/null +++ b/example/src/main/java/com/yheriatovych/reductor/example/MyAdapterFactory.java @@ -0,0 +1,11 @@ +package com.yheriatovych.reductor.example; + +import com.google.gson.TypeAdapterFactory; +import com.ryanharter.auto.value.gson.GsonTypeAdapterFactory; + +@GsonTypeAdapterFactory +public abstract class MyAdapterFactory implements TypeAdapterFactory { + public static TypeAdapterFactory create() { + return new AutoValueGson_MyAdapterFactory(); + } +} diff --git a/example/src/main/java/com/yheriatovych/reductor/example/ReductorApp.java b/example/src/main/java/com/yheriatovych/reductor/example/ReductorApp.java index 872995e..fda5bb1 100644 --- a/example/src/main/java/com/yheriatovych/reductor/example/ReductorApp.java +++ b/example/src/main/java/com/yheriatovych/reductor/example/ReductorApp.java @@ -2,14 +2,15 @@ import android.app.Application; import android.os.Handler; +import android.support.annotation.NonNull; import android.util.Log; import com.facebook.stetho.Stetho; import com.facebook.stetho.inspector.console.RuntimeReplFactory; import com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.yheriatovych.reductor.Store; import com.yheriatovych.reductor.example.model.AppState; -import com.yheriatovych.reductor.example.model.AppStateImpl; import com.yheriatovych.reductor.example.model.AppStateReducer; import com.yheriatovych.reductor.example.reducers.NotesFilterReducerImpl; import com.yheriatovych.reductor.example.reducers.NotesListReducer; @@ -23,7 +24,9 @@ public class ReductorApp extends Application { public Store store; - Gson gson = new Gson(); + Gson gson = new GsonBuilder() + .registerTypeAdapterFactory(MyAdapterFactory.create()) + .create(); @Override public void onCreate() { @@ -64,7 +67,7 @@ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] ar Function stringifyFunction = (Function) json.get("stringify", scope); String jsonString = (String) stringifyFunction.call(cx, json, scope, new Object[]{args[0]}); - final AppState arg = gson.fromJson(jsonString, AppStateImpl.class); + final AppState arg = gson.fromJson(jsonString, AppState.class); Log.d("ReductorApp", arg.toString()); handler.post(() -> store.dispatch(SetStateReducer.setStateAction(arg))); return arg; diff --git a/example/src/main/java/com/yheriatovych/reductor/example/model/AppState.java b/example/src/main/java/com/yheriatovych/reductor/example/model/AppState.java index 3fb24e1..af86e33 100644 --- a/example/src/main/java/com/yheriatovych/reductor/example/model/AppState.java +++ b/example/src/main/java/com/yheriatovych/reductor/example/model/AppState.java @@ -1,12 +1,20 @@ package com.yheriatovych.reductor.example.model; +import com.google.auto.value.AutoValue; +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; import com.yheriatovych.reductor.annotations.CombinedState; import java.util.List; @CombinedState -public interface AppState { - List notes(); +@AutoValue +public abstract class AppState { + public abstract List notes(); - NotesFilter filter(); + public abstract NotesFilter filter(); + + public static TypeAdapter typeAdapter(Gson gson) { + return new AutoValue_AppState.GsonTypeAdapter(gson); + } } From 18fcaafd9fa0539d81e131290e10d9b7c5518d9f Mon Sep 17 00:00:00 2001 From: Yaroslav Heriatovych Date: Tue, 11 Oct 2016 11:27:07 +0100 Subject: [PATCH 5/5] bumped version to 0.9.2, add changes for this version --- CHANGES.md | 11 +++++++++++ build.gradle | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index e3b25aa..96d6618 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,16 @@ # Reductor Releases # +### Version 0.9.2 - October 11, 2016 +#### New Features + - Big update on `@CombinedState`. + Now `@AutoValue` value classes are supported as combined state! + Interfaces as combined state are still supported. + +#### Other improvements +Update code generator for `@CombinedState` reducers. + - Remove unnecessary state object allocation if all sub-states are the same. + - Use boxed version of sub-state types in reducer, to remove boxing/unboxing when passing to sub-reducers. + ### Version 0.9.1 - October 10, 2016 - Rename `reductor-rx` maven artifact to `reductor-rxjava` - Updated `rxjava` version to 1.2.1 diff --git a/build.gradle b/build.gradle index a26aec3..81bce15 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { project.ext { bintrayUser = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : "" bintrayKey = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : "" - reductorVersion = '0.9.1' + reductorVersion = '0.9.2' }