Skip to content

Commit

Permalink
Merge pull request #4 from Yarikx/auto_value
Browse files Browse the repository at this point in the history
Auto value support for @CombinedState reducers
  • Loading branch information
Yarikx authored Oct 11, 2016
2 parents 61f94ad + 18fcaaf commit b6db3d1
Show file tree
Hide file tree
Showing 14 changed files with 316 additions and 89 deletions.
11 changes: 11 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}


Expand Down
2 changes: 2 additions & 0 deletions compiller/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.yheriatovych.reductor.processor;

import com.google.auto.service.AutoService;
import com.google.auto.value.extension.AutoValueExtension;
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.TypeElement;

@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;
}

@Override
public boolean applicable(Context context) {
TypeElement typeElement = context.autoValueClass();
boolean isApplicable = typeElement.getAnnotation(CombinedState.class) != null;
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());
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();
}
}

return false;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ public boolean process(Set<? extends TypeElement> 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) {
Expand Down Expand Up @@ -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";

Expand All @@ -106,8 +108,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)
Expand All @@ -122,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 $N = $N.reduce(state.$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();

Expand All @@ -132,13 +133,13 @@ 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, 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")
Expand All @@ -161,45 +162,47 @@ public CodeBlock.Builder call(CodeBlock.Builder builder, StateProperty property)
javaFile.writeTo(env.getFiler());
}

private CodeBlock emitInitialValueBlock(ClassName stateClassName, List<StateProperty> properties) {
String defaultArgs = join(", ", map(properties, new Utils.Func1<StateProperty, String>() {
@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 static CodeBlock emitDestructuringBlock(List<StateProperty> properties, Env env) {
CodeBlock.Builder destructuringBlock = CodeBlock.builder();
for (StateProperty property : properties) {
destructuringBlock.addStatement("$T $N = null", property.boxedStateType(env), property.name);
}

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<StateProperty> properties) {
private static CodeBlock emitReturnBlock(ClassName stateClassName, List<StateProperty> properties) {
String equalsCondition = join("\n && ",
map(properties, new Utils.Func1<StateProperty, String>() {
@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<StateProperty, String>() {
@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)
.endControlFlow()
.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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,4 @@ public static <T, R> R reduce(List<T> list, R initialValue, Func2<R, T, R> 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";
}
}

}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -22,23 +25,43 @@ public CombinedStateElement(TypeElement stateTypeElement, List<StateProperty> 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<StateProperty> 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<StateProperty> 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<String, ExecutableElement> autoValueProperties) throws ValidationException {
List<StateProperty> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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;
Expand Down
Loading

0 comments on commit b6db3d1

Please sign in to comment.