diff --git a/.travis.yml b/.travis.yml
index f0845000..110ecffd 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,6 @@
language: java
jdk:
- oraclejdk8
- - oraclejdk7
- - openjdk7
script:
- mvn test
- "./jackson-it.sh"
diff --git a/pom.xml b/pom.xml
index efdc7f19..29b34e90 100644
--- a/pom.xml
+++ b/pom.xml
@@ -60,8 +60,8 @@
3.3
false
- 1.7
- 1.7
+ 1.8
+ 1.8
-Xlint:all
diff --git a/processor/src/main/java/io/norberg/automatter/processor/AutoMatterProcessor.java b/processor/src/main/java/io/norberg/automatter/processor/AutoMatterProcessor.java
index cd49bde7..f261fc69 100644
--- a/processor/src/main/java/io/norberg/automatter/processor/AutoMatterProcessor.java
+++ b/processor/src/main/java/io/norberg/automatter/processor/AutoMatterProcessor.java
@@ -2,14 +2,14 @@
import com.google.auto.service.AutoService;
import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.squareup.javapoet.AnnotationSpec;
-import com.squareup.javapoet.ArrayTypeName;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
-import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
@@ -19,16 +19,8 @@
import com.squareup.javapoet.TypeVariableName;
import com.squareup.javapoet.WildcardTypeName;
-import org.modeshape.common.text.Inflector;
-
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -43,10 +35,8 @@
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
-import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
@@ -54,15 +44,14 @@
import io.norberg.automatter.AutoMatter;
-import static com.google.common.base.Preconditions.checkArgument;
import static com.squareup.javapoet.WildcardTypeName.subtypeOf;
+import static io.norberg.automatter.processor.Common.builderType;
+import static io.norberg.automatter.processor.Common.rawBuilderType;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
import static javax.lang.model.type.TypeKind.ARRAY;
-import static javax.lang.model.type.TypeKind.DECLARED;
-import static javax.lang.model.type.TypeKind.TYPEVAR;
import static javax.tools.Diagnostic.Kind.ERROR;
/**
@@ -72,20 +61,14 @@
@AutoService(Processor.class)
public final class AutoMatterProcessor extends AbstractProcessor {
- private static final Inflector INFLECTOR = new Inflector();
-
- private static final Set KEYWORDS = ImmutableSet.of(
- "abstract", "continue", "for", "new", "switch", "assert", "default", "if", "package",
- "synchronized", "boolean", "do", "goto", "private", "this", "break", "double", "implements",
- "protected", "throw", "byte", "else", "import", "public", "throws", "case", "enum",
- "instanceof", "return", "transient", "catch", "extends", "int", "short", "try", "char",
- "final", "interface", "static", "void", "class", "finally", "long", "strictfp", "volatile",
- "const", "float", "native", "super", "while");
+ private static final Splitter FIELD_SPLITTER = Splitter.on("<");
private Filer filer;
private Elements elements;
private Messager messager;
private Types types;
+ private Map processors;
+ private final DefaultProcessor defaultProcessor = new DefaultProcessor();
@Override
@@ -94,7 +77,18 @@ public synchronized void init(final ProcessingEnvironment processingEnv) {
filer = processingEnv.getFiler();
elements = processingEnv.getElementUtils();
types = processingEnv.getTypeUtils();
- this.messager = processingEnv.getMessager();
+ messager = processingEnv.getMessager();
+
+ CollectionProcessor collectionProcessor = new CollectionProcessor(elements);
+ OptionalProcessor optionalProcessor = new OptionalProcessor();
+
+ processors = ImmutableMap.of(
+ "java.util.Map", collectionProcessor,
+ "java.util.List", collectionProcessor,
+ "java.util.Set", collectionProcessor,
+ "java.util.Optional", optionalProcessor,
+ "com.google.common.base.Optional", optionalProcessor
+ );
}
@Override
@@ -123,6 +117,11 @@ private void process(final Element element) throws IOException, AutoMatterProces
javaFile.writeTo(filer);
}
+ private FieldProcessor processorForField(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException {
+ String prefix = FIELD_SPLITTER.split(fieldType(d, field).toString()).iterator().next();
+ return processors.getOrDefault(prefix, defaultProcessor);
+ }
+
private TypeSpec builder(final Descriptor d) throws AutoMatterProcessorException {
AnnotationSpec generatedAnnotation = AnnotationSpec.builder(Generated.class)
.addMember("value", "$S", AutoMatterProcessor.class.getName())
@@ -138,7 +137,7 @@ private TypeSpec builder(final Descriptor d) throws AutoMatterProcessorException
}
for (ExecutableElement field : d.fields()) {
- builder.addField(FieldSpec.builder(fieldType(d, field), fieldName(field), PRIVATE).build());
+ builder.addField(processorForField(d, field).builderField(d, field));
}
builder.addMethod(defaultConstructor(d));
@@ -162,14 +161,13 @@ private TypeSpec builder(final Descriptor d) throws AutoMatterProcessorException
return builder.build();
}
- private MethodSpec defaultConstructor(final Descriptor d) {
+ private MethodSpec defaultConstructor(final Descriptor d) throws AutoMatterProcessorException {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addModifiers(PUBLIC);
for (ExecutableElement field : d.fields()) {
- if (isOptional(field) && shouldEnforceNonNull(field)) {
- ClassName type = ClassName.bestGuess(optionalType(field));
- constructor.addStatement("this.$N = $T.$L()", fieldName(field), type, optionalEmptyName(field));
+ for (Statement s : processorForField(d, field).defaultConstructor(d, field)) {
+ constructor.addStatement(s.statement, s.args);
}
}
@@ -182,64 +180,22 @@ private MethodSpec copyValueConstructor(final Descriptor d) throws AutoMatterPro
.addParameter(upperBoundedValueType(d), "v");
for (ExecutableElement field : d.fields()) {
- String fieldName = fieldName(field);
-
- if (isCollection(field) || isMap(field)) {
- TypeName fieldType = upperBoundedFieldType(field);
- constructor.addStatement("$T _$N = v.$N()", fieldType, fieldName, fieldName);
- constructor.addStatement(
- "this.$N = (_$N == null) ? null : new $T(_$N)",
- fieldName, fieldName, collectionImplType(field), fieldName);
- } else {
- if (isFieldTypeParameterized(field)) {
- TypeName fieldType = fieldType(d, field);
- constructor.addStatement("@SuppressWarnings(\"unchecked\") $T _$N = ($T) v.$N()",
- fieldType, fieldName, fieldType, fieldName);
- constructor.addStatement("this.$N = _$N", fieldName, fieldName);
- } else {
- constructor.addStatement("this.$N = v.$N()", fieldName, fieldName);
- }
+ for (Statement s : processorForField(d, field).copyValueConstructor(d, field)) {
+ constructor.addStatement(s.statement, s.args);
}
}
return constructor.build();
}
- private boolean isFieldTypeParameterized(final ExecutableElement field) {
- final TypeMirror returnType = field.getReturnType();
- if (returnType.getKind() != DECLARED) {
- return false;
- }
- final DeclaredType declaredType = (DeclaredType) returnType;
- for (final TypeMirror typeArgument : declaredType.getTypeArguments()) {
- if (typeArgument.getKind() == TYPEVAR) {
- return true;
- }
- }
- return false;
- }
-
private MethodSpec copyBuilderConstructor(final Descriptor d) throws AutoMatterProcessorException {
final MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addModifiers(PRIVATE)
.addParameter(upperBoundedBuilderType(d), "v");
for (ExecutableElement field : d.fields()) {
- String fieldName = fieldName(field);
-
- if (isCollection(field) || isMap(field)) {
- constructor.addStatement(
- "this.$N = (v.$N == null) ? null : new $T(v.$N)",
- fieldName, fieldName, collectionImplType(field), fieldName);
- } else {
- if (isFieldTypeParameterized(field)) {
- TypeName fieldType = fieldType(d, field);
- constructor.addStatement("@SuppressWarnings(\"unchecked\") $T _$N = ($T) v.$N()",
- fieldType, fieldName, fieldType, fieldName);
- constructor.addStatement("this.$N = _$N", fieldName, fieldName);
- } else {
- constructor.addStatement("this.$N = v.$N", fieldName, fieldName);
- }
+ for (Statement s : processorForField(d, field).copyBuilderConstructor(d, field)) {
+ constructor.addStatement(s.statement, s.args);
}
}
@@ -249,386 +205,13 @@ private MethodSpec copyBuilderConstructor(final Descriptor d) throws AutoMatterP
private Set accessors(final Descriptor d) throws AutoMatterProcessorException {
ImmutableSet.Builder result = ImmutableSet.builder();
for (ExecutableElement field : d.fields()) {
- result.add(getter(d, field));
-
- if (isOptional(field)) {
- result.add(optionalRawSetter(d, field));
- result.add(optionalSetter(d, field));
- } else if (isCollection(field)) {
- result.add(collectionSetter(d, field));
- result.add(collectionCollectionSetter(d, field));
- result.add(collectionIterableSetter(d, field));
- result.add(collectionIteratorSetter(d, field));
- result.add(collectionVarargSetter(d, field));
-
- MethodSpec adder = collectionAdder(d, field);
- if (adder != null) {
- result.add(adder);
- }
- } else if (isMap(field)) {
- result.add(mapSetter(d, field));
- for (int i = 1; i <= 5; i++) {
- result.add(mapSetterPairs(d, field, i));
- }
-
- MethodSpec putter = mapPutter(d, field);
- if (putter != null) {
- result.add(putter);
- }
- } else {
- result.add(setter(d, field));
+ for (MethodSpec method: processorForField(d, field).accessors(d, field)) {
+ result.add(method);
}
}
return result.build();
}
- private MethodSpec getter(final Descriptor d, final ExecutableElement field) throws AutoMatterProcessorException {
- String fieldName = fieldName(field);
-
- MethodSpec.Builder getter = MethodSpec.methodBuilder(fieldName)
- .addModifiers(PUBLIC)
- .returns(fieldType(d, field));
-
- if ((isCollection(field) || isMap(field)) && shouldEnforceNonNull(field)) {
- getter.beginControlFlow("if (this.$N == null)", fieldName)
- .addStatement("this.$N = new $T()", fieldName, collectionImplType(field))
- .endControlFlow();
- }
- getter.addStatement("return $N", fieldName);
-
- return getter.build();
- }
-
- private MethodSpec optionalRawSetter(final Descriptor d, final ExecutableElement field) {
- String fieldName = fieldName(field);
- ClassName type = ClassName.bestGuess(optionalType(field));
- TypeName valueType = genericArgument(field, 0);
-
- return MethodSpec.methodBuilder(fieldName)
- .addModifiers(PUBLIC)
- .addParameter(valueType, fieldName)
- .returns(builderType(d))
- .addStatement("return $N($T.$N($N))", fieldName, type, optionalMaybeName(field), fieldName)
- .build();
- }
-
- private MethodSpec optionalSetter(final Descriptor d, final ExecutableElement field)
- throws AutoMatterProcessorException {
- String fieldName = fieldName(field);
- TypeName valueType = genericArgument(field, 0);
- ClassName optionalType = ClassName.bestGuess(optionalType(field));
- TypeName parameterType = ParameterizedTypeName.get(optionalType, subtypeOf(valueType));
-
- AnnotationSpec suppressUncheckedAnnotation = AnnotationSpec.builder(SuppressWarnings.class)
- .addMember("value", "$S", "unchecked")
- .build();
-
- MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
- .addAnnotation(suppressUncheckedAnnotation)
- .addModifiers(PUBLIC)
- .addParameter(parameterType, fieldName)
- .returns(builderType(d));
-
- if (shouldEnforceNonNull(field)) {
- assertNotNull(setter, fieldName);
- }
-
- setter.addStatement("this.$N = ($T)$N", fieldName, fieldType(d, field), fieldName);
-
- return setter.addStatement("return this").build();
- }
-
- private MethodSpec collectionSetter(final Descriptor d, final ExecutableElement field) {
- String fieldName = fieldName(field);
- ClassName collectionType = collectionRawType(field);
- TypeName itemType = genericArgument(field, 0);
- WildcardTypeName extendedType = subtypeOf(itemType);
-
- return MethodSpec.methodBuilder(fieldName)
- .addModifiers(PUBLIC)
- .addParameter(ParameterizedTypeName.get(collectionType, extendedType), fieldName)
- .returns(builderType(d))
- .addStatement("return $N((Collection<$T>) $N)", fieldName, extendedType, fieldName)
- .build();
- }
-
- private MethodSpec collectionCollectionSetter(final Descriptor d, final ExecutableElement field) {
- String fieldName = fieldName(field);
- ClassName collectionType = ClassName.get(Collection.class);
- TypeName itemType = genericArgument(field, 0);
- WildcardTypeName extendedType = subtypeOf(itemType);
-
- MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
- .addModifiers(PUBLIC)
- .addParameter(ParameterizedTypeName.get(collectionType, extendedType), fieldName)
- .returns(builderType(d));
-
- collectionNullGuard(setter, field);
- if (shouldEnforceNonNull(field)) {
- setter.beginControlFlow("for ($T item : $N)", itemType, fieldName);
- assertNotNull(setter, "item", fieldName + ": null item");
- setter.endControlFlow();
- }
-
- setter.addStatement("this.$N = new $T($N)", fieldName, collectionImplType(field), fieldName);
- return setter.addStatement("return this").build();
- }
-
- private MethodSpec collectionIterableSetter(final Descriptor d, final ExecutableElement field) {
- String fieldName = fieldName(field);
- ClassName iterableType = ClassName.get(Iterable.class);
- TypeName itemType = genericArgument(field, 0);
- WildcardTypeName extendedType = subtypeOf(itemType);
-
- MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
- .addModifiers(PUBLIC)
- .addParameter(ParameterizedTypeName.get(iterableType, extendedType), fieldName)
- .returns(builderType(d));
-
- collectionNullGuard(setter, field);
-
- ClassName collectionType = ClassName.get(Collection.class);
- setter.beginControlFlow("if ($N instanceof $T)", fieldName, collectionType)
- .addStatement("return $N(($T<$T>) $N)", fieldName, collectionType, extendedType, fieldName)
- .endControlFlow();
-
- setter.addStatement("return $N($N.iterator())", fieldName, fieldName);
- return setter.build();
- }
-
- private MethodSpec collectionIteratorSetter(final Descriptor d, final ExecutableElement field) {
- String fieldName = fieldName(field);
- ClassName iteratorType = ClassName.get(Iterator.class);
- TypeName itemType = genericArgument(field, 0);
- WildcardTypeName extendedType = subtypeOf(itemType);
-
- MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
- .addModifiers(PUBLIC)
- .addParameter(ParameterizedTypeName.get(iteratorType, extendedType), fieldName)
- .returns(builderType(d));
-
- collectionNullGuard(setter, field);
-
- setter.addStatement("this.$N = new $T()", fieldName, collectionImplType(field))
- .beginControlFlow("while ($N.hasNext())", fieldName)
- .addStatement("$T item = $N.next()", itemType, fieldName);
-
- if (shouldEnforceNonNull(field)) {
- assertNotNull(setter, "item", fieldName + ": null item");
- }
-
- setter.addStatement("this.$N.add(item)", fieldName)
- .endControlFlow();
-
- return setter.addStatement("return this").build();
- }
-
- private MethodSpec collectionVarargSetter(final Descriptor d, final ExecutableElement field) {
- String fieldName = fieldName(field);
- TypeName itemType = genericArgument(field, 0);
-
- MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
- .addModifiers(PUBLIC)
- .addParameter(ArrayTypeName.of(itemType), fieldName)
- .varargs()
- .returns(builderType(d));
-
- ensureSafeVarargs(setter);
-
- collectionNullGuard(setter, field);
-
- setter.addStatement("return $N($T.asList($N))", fieldName, ClassName.get(Arrays.class), fieldName);
- return setter.build();
- }
-
- private void ensureSafeVarargs(MethodSpec.Builder setter) {
- // TODO: Add SafeVarargs annotation only for non-reifiable types.
- AnnotationSpec safeVarargsAnnotation = AnnotationSpec.builder(SafeVarargs.class).build();
-
- setter
- .addAnnotation(safeVarargsAnnotation)
- .addModifiers(FINAL); // Only because SafeVarargs can be applied to final methods.
- }
-
- private MethodSpec collectionAdder(final Descriptor d, final ExecutableElement field) {
- final String fieldName = fieldName(field);
- final String singular = singular(fieldName);
- if (singular == null || singular.isEmpty()) {
- return null;
- }
-
- final String appendMethodName = "add" + capitalizeFirstLetter(singular);
- final TypeName itemType = genericArgument(field, 0);
- MethodSpec.Builder adder = MethodSpec.methodBuilder(appendMethodName)
- .addModifiers(PUBLIC)
- .addParameter(itemType, singular)
- .returns(builderType(d));
-
- if (shouldEnforceNonNull(field)) {
- assertNotNull(adder, singular);
- }
- lazyCollectionInitialization(adder, field);
-
- adder.addStatement("$L.add($L)", fieldName, singular);
- return adder.addStatement("return this").build();
- }
-
- private void collectionNullGuard(final MethodSpec.Builder spec, final ExecutableElement field) {
- String fieldName = fieldName(field);
- if (shouldEnforceNonNull(field)) {
- assertNotNull(spec, fieldName);
- } else {
- spec.beginControlFlow("if ($N == null)", fieldName)
- .addStatement("this.$N = null", fieldName)
- .addStatement("return this")
- .endControlFlow();
- }
- }
-
- private void lazyCollectionInitialization(final MethodSpec.Builder spec, final ExecutableElement field) {
- final String fieldName = fieldName(field);
- spec.beginControlFlow("if (this.$N == null)", fieldName)
- .addStatement("this.$N = new $T()", fieldName, collectionImplType(field))
- .endControlFlow();
- }
-
- private MethodSpec mapSetter(final Descriptor d, final ExecutableElement field) {
- final String fieldName = fieldName(field);
- final TypeName keyType = subtypeOf(genericArgument(field, 0));
- final TypeName valueType = subtypeOf(genericArgument(field, 1));
- final TypeName paramType = ParameterizedTypeName.get(ClassName.get(Map.class), keyType, valueType);
-
- MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
- .addModifiers(PUBLIC)
- .addParameter(paramType, fieldName)
- .returns(builderType(d));
-
- if (shouldEnforceNonNull(field)) {
- final String entryName = variableName("entry", fieldName);
- assertNotNull(setter, fieldName);
- setter.beginControlFlow(
- "for ($T<$T, $T> $L : $N.entrySet())",
- ClassName.get(Map.Entry.class), keyType, valueType, entryName, fieldName);
- assertNotNull(setter, entryName + ".getKey()", fieldName + ": null key");
- assertNotNull(setter, entryName + ".getValue()", fieldName + ": null value");
- setter.endControlFlow();
- } else {
- setter.beginControlFlow("if ($N == null)", fieldName)
- .addStatement("this.$N = null", fieldName)
- .addStatement("return this")
- .endControlFlow();
- }
-
- setter.addStatement("this.$N = new $T($N)", fieldName, collectionImplType(field), fieldName);
-
- return setter.addStatement("return this").build();
- }
-
- private MethodSpec mapSetterPairs(final Descriptor d, final ExecutableElement field, int entries) {
- checkArgument(entries > 0, "entries");
- final String fieldName = fieldName(field);
- final TypeName keyType = genericArgument(field, 0);
- final TypeName valueType = genericArgument(field, 1);
-
- MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
- .addModifiers(PUBLIC)
- .returns(builderType(d));
-
- for (int i = 1; i < entries + 1; i++) {
- setter.addParameter(keyType, "k" + i);
- setter.addParameter(valueType, "v" + i);
- }
-
- // Recursion
- if (entries > 1) {
- final List recursionParameters = Lists.newArrayList();
- for (int i = 1; i < entries; i++) {
- recursionParameters.add("k" + i);
- recursionParameters.add("v" + i);
- }
- setter.addStatement("$L($L)", fieldName, Joiner.on(", ").join(recursionParameters));
- }
-
- // Null checks
- final String keyName = "k" + entries;
- final String valueName = "v" + entries;
- if (shouldEnforceNonNull(field)) {
- assertNotNull(setter, keyName, fieldName + ": " + keyName);
- assertNotNull(setter, valueName, fieldName + ": " + valueName);
- }
-
- // Map instantiation
- if (entries == 1) {
- setter.addStatement("$N = new $T()", fieldName, collectionImplType(field));
- }
-
- // Put
- setter.addStatement("$N.put($N, $N)", fieldName, keyName, valueName);
-
- return setter.addStatement("return this").build();
- }
-
- private MethodSpec mapPutter(final Descriptor d, final ExecutableElement field) {
- final String fieldName = fieldName(field);
- final String singular = singular(fieldName);
- if (singular == null) {
- return null;
- }
-
- final String putSingular = "put" + capitalizeFirstLetter(singular);
- final TypeName keyType = genericArgument(field, 0);
- final TypeName valueType = genericArgument(field, 1);
-
- MethodSpec.Builder setter = MethodSpec.methodBuilder(putSingular)
- .addModifiers(PUBLIC)
- .addParameter(keyType, "key")
- .addParameter(valueType, "value")
- .returns(builderType(d));
-
- // Null checks
- if (shouldEnforceNonNull(field)) {
- assertNotNull(setter, "key", singular + ": key");
- assertNotNull(setter, "value", singular + ": value");
- }
-
- // Put
- lazMapInitialization(setter, field);
- setter.addStatement("$N.put(key, value)", fieldName);
-
- return setter.addStatement("return this").build();
- }
-
- private void lazMapInitialization(final MethodSpec.Builder spec, final ExecutableElement field) {
- final String fieldName = fieldName(field);
- spec.beginControlFlow("if (this.$N == null)", fieldName)
- .addStatement("this.$N = new $T()", fieldName, collectionImplType(field))
- .endControlFlow();
- }
-
- private MethodSpec setter(final Descriptor d, final ExecutableElement field) throws AutoMatterProcessorException {
- String fieldName = fieldName(field);
-
- ParameterSpec.Builder parameterSpecBuilder =
- ParameterSpec.builder(fieldType(d, field), fieldName);
- if (!isPrimitive(field)) {
- AnnotationMirror nullableAnnotation = nullableAnnotation(field);
- if (nullableAnnotation != null) {
- parameterSpecBuilder.addAnnotation(AnnotationSpec.get(nullableAnnotation));
- }
- }
- MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
- .addModifiers(PUBLIC)
- .addParameter(parameterSpecBuilder.build())
- .returns(builderType(d));
-
- if (shouldEnforceNonNull(field)) {
- assertNotNull(setter, fieldName);
- }
-
- setter.addStatement("this.$N = $N", fieldName, fieldName);
- return setter.addStatement("return this").build();
- }
-
private MethodSpec toBuilder(final Descriptor d) {
return MethodSpec.methodBuilder("builder")
.addModifiers(PUBLIC)
@@ -644,48 +227,11 @@ private MethodSpec build(final Descriptor d) throws AutoMatterProcessorException
final List parameters = Lists.newArrayList();
for (ExecutableElement field : d.fields()) {
- final String fieldName = fieldName(field);
- final TypeName fieldType = fieldType(d, field);
- final ClassName collections = ClassName.get(Collections.class);
-
- if (isCollection(field)) {
- final TypeName itemType = genericArgument(field, 0);
-
- if (shouldEnforceNonNull(field)) {
- build.addStatement(
- "$T _$L = ($L != null) ? $T.$L(new $T($N)) : $T.<$T>$L()",
- fieldType, fieldName, fieldName,
- collections, unmodifiableCollection(field), collectionImplType(field), fieldName,
- collections, itemType, emptyCollection(field));
- } else {
- build.addStatement(
- "$T _$L = ($L != null) ? $T.$L(new $T($N)) : null",
- fieldType, fieldName, fieldName,
- collections, unmodifiableCollection(field), collectionImplType(field), fieldName);
- }
-
- parameters.add("_" + fieldName);
- } else if (isMap(field)) {
- final TypeName keyType = genericArgument(field, 0);
- final TypeName valueType = genericArgument(field, 1);
-
- if (shouldEnforceNonNull(field)) {
- build.addStatement(
- "$T _$L = ($L != null) ? $T.unmodifiableMap(new $T($N)) : $T.<$T, $T>emptyMap()",
- fieldType, fieldName, fieldName,
- collections, collectionImplType(field), fieldName,
- collections, keyType, valueType);
- } else {
- build.addStatement(
- "$T _$L = ($L != null) ? $T.unmodifiableMap(new $T($N)) : null",
- fieldType, fieldName, fieldName,
- collections, collectionImplType(field), fieldName);
- }
-
- parameters.add("_" + fieldName);
- } else {
- parameters.add(fieldName(field));
+ BuildStatements buildStatements = processorForField(d, field).build(d, field);
+ for (Statement s : buildStatements.statements) {
+ build.addStatement(s.statement, s.args);
}
+ parameters.add(buildStatements.parameter);
}
return build.addStatement("return new $T($N)", valueImplType(d), Joiner.on(", ").join(parameters)).build();
@@ -718,7 +264,7 @@ private TypeSpec valueClass(final Descriptor d) throws AutoMatterProcessorExcept
.addSuperinterface(valueType(d));
for (ExecutableElement field : d.fields()) {
- value.addField(FieldSpec.builder(fieldType(d, field), fieldName(field), PRIVATE, FINAL).build());
+ value.addField(processorForField(d, field).valueField(d, field));
}
value.addMethod(valueConstructor(d));
@@ -754,20 +300,8 @@ private MethodSpec valueConstructor(final Descriptor d) throws AutoMatterProcess
.build();
constructor.addParameter(parameter);
- final ClassName collectionsType = ClassName.get(Collections.class);
- if (shouldEnforceNonNull(field) && isCollection(field)) {
- final TypeName itemType = genericArgument(field, 0);
- constructor.addStatement(
- "this.$N = ($N != null) ? $N : $T.<$T>$L()",
- fieldName, fieldName, fieldName, collectionsType, itemType, emptyCollection(field));
- } else if (shouldEnforceNonNull(field) && isMap(field)) {
- final TypeName keyType = genericArgument(field, 0);
- final TypeName valueType = genericArgument(field, 1);
- constructor.addStatement(
- "this.$N = ($N != null) ? $N : $T.<$T, $T>emptyMap()",
- fieldName, fieldName, fieldName, collectionsType, keyType, valueType);
- } else {
- constructor.addStatement("this.$N = $N", fieldName, fieldName);
+ for (Statement s : processorForField(d, field).valueConstructor(d, field)) {
+ constructor.addStatement(s.statement, s.args);
}
}
@@ -950,14 +484,6 @@ private void assertNotNull(MethodSpec.Builder spec, String name, String msg) {
.endControlFlow();
}
- private TypeName builderType(final Descriptor d) {
- final ClassName raw = rawBuilderType(d);
- if (!d.isGeneric()) {
- return raw;
- }
- return ParameterizedTypeName.get(raw, d.typeArguments());
- }
-
private TypeName upperBoundedBuilderType(final Descriptor d) {
final ClassName raw = rawBuilderType(d);
if (!d.isGeneric()) {
@@ -974,9 +500,6 @@ private TypeName[] upperBounded(final List typeVariables) {
return typeNames;
}
- private ClassName rawBuilderType(final Descriptor d) {
- return ClassName.get(d.packageName(), d.builderName());
- }
private ClassName rawValueType(final Descriptor d) {
return ClassName.get(d.packageName(), d.valueTypeName());
@@ -1036,147 +559,12 @@ private TypeName fieldType(final Descriptor d, final ExecutableElement field) th
return TypeName.get(fieldType);
}
- private TypeName upperBoundedFieldType(final ExecutableElement field) throws AutoMatterProcessorException {
- TypeMirror type = field.getReturnType();
- if (type.getKind() == TypeKind.ERROR) {
- throw fail("Cannot resolve type, might be missing import: " + type, field);
- }
- if (type.getKind() != DECLARED) {
- return TypeName.get(type);
- }
- final DeclaredType declaredType = (DeclaredType) type;
- if (declaredType.getTypeArguments().isEmpty()) {
- return TypeName.get(type);
- }
- final ClassName raw = rawClassName(declaredType);
- if (isOptional(field) || isCollection(field)) {
- final TypeName elementType = TypeName.get(declaredType.getTypeArguments().get(0));
- return ParameterizedTypeName.get(raw, subtypeOf(elementType));
- } else if (isMap(field)) {
- final TypeName keyTypeArgument = TypeName.get(declaredType.getTypeArguments().get(0));
- final TypeName valueTypeArgument = TypeName.get(declaredType.getTypeArguments().get(1));
- return ParameterizedTypeName.get(raw, subtypeOf(keyTypeArgument), subtypeOf(valueTypeArgument));
- }
- return TypeName.get(type);
- }
-
- private ClassName rawClassName(final DeclaredType declaredType) {
- final String simpleName = declaredType.asElement().getSimpleName().toString();
- final String packageName = packageName(declaredType);
- return ClassName.get(packageName, simpleName);
- }
-
- private String packageName(final DeclaredType declaredType) {
- Element type = declaredType.asElement();
- while (type.getKind() != ElementKind.PACKAGE) {
- type = type.getEnclosingElement();
- }
- return type.toString();
- }
-
- private TypeName genericArgument(final ExecutableElement field, int index) {
- final DeclaredType type = (DeclaredType) field.getReturnType();
- checkArgument(type.getTypeArguments().size() >= index);
- return TypeName.get(type.getTypeArguments().get(index));
- }
-
- private TypeName collectionImplType(final ExecutableElement field) {
- switch (collectionType(field)) {
- case "List":
- return ParameterizedTypeName.get(
- ClassName.get(ArrayList.class),
- genericArgument(field, 0));
- case "Set":
- return ParameterizedTypeName.get(
- ClassName.get(HashSet.class),
- genericArgument(field, 0));
- case "Map":
- return ParameterizedTypeName.get(
- ClassName.get(HashMap.class),
- genericArgument(field, 0), genericArgument(field, 1));
- default:
- throw new IllegalStateException("invalid collection type " + field);
- }
- }
-
- private ClassName collectionRawType(final ExecutableElement field) {
- final DeclaredType type = (DeclaredType) field.getReturnType();
- return ClassName.get("java.util", type.asElement().getSimpleName().toString());
- }
-
- private static String optionalEmptyName(final ExecutableElement field) {
- final String returnType = field.getReturnType().toString();
- if (returnType.startsWith("com.google.common.base.Optional<")) {
- return "absent";
- }
- return "empty";
- }
-
- private static String optionalMaybeName(final ExecutableElement field) {
- final String returnType = field.getReturnType().toString();
- if (returnType.startsWith("com.google.common.base.Optional<")) {
- return "fromNullable";
- }
- return "ofNullable";
- }
-
private boolean isCollection(final ExecutableElement field) {
final String returnType = field.getReturnType().toString();
return returnType.startsWith("java.util.List<") ||
returnType.startsWith("java.util.Set<");
}
- private String unmodifiableCollection(final ExecutableElement field) {
- final String type = collectionType(field);
- switch (type) {
- case "List":
- return "unmodifiableList";
- case "Set":
- return "unmodifiableSet";
- case "Map":
- return "unmodifiableMap";
- default:
- throw new AssertionError();
- }
- }
-
- private String emptyCollection(final ExecutableElement field) {
- final String type = collectionType(field);
- switch (type) {
- case "List":
- return "emptyList";
- case "Set":
- return "emptySet";
- case "Map":
- return "emptyMap";
- default:
- throw new AssertionError();
- }
- }
-
- private String collectionType(final ExecutableElement field) {
- final String returnType = field.getReturnType().toString();
- if (returnType.startsWith("java.util.List<")) {
- return "List";
- } else if (returnType.startsWith("java.util.Set<")) {
- return "Set";
- } else if (returnType.startsWith("java.util.Map<")) {
- return "Map";
- } else {
- throw new AssertionError();
- }
- }
-
- private String optionalType(final ExecutableElement field) {
- final String returnType = field.getReturnType().toString();
- if (returnType.startsWith("java.util.Optional<")) {
- return "java.util.Optional";
- } else if (returnType.startsWith("com.google.common.base.Optional<")) {
- return "com.google.common.base.Optional";
- }
- return returnType;
- }
-
private boolean isMap(final ExecutableElement field) {
final String returnType = field.getReturnType().toString();
return returnType.startsWith("java.util.Map<");
@@ -1186,33 +574,7 @@ private boolean isPrimitive(final ExecutableElement field) {
return field.getReturnType().getKind().isPrimitive();
}
- private boolean isOptional(final ExecutableElement field) {
- final String returnType = field.getReturnType().toString();
- return returnType.startsWith("java.util.Optional<") ||
- returnType.startsWith("com.google.common.base.Optional<");
- }
- private String singular(final String name) {
- final String singular = INFLECTOR.singularize(name);
- if (KEYWORDS.contains(singular)) {
- return null;
- }
- if (elements.getTypeElement("java.lang." + singular) != null) {
- return null;
- }
- return name.equals(singular) ? null : singular;
- }
-
- private String variableName(final String name, final String... scope) {
- return variableName(name, ImmutableSet.copyOf(scope));
- }
-
- private String variableName(final String name, final Set scope) {
- if (!scope.contains(name)) {
- return name;
- }
- return variableName("_" + name, scope);
- }
private String fieldName(final ExecutableElement field) {
return field.getSimpleName().toString();
@@ -1245,16 +607,6 @@ private AutoMatterProcessorException fail(final String msg, final Element elemen
throw new AutoMatterProcessorException(msg, element);
}
- private static String capitalizeFirstLetter(String s) {
- if (s == null) {
- throw new NullPointerException("s");
- }
- if (s.isEmpty()) {
- return "";
- }
- return s.substring(0, 1).toUpperCase() + (s.length() > 1 ? s.substring(1) : "");
- }
-
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
diff --git a/processor/src/main/java/io/norberg/automatter/processor/BuildStatements.java b/processor/src/main/java/io/norberg/automatter/processor/BuildStatements.java
new file mode 100644
index 00000000..65521e97
--- /dev/null
+++ b/processor/src/main/java/io/norberg/automatter/processor/BuildStatements.java
@@ -0,0 +1,11 @@
+package io.norberg.automatter.processor;
+
+class BuildStatements {
+ final Iterable statements;
+ final String parameter;
+
+ BuildStatements(Iterable statements, String parameter) {
+ this.statements = statements;
+ this.parameter = parameter;
+ }
+}
diff --git a/processor/src/main/java/io/norberg/automatter/processor/CollectionProcessor.java b/processor/src/main/java/io/norberg/automatter/processor/CollectionProcessor.java
new file mode 100644
index 00000000..76123b06
--- /dev/null
+++ b/processor/src/main/java/io/norberg/automatter/processor/CollectionProcessor.java
@@ -0,0 +1,606 @@
+package io.norberg.automatter.processor;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.ArrayTypeName;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.WildcardTypeName;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.squareup.javapoet.WildcardTypeName.subtypeOf;
+import static io.norberg.automatter.processor.Common.assertNotNull;
+import static io.norberg.automatter.processor.Common.builderType;
+import static io.norberg.automatter.processor.Common.fail;
+import static io.norberg.automatter.processor.Fields.fieldName;
+import static io.norberg.automatter.processor.Fields.fieldType;
+import static io.norberg.automatter.processor.Fields.genericArgument;
+import static io.norberg.automatter.processor.Fields.shouldEnforceNonNull;
+import static javax.lang.model.element.Modifier.FINAL;
+import static javax.lang.model.element.Modifier.PRIVATE;
+import static javax.lang.model.element.Modifier.PUBLIC;
+import static javax.lang.model.type.TypeKind.DECLARED;
+
+class CollectionProcessor implements FieldProcessor {
+ private final Elements elements;
+
+ CollectionProcessor(Elements elements) {
+ this.elements = elements;
+ }
+
+ @Override
+ public FieldSpec builderField(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException {
+ return FieldSpec.builder(fieldType(d, field), fieldName(field), PRIVATE).build();
+ }
+
+ @Override
+ public FieldSpec valueField(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException {
+ return FieldSpec.builder(fieldType(d, field), fieldName(field), PRIVATE, FINAL).build();
+ }
+
+ @Override
+ public Iterable accessors(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException {
+ ArrayList result = new ArrayList<>();
+
+ result.add(getter(d, field));
+ if (isCollection(field)) {
+ result.add(collectionSetter(d, field));
+ result.add(collectionCollectionSetter(d, field));
+ result.add(collectionIterableSetter(d, field));
+ result.add(collectionIteratorSetter(d, field));
+ result.add(collectionVarargSetter(d, field));
+
+ MethodSpec adder = collectionAdder(d, field);
+ if (adder != null) {
+ result.add(adder);
+ }
+ } else {
+ result.add(mapSetter(d, field));
+ for (int i = 1; i <= 5; i++) {
+ result.add(mapSetterPairs(d, field, i));
+ }
+
+ MethodSpec putter = mapPutter(d, field);
+ if (putter != null) {
+ result.add(putter);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public Iterable defaultConstructor(Descriptor d, ExecutableElement field) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Iterable copyValueConstructor(Descriptor d, ExecutableElement field)
+ throws AutoMatterProcessorException {
+ ArrayList result = new ArrayList<>();
+ String fieldName = fieldName(field);
+ TypeName fieldType = upperBoundedFieldType(field);
+ result.add(new Statement("$T _$N = v.$N()", fieldType, fieldName, fieldName));
+ result.add(new Statement("this.$N = (_$N == null) ? null : new $T(_$N)",
+ fieldName, fieldName, collectionImplType(field), fieldName));
+ return result;
+ }
+
+ @Override
+ public Iterable copyBuilderConstructor(Descriptor d, ExecutableElement field)
+ throws AutoMatterProcessorException {
+ String fieldName = fieldName(field);
+ return Collections.singletonList(new Statement(
+ "this.$N = (v.$N == null) ? null : new $T(v.$N)",
+ fieldName, fieldName, collectionImplType(field), fieldName));
+ }
+
+ @Override
+ public BuildStatements build(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException {
+ final List statements = Lists.newArrayList();
+ final String fieldName = fieldName(field);
+ final TypeName fieldType = fieldType(d, field);
+ final ClassName collections = ClassName.get(Collections.class);
+
+ if (isCollection(field)) {
+ final TypeName itemType = genericArgument(field, 0);
+
+ if (shouldEnforceNonNull(field)) {
+ statements.add(new Statement(
+ "$T _$L = ($L != null) ? $T.$L(new $T($N)) : $T.<$T>$L()",
+ fieldType, fieldName, fieldName,
+ collections, unmodifiableCollection(field), collectionImplType(field), fieldName,
+ collections, itemType, emptyCollection(field)));
+ } else {
+ statements.add(new Statement(
+ "$T _$L = ($L != null) ? $T.$L(new $T($N)) : null",
+ fieldType, fieldName, fieldName,
+ collections, unmodifiableCollection(field), collectionImplType(field), fieldName));
+ }
+ } else if (isMap(field)) {
+ final TypeName keyType = genericArgument(field, 0);
+ final TypeName valueType = genericArgument(field, 1);
+
+ if (shouldEnforceNonNull(field)) {
+ statements.add(new Statement(
+ "$T _$L = ($L != null) ? $T.unmodifiableMap(new $T($N)) : $T.<$T, $T>emptyMap()",
+ fieldType, fieldName, fieldName,
+ collections, collectionImplType(field), fieldName,
+ collections, keyType, valueType));
+ } else {
+ statements.add(new Statement(
+ "$T _$L = ($L != null) ? $T.unmodifiableMap(new $T($N)) : null",
+ fieldType, fieldName, fieldName,
+ collections, collectionImplType(field), fieldName));
+ }
+ }
+ String parameter = "_" + fieldName;
+ return new BuildStatements(statements, parameter);
+ }
+
+ @Override
+ public Iterable valueConstructor(Descriptor d, ExecutableElement field)
+ throws AutoMatterProcessorException {
+ final List statements = Lists.newArrayList();
+ final String fieldName = fieldName(field);
+ final ClassName collectionsType = ClassName.get(Collections.class);
+ if (shouldEnforceNonNull(field) && isCollection(field)) {
+ final TypeName itemType = genericArgument(field, 0);
+ statements.add(new Statement(
+ "this.$N = ($N != null) ? $N : $T.<$T>$L()",
+ fieldName, fieldName, fieldName, collectionsType, itemType, emptyCollection(field)));
+ } else if (shouldEnforceNonNull(field) && isMap(field)) {
+ final TypeName keyType = genericArgument(field, 0);
+ final TypeName valueType = genericArgument(field, 1);
+ statements.add(new Statement(
+ "this.$N = ($N != null) ? $N : $T.<$T, $T>emptyMap()",
+ fieldName, fieldName, fieldName, collectionsType, keyType, valueType));
+ } else {
+ statements.add(new Statement("this.$N = $N", fieldName, fieldName));
+ }
+ return statements;
+ }
+
+ private MethodSpec collectionSetter(final Descriptor d, final ExecutableElement field) {
+ String fieldName = fieldName(field);
+ ClassName collectionType = collectionRawType(field);
+ TypeName itemType = genericArgument(field, 0);
+ WildcardTypeName extendedType = subtypeOf(itemType);
+
+ return MethodSpec.methodBuilder(fieldName)
+ .addModifiers(PUBLIC)
+ .addParameter(ParameterizedTypeName.get(collectionType, extendedType), fieldName)
+ .returns(builderType(d))
+ .addStatement("return $N((Collection<$T>) $N)", fieldName, extendedType, fieldName)
+ .build();
+ }
+
+ private MethodSpec collectionCollectionSetter(final Descriptor d, final ExecutableElement field) {
+ String fieldName = fieldName(field);
+ ClassName collectionType = ClassName.get(Collection.class);
+ TypeName itemType = genericArgument(field, 0);
+ WildcardTypeName extendedType = subtypeOf(itemType);
+
+ MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
+ .addModifiers(PUBLIC)
+ .addParameter(ParameterizedTypeName.get(collectionType, extendedType), fieldName)
+ .returns(builderType(d));
+
+ collectionNullGuard(setter, field);
+ if (shouldEnforceNonNull(field)) {
+ setter.beginControlFlow("for ($T item : $N)", itemType, fieldName);
+ assertNotNull(setter, "item", fieldName + ": null item");
+ setter.endControlFlow();
+ }
+
+ setter.addStatement("this.$N = new $T($N)", fieldName, collectionImplType(field), fieldName);
+ return setter.addStatement("return this").build();
+ }
+
+ private MethodSpec collectionIterableSetter(final Descriptor d, final ExecutableElement field) {
+ String fieldName = fieldName(field);
+ ClassName iterableType = ClassName.get(Iterable.class);
+ TypeName itemType = genericArgument(field, 0);
+ WildcardTypeName extendedType = subtypeOf(itemType);
+
+ MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
+ .addModifiers(PUBLIC)
+ .addParameter(ParameterizedTypeName.get(iterableType, extendedType), fieldName)
+ .returns(builderType(d));
+
+ collectionNullGuard(setter, field);
+
+ ClassName collectionType = ClassName.get(Collection.class);
+ setter.beginControlFlow("if ($N instanceof $T)", fieldName, collectionType)
+ .addStatement("return $N(($T<$T>) $N)", fieldName, collectionType, extendedType, fieldName)
+ .endControlFlow();
+
+ setter.addStatement("return $N($N.iterator())", fieldName, fieldName);
+ return setter.build();
+ }
+
+ private MethodSpec collectionIteratorSetter(final Descriptor d, final ExecutableElement field) {
+ String fieldName = fieldName(field);
+ ClassName iteratorType = ClassName.get(Iterator.class);
+ TypeName itemType = genericArgument(field, 0);
+ WildcardTypeName extendedType = subtypeOf(itemType);
+
+ MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
+ .addModifiers(PUBLIC)
+ .addParameter(ParameterizedTypeName.get(iteratorType, extendedType), fieldName)
+ .returns(builderType(d));
+
+ collectionNullGuard(setter, field);
+
+ setter.addStatement("this.$N = new $T()", fieldName, collectionImplType(field))
+ .beginControlFlow("while ($N.hasNext())", fieldName)
+ .addStatement("$T item = $N.next()", itemType, fieldName);
+
+ if (shouldEnforceNonNull(field)) {
+ assertNotNull(setter, "item", fieldName + ": null item");
+ }
+
+ setter.addStatement("this.$N.add(item)", fieldName)
+ .endControlFlow();
+
+ return setter.addStatement("return this").build();
+ }
+
+ private MethodSpec collectionVarargSetter(final Descriptor d, final ExecutableElement field) {
+ String fieldName = fieldName(field);
+ TypeName itemType = genericArgument(field, 0);
+
+ MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
+ .addModifiers(PUBLIC)
+ .addParameter(ArrayTypeName.of(itemType), fieldName)
+ .varargs()
+ .returns(builderType(d));
+
+ ensureSafeVarargs(setter);
+
+ collectionNullGuard(setter, field);
+
+ setter.addStatement("return $N($T.asList($N))", fieldName, ClassName.get(Arrays.class), fieldName);
+ return setter.build();
+ }
+
+ private void ensureSafeVarargs(MethodSpec.Builder setter) {
+ // TODO: Add SafeVarargs annotation only for non-reifiable types.
+ AnnotationSpec safeVarargsAnnotation = AnnotationSpec.builder(SafeVarargs.class).build();
+
+ setter
+ .addAnnotation(safeVarargsAnnotation)
+ .addModifiers(FINAL); // Only because SafeVarargs can be applied to final methods.
+ }
+
+ private MethodSpec collectionAdder(final Descriptor d, final ExecutableElement field) {
+ final String fieldName = fieldName(field);
+ final String singular = Common.singular(elements, fieldName);
+ if (singular == null || singular.isEmpty()) {
+ return null;
+ }
+
+ final String appendMethodName = "add" + capitalizeFirstLetter(singular);
+ final TypeName itemType = genericArgument(field, 0);
+ MethodSpec.Builder adder = MethodSpec.methodBuilder(appendMethodName)
+ .addModifiers(PUBLIC)
+ .addParameter(itemType, singular)
+ .returns(builderType(d));
+
+ if (shouldEnforceNonNull(field)) {
+ assertNotNull(adder, singular);
+ }
+ lazyCollectionInitialization(adder, field);
+
+ adder.addStatement("$L.add($L)", fieldName, singular);
+ return adder.addStatement("return this").build();
+ }
+
+ private void collectionNullGuard(final MethodSpec.Builder spec, final ExecutableElement field) {
+ String fieldName = fieldName(field);
+ if (shouldEnforceNonNull(field)) {
+ assertNotNull(spec, fieldName);
+ } else {
+ spec.beginControlFlow("if ($N == null)", fieldName)
+ .addStatement("this.$N = null", fieldName)
+ .addStatement("return this")
+ .endControlFlow();
+ }
+ }
+
+ private void lazyCollectionInitialization(final MethodSpec.Builder spec, final ExecutableElement field) {
+ final String fieldName = fieldName(field);
+ spec.beginControlFlow("if (this.$N == null)", fieldName)
+ .addStatement("this.$N = new $T()", fieldName, collectionImplType(field))
+ .endControlFlow();
+ }
+
+ private String collectionType(final ExecutableElement field) {
+ final String returnType = field.getReturnType().toString();
+ if (returnType.startsWith("java.util.List<")) {
+ return "List";
+ } else if (returnType.startsWith("java.util.Set<")) {
+ return "Set";
+ } else if (returnType.startsWith("java.util.Map<")) {
+ return "Map";
+ } else {
+ throw new AssertionError();
+ }
+ }
+
+ private TypeName collectionImplType(final ExecutableElement field) {
+ switch (collectionType(field)) {
+ case "List":
+ return ParameterizedTypeName.get(
+ ClassName.get(ArrayList.class),
+ genericArgument(field, 0));
+ case "Set":
+ return ParameterizedTypeName.get(
+ ClassName.get(HashSet.class),
+ genericArgument(field, 0));
+ case "Map":
+ return ParameterizedTypeName.get(
+ ClassName.get(HashMap.class),
+ genericArgument(field, 0), genericArgument(field, 1));
+ default:
+ throw new IllegalStateException("invalid collection type " + field);
+ }
+ }
+
+ private ClassName collectionRawType(final ExecutableElement field) {
+ final DeclaredType type = (DeclaredType) field.getReturnType();
+ return ClassName.get("java.util", type.asElement().getSimpleName().toString());
+ }
+
+ private TypeName upperBoundedFieldType(final ExecutableElement field) throws AutoMatterProcessorException {
+ TypeMirror type = field.getReturnType();
+ if (type.getKind() == TypeKind.ERROR) {
+ throw fail("Cannot resolve type, might be missing import: " + type, field);
+ }
+ if (type.getKind() != DECLARED) {
+ return TypeName.get(type);
+ }
+ final DeclaredType declaredType = (DeclaredType) type;
+ if (declaredType.getTypeArguments().isEmpty()) {
+ return TypeName.get(type);
+ }
+ final ClassName raw = rawClassName(declaredType);
+ if (isCollection(field)) {
+ final TypeName elementType = TypeName.get(declaredType.getTypeArguments().get(0));
+ return ParameterizedTypeName.get(raw, subtypeOf(elementType));
+ } else if (isMap(field)) {
+ final TypeName keyTypeArgument = TypeName.get(declaredType.getTypeArguments().get(0));
+ final TypeName valueTypeArgument = TypeName.get(declaredType.getTypeArguments().get(1));
+ return ParameterizedTypeName.get(raw, subtypeOf(keyTypeArgument), subtypeOf(valueTypeArgument));
+ }
+ return TypeName.get(type);
+ }
+
+ private ClassName rawClassName(final DeclaredType declaredType) {
+ final String simpleName = declaredType.asElement().getSimpleName().toString();
+ final String packageName = packageName(declaredType);
+ return ClassName.get(packageName, simpleName);
+ }
+
+ private String packageName(final DeclaredType declaredType) {
+ Element type = declaredType.asElement();
+ while (type.getKind() != ElementKind.PACKAGE) {
+ type = type.getEnclosingElement();
+ }
+ return type.toString();
+ }
+
+ private boolean isCollection(final ExecutableElement field) {
+ final String returnType = field.getReturnType().toString();
+ return returnType.startsWith("java.util.List<") ||
+ returnType.startsWith("java.util.Set<");
+ }
+
+ private String unmodifiableCollection(final ExecutableElement field) {
+ final String type = collectionType(field);
+ switch (type) {
+ case "List":
+ return "unmodifiableList";
+ case "Set":
+ return "unmodifiableSet";
+ case "Map":
+ return "unmodifiableMap";
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ private String emptyCollection(final ExecutableElement field) {
+ final String type = collectionType(field);
+ switch (type) {
+ case "List":
+ return "emptyList";
+ case "Set":
+ return "emptySet";
+ case "Map":
+ return "emptyMap";
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ private boolean isMap(final ExecutableElement field) {
+ final String returnType = field.getReturnType().toString();
+ return returnType.startsWith("java.util.Map<");
+ }
+
+ private MethodSpec mapSetter(final Descriptor d, final ExecutableElement field) {
+ final String fieldName = fieldName(field);
+ final TypeName keyType = subtypeOf(genericArgument(field, 0));
+ final TypeName valueType = subtypeOf(genericArgument(field, 1));
+ final TypeName paramType = ParameterizedTypeName.get(ClassName.get(Map.class), keyType, valueType);
+
+ MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
+ .addModifiers(PUBLIC)
+ .addParameter(paramType, fieldName)
+ .returns(builderType(d));
+
+ if (shouldEnforceNonNull(field)) {
+ final String entryName = variableName("entry", fieldName);
+ assertNotNull(setter, fieldName);
+ setter.beginControlFlow(
+ "for ($T<$T, $T> $L : $N.entrySet())",
+ ClassName.get(Map.Entry.class), keyType, valueType, entryName, fieldName);
+ assertNotNull(setter, entryName + ".getKey()", fieldName + ": null key");
+ assertNotNull(setter, entryName + ".getValue()", fieldName + ": null value");
+ setter.endControlFlow();
+ } else {
+ setter.beginControlFlow("if ($N == null)", fieldName)
+ .addStatement("this.$N = null", fieldName)
+ .addStatement("return this")
+ .endControlFlow();
+ }
+
+ setter.addStatement("this.$N = new $T($N)", fieldName, collectionImplType(field), fieldName);
+
+ return setter.addStatement("return this").build();
+ }
+
+ private MethodSpec mapSetterPairs(final Descriptor d, final ExecutableElement field, int entries) {
+ checkArgument(entries > 0, "entries");
+ final String fieldName = fieldName(field);
+ final TypeName keyType = genericArgument(field, 0);
+ final TypeName valueType = genericArgument(field, 1);
+
+ MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
+ .addModifiers(PUBLIC)
+ .returns(builderType(d));
+
+ for (int i = 1; i < entries + 1; i++) {
+ setter.addParameter(keyType, "k" + i);
+ setter.addParameter(valueType, "v" + i);
+ }
+
+ // Recursion
+ if (entries > 1) {
+ final List recursionParameters = Lists.newArrayList();
+ for (int i = 1; i < entries; i++) {
+ recursionParameters.add("k" + i);
+ recursionParameters.add("v" + i);
+ }
+ setter.addStatement("$L($L)", fieldName, Joiner.on(", ").join(recursionParameters));
+ }
+
+ // Null checks
+ final String keyName = "k" + entries;
+ final String valueName = "v" + entries;
+ if (shouldEnforceNonNull(field)) {
+ assertNotNull(setter, keyName, fieldName + ": " + keyName);
+ assertNotNull(setter, valueName, fieldName + ": " + valueName);
+ }
+
+ // Map instantiation
+ if (entries == 1) {
+ setter.addStatement("$N = new $T()", fieldName, collectionImplType(field));
+ }
+
+ // Put
+ setter.addStatement("$N.put($N, $N)", fieldName, keyName, valueName);
+
+ return setter.addStatement("return this").build();
+ }
+
+ private MethodSpec mapPutter(final Descriptor d, final ExecutableElement field) {
+ final String fieldName = fieldName(field);
+ final String singular = Common.singular(elements, fieldName);
+ if (singular == null) {
+ return null;
+ }
+
+ final String putSingular = "put" + capitalizeFirstLetter(singular);
+ final TypeName keyType = genericArgument(field, 0);
+ final TypeName valueType = genericArgument(field, 1);
+
+ MethodSpec.Builder setter = MethodSpec.methodBuilder(putSingular)
+ .addModifiers(PUBLIC)
+ .addParameter(keyType, "key")
+ .addParameter(valueType, "value")
+ .returns(builderType(d));
+
+ // Null checks
+ if (shouldEnforceNonNull(field)) {
+ assertNotNull(setter, "key", singular + ": key");
+ assertNotNull(setter, "value", singular + ": value");
+ }
+
+ // Put
+ lazyMapInitialization(setter, field);
+ setter.addStatement("$N.put(key, value)", fieldName);
+
+ return setter.addStatement("return this").build();
+ }
+
+ private void lazyMapInitialization(final MethodSpec.Builder spec, final ExecutableElement field) {
+ final String fieldName = fieldName(field);
+ spec.beginControlFlow("if (this.$N == null)", fieldName)
+ .addStatement("this.$N = new $T()", fieldName, collectionImplType(field))
+ .endControlFlow();
+ }
+
+ private String variableName(final String name, final String... scope) {
+ return variableName(name, ImmutableSet.copyOf(scope));
+ }
+
+ private String variableName(final String name, final Set scope) {
+ if (!scope.contains(name)) {
+ return name;
+ }
+ return variableName("_" + name, scope);
+ }
+
+ private MethodSpec getter(final Descriptor d, final ExecutableElement field) throws AutoMatterProcessorException {
+ String fieldName = fieldName(field);
+
+ MethodSpec.Builder getter = MethodSpec.methodBuilder(fieldName)
+ .addModifiers(PUBLIC)
+ .returns(fieldType(d, field));
+
+ if (shouldEnforceNonNull(field)) {
+ getter.beginControlFlow("if (this.$N == null)", fieldName)
+ .addStatement("this.$N = new $T()", fieldName, collectionImplType(field))
+ .endControlFlow();
+ }
+ getter.addStatement("return $N", fieldName);
+
+ return getter.build();
+ }
+
+ private String capitalizeFirstLetter(String s) {
+ if (s == null) {
+ throw new NullPointerException("s");
+ }
+ if (s.isEmpty()) {
+ return "";
+ }
+ return s.substring(0, 1).toUpperCase() + (s.length() > 1 ? s.substring(1) : "");
+ }
+
+}
diff --git a/processor/src/main/java/io/norberg/automatter/processor/Common.java b/processor/src/main/java/io/norberg/automatter/processor/Common.java
new file mode 100644
index 00000000..5331963c
--- /dev/null
+++ b/processor/src/main/java/io/norberg/automatter/processor/Common.java
@@ -0,0 +1,62 @@
+package io.norberg.automatter.processor;
+
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import org.modeshape.common.text.Inflector;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.util.Elements;
+import java.util.Set;
+
+class Common {
+ private static final Inflector INFLECTOR = new Inflector();
+
+ private static final Set KEYWORDS = ImmutableSet.of(
+ "abstract", "continue", "for", "new", "switch", "assert", "default", "if", "package",
+ "synchronized", "boolean", "do", "goto", "private", "this", "break", "double", "implements",
+ "protected", "throw", "byte", "else", "import", "public", "throws", "case", "enum",
+ "instanceof", "return", "transient", "catch", "extends", "int", "short", "try", "char",
+ "final", "interface", "static", "void", "class", "finally", "long", "strictfp", "volatile",
+ "const", "float", "native", "super", "while");
+
+ static void assertNotNull(MethodSpec.Builder spec, String name) {
+ assertNotNull(spec, name, name);
+ }
+
+ static void assertNotNull(MethodSpec.Builder spec, String name, String msg) {
+ spec.beginControlFlow("if ($N == null)", name)
+ .addStatement("throw new $T($S)", ClassName.get(NullPointerException.class), msg)
+ .endControlFlow();
+ }
+
+ static AutoMatterProcessorException fail(final String msg, final Element element)
+ throws AutoMatterProcessorException {
+ throw new AutoMatterProcessorException(msg, element);
+ }
+
+ static String singular(final Elements elements, final String name) {
+ final String singular = INFLECTOR.singularize(name);
+ if (KEYWORDS.contains(singular)) {
+ return null;
+ }
+ if (elements.getTypeElement("java.lang." + singular) != null) {
+ return null;
+ }
+ return name.equals(singular) ? null : singular;
+ }
+
+ static ClassName rawBuilderType(final Descriptor d) {
+ return ClassName.get(d.packageName(), d.builderName());
+ }
+
+ static TypeName builderType(final Descriptor d) {
+ final ClassName raw = rawBuilderType(d);
+ if (!d.isGeneric()) {
+ return raw;
+ }
+ return ParameterizedTypeName.get(raw, d.typeArguments());
+ }
+}
diff --git a/processor/src/main/java/io/norberg/automatter/processor/DefaultProcessor.java b/processor/src/main/java/io/norberg/automatter/processor/DefaultProcessor.java
new file mode 100644
index 00000000..890756f8
--- /dev/null
+++ b/processor/src/main/java/io/norberg/automatter/processor/DefaultProcessor.java
@@ -0,0 +1,147 @@
+package io.norberg.automatter.processor;
+
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterSpec;
+import com.squareup.javapoet.TypeName;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+import static io.norberg.automatter.processor.Common.assertNotNull;
+import static io.norberg.automatter.processor.Common.builderType;
+import static io.norberg.automatter.processor.Fields.fieldName;
+import static io.norberg.automatter.processor.Fields.fieldType;
+import static io.norberg.automatter.processor.Fields.isPrimitive;
+import static io.norberg.automatter.processor.Fields.shouldEnforceNonNull;
+import static javax.lang.model.element.Modifier.FINAL;
+import static javax.lang.model.element.Modifier.PRIVATE;
+import static javax.lang.model.element.Modifier.PUBLIC;
+import static javax.lang.model.type.TypeKind.DECLARED;
+import static javax.lang.model.type.TypeKind.TYPEVAR;
+
+class DefaultProcessor implements FieldProcessor {
+ @Override
+ public FieldSpec builderField(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException {
+ return FieldSpec.builder(fieldType(d, field), fieldName(field), PRIVATE).build();
+ }
+
+ @Override
+ public FieldSpec valueField(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException {
+ return FieldSpec.builder(fieldType(d, field), fieldName(field), PRIVATE, FINAL).build();
+ }
+
+ @Override
+ public Iterable accessors(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException {
+ ArrayList methods = new ArrayList<>();
+ methods.add(getter(d, field));
+ methods.add(setter(d, field));
+ return methods;
+ }
+
+ @Override
+ public Iterable defaultConstructor(Descriptor d, ExecutableElement field) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Iterable copyValueConstructor(Descriptor d, ExecutableElement field)
+ throws AutoMatterProcessorException {
+ ArrayList statements = new ArrayList<>();
+ String fieldName = fieldName(field);
+ TypeName fieldType = fieldType(d, field);
+ if (isFieldTypeParameterized(field)) {
+ statements.add(new Statement("@SuppressWarnings(\"unchecked\") $T _$N = ($T) v.$N()",
+ fieldType, fieldName, fieldType, fieldName));
+ statements.add(new Statement("this.$N = _$N", fieldName, fieldName));
+ } else {
+ statements.add(new Statement("this.$N = v.$N()", fieldName, fieldName));
+ }
+ return statements;
+ }
+
+ @Override
+ public Iterable copyBuilderConstructor(Descriptor d, ExecutableElement field)
+ throws AutoMatterProcessorException {
+ ArrayList statements = new ArrayList<>();
+ String fieldName = fieldName(field);
+ TypeName fieldType = fieldType(d, field);
+ if (isFieldTypeParameterized(field)) {
+ statements.add(new Statement("@SuppressWarnings(\"unchecked\") $T _$N = ($T) v.$N()",
+ fieldType, fieldName, fieldType, fieldName));
+ statements.add(new Statement("this.$N = _$N", fieldName, fieldName));
+ } else {
+ statements.add(new Statement("this.$N = v.$N", fieldName, fieldName));
+ }
+ return statements;
+ }
+
+ @Override
+ public BuildStatements build(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException {
+ return new BuildStatements(Collections.emptyList(), fieldName(field));
+ }
+
+ @Override
+ public Iterable valueConstructor(Descriptor d, ExecutableElement field)
+ throws AutoMatterProcessorException {
+ String fieldName = fieldName(field);
+ return Collections.singletonList(new Statement("this.$N = $N", fieldName, fieldName));
+ }
+
+ private boolean isFieldTypeParameterized(final ExecutableElement field) {
+ final TypeMirror returnType = field.getReturnType();
+ if (returnType.getKind() != DECLARED) {
+ return false;
+ }
+ final DeclaredType declaredType = (DeclaredType) returnType;
+ for (final TypeMirror typeArgument : declaredType.getTypeArguments()) {
+ if (typeArgument.getKind() == TYPEVAR) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private MethodSpec setter(final Descriptor d, final ExecutableElement field) throws AutoMatterProcessorException {
+ String fieldName = fieldName(field);
+
+ ParameterSpec.Builder parameterSpecBuilder =
+ ParameterSpec.builder(fieldType(d, field), fieldName);
+ if (!isPrimitive(field)) {
+ AnnotationMirror nullableAnnotation = Fields.nullableAnnotation(field);
+ if (nullableAnnotation != null) {
+ parameterSpecBuilder.addAnnotation(AnnotationSpec.get(nullableAnnotation));
+ }
+ }
+ MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
+ .addModifiers(PUBLIC)
+ .addParameter(parameterSpecBuilder.build())
+ .returns(builderType(d));
+
+ if (shouldEnforceNonNull(field)) {
+ assertNotNull(setter, fieldName);
+ }
+
+ setter.addStatement("this.$N = $N", fieldName, fieldName);
+ return setter.addStatement("return this").build();
+ }
+
+ protected MethodSpec getter(final Descriptor d, final ExecutableElement field) throws AutoMatterProcessorException {
+ String fieldName = fieldName(field);
+
+ MethodSpec.Builder getter = MethodSpec.methodBuilder(fieldName)
+ .addModifiers(PUBLIC)
+ .returns(fieldType(d, field));
+
+ getter.addStatement("return $N", fieldName);
+
+ return getter.build();
+ }
+
+}
diff --git a/processor/src/main/java/io/norberg/automatter/processor/Field.java b/processor/src/main/java/io/norberg/automatter/processor/Field.java
index db61ebca..654fcfea 100644
--- a/processor/src/main/java/io/norberg/automatter/processor/Field.java
+++ b/processor/src/main/java/io/norberg/automatter/processor/Field.java
@@ -7,7 +7,7 @@ class Field {
final ExecutableElement method;
final TypeMirror type;
- public Field(final ExecutableElement method, final TypeMirror type) {
+ Field(final ExecutableElement method, final TypeMirror type) {
this.method = method;
this.type = type;
}
diff --git a/processor/src/main/java/io/norberg/automatter/processor/FieldProcessor.java b/processor/src/main/java/io/norberg/automatter/processor/FieldProcessor.java
new file mode 100644
index 00000000..39d131d6
--- /dev/null
+++ b/processor/src/main/java/io/norberg/automatter/processor/FieldProcessor.java
@@ -0,0 +1,19 @@
+package io.norberg.automatter.processor;
+
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.MethodSpec;
+
+import javax.lang.model.element.ExecutableElement;
+
+interface FieldProcessor {
+ FieldSpec builderField(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException;
+ FieldSpec valueField(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException;
+
+ Iterable accessors(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException;
+
+ Iterable defaultConstructor(Descriptor d, ExecutableElement field);
+ Iterable copyValueConstructor(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException;
+ Iterable copyBuilderConstructor(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException;
+ BuildStatements build(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException;
+ Iterable valueConstructor(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException;
+}
diff --git a/processor/src/main/java/io/norberg/automatter/processor/Fields.java b/processor/src/main/java/io/norberg/automatter/processor/Fields.java
new file mode 100644
index 00000000..bf7f6c16
--- /dev/null
+++ b/processor/src/main/java/io/norberg/automatter/processor/Fields.java
@@ -0,0 +1,54 @@
+package io.norberg.automatter.processor;
+
+import com.squareup.javapoet.TypeName;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static io.norberg.automatter.processor.Common.fail;
+
+class Fields {
+ static TypeName fieldType(final Descriptor d, final ExecutableElement field) throws AutoMatterProcessorException {
+ final TypeMirror returnType = field.getReturnType();
+ if (returnType.getKind() == TypeKind.ERROR) {
+ throw fail("Cannot resolve type, might be missing import: " + returnType, field);
+ }
+ final TypeMirror fieldType = d.fieldTypes().get(field);
+ return TypeName.get(fieldType);
+ }
+
+ static String fieldName(final ExecutableElement field) {
+ return field.getSimpleName().toString();
+ }
+
+ static boolean isPrimitive(final ExecutableElement field) {
+ return field.getReturnType().getKind().isPrimitive();
+ }
+
+ static AnnotationMirror nullableAnnotation(final ExecutableElement field) {
+ for (AnnotationMirror annotation : field.getAnnotationMirrors()) {
+ if (annotation.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable")) {
+ return annotation;
+ }
+ }
+ return null;
+ }
+
+ static boolean isNullableAnnotated(final ExecutableElement field) {
+ return nullableAnnotation(field) != null;
+ }
+
+ static boolean shouldEnforceNonNull(final ExecutableElement field) {
+ return !isPrimitive(field) && !isNullableAnnotated(field);
+ }
+
+ static TypeName genericArgument(final ExecutableElement field, int index) {
+ final DeclaredType type = (DeclaredType) field.getReturnType();
+ checkArgument(type.getTypeArguments().size() >= index);
+ return TypeName.get(type.getTypeArguments().get(index));
+ }
+}
diff --git a/processor/src/main/java/io/norberg/automatter/processor/OptionalProcessor.java b/processor/src/main/java/io/norberg/automatter/processor/OptionalProcessor.java
new file mode 100644
index 00000000..c2d840a1
--- /dev/null
+++ b/processor/src/main/java/io/norberg/automatter/processor/OptionalProcessor.java
@@ -0,0 +1,108 @@
+package io.norberg.automatter.processor;
+
+import com.google.common.collect.Lists;
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+
+import javax.lang.model.element.ExecutableElement;
+
+import java.util.Collections;
+import java.util.List;
+
+import static com.squareup.javapoet.WildcardTypeName.subtypeOf;
+import static io.norberg.automatter.processor.Common.assertNotNull;
+import static io.norberg.automatter.processor.Common.builderType;
+import static io.norberg.automatter.processor.Fields.fieldName;
+import static io.norberg.automatter.processor.Fields.fieldType;
+import static io.norberg.automatter.processor.Fields.genericArgument;
+import static io.norberg.automatter.processor.Fields.shouldEnforceNonNull;
+import static javax.lang.model.element.Modifier.PUBLIC;
+
+class OptionalProcessor extends DefaultProcessor {
+ @Override
+ public Iterable accessors(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException {
+ List methods = Lists.newArrayList();
+ methods.add(getter(d, field));
+ methods.add(optionalRawSetter(d, field));
+ methods.add(optionalSetter(d, field));
+ return methods;
+ }
+
+ @Override
+ public Iterable defaultConstructor(Descriptor d, ExecutableElement field) {
+ if (shouldEnforceNonNull(field)) {
+ ClassName type = ClassName.bestGuess(optionalType(field));
+ return Collections.singletonList(
+ new Statement("this.$N = $T.$L()", fieldName(field), type, optionalEmptyName(field)));
+ }
+ return Collections.emptyList();
+ }
+
+ private MethodSpec optionalRawSetter(final Descriptor d, final ExecutableElement field) {
+ String fieldName = fieldName(field);
+ ClassName type = ClassName.bestGuess(optionalType(field));
+ TypeName valueType = genericArgument(field, 0);
+
+ return MethodSpec.methodBuilder(fieldName)
+ .addModifiers(PUBLIC)
+ .addParameter(valueType, fieldName)
+ .returns(builderType(d))
+ .addStatement("return $N($T.$N($N))", fieldName, type, optionalMaybeName(field), fieldName)
+ .build();
+ }
+
+ private MethodSpec optionalSetter(final Descriptor d, final ExecutableElement field)
+ throws AutoMatterProcessorException {
+ String fieldName = fieldName(field);
+ TypeName valueType = genericArgument(field, 0);
+ ClassName optionalType = ClassName.bestGuess(optionalType(field));
+ TypeName parameterType = ParameterizedTypeName.get(optionalType, subtypeOf(valueType));
+
+ AnnotationSpec suppressUncheckedAnnotation = AnnotationSpec.builder(SuppressWarnings.class)
+ .addMember("value", "$S", "unchecked")
+ .build();
+
+ MethodSpec.Builder setter = MethodSpec.methodBuilder(fieldName)
+ .addAnnotation(suppressUncheckedAnnotation)
+ .addModifiers(PUBLIC)
+ .addParameter(parameterType, fieldName)
+ .returns(builderType(d));
+
+ if (shouldEnforceNonNull(field)) {
+ assertNotNull(setter, fieldName);
+ }
+
+ setter.addStatement("this.$N = ($T)$N", fieldName, fieldType(d, field), fieldName);
+
+ return setter.addStatement("return this").build();
+ }
+
+ private String optionalType(final ExecutableElement field) {
+ final String returnType = field.getReturnType().toString();
+ if (returnType.startsWith("java.util.Optional<")) {
+ return "java.util.Optional";
+ } else if (returnType.startsWith("com.google.common.base.Optional<")) {
+ return "com.google.common.base.Optional";
+ }
+ return returnType;
+ }
+
+ private static String optionalEmptyName(final ExecutableElement field) {
+ final String returnType = field.getReturnType().toString();
+ if (returnType.startsWith("com.google.common.base.Optional<")) {
+ return "absent";
+ }
+ return "empty";
+ }
+
+ private static String optionalMaybeName(final ExecutableElement field) {
+ final String returnType = field.getReturnType().toString();
+ if (returnType.startsWith("com.google.common.base.Optional<")) {
+ return "fromNullable";
+ }
+ return "ofNullable";
+ }
+}
diff --git a/processor/src/main/java/io/norberg/automatter/processor/Statement.java b/processor/src/main/java/io/norberg/automatter/processor/Statement.java
new file mode 100644
index 00000000..8efdb702
--- /dev/null
+++ b/processor/src/main/java/io/norberg/automatter/processor/Statement.java
@@ -0,0 +1,11 @@
+package io.norberg.automatter.processor;
+
+class Statement {
+ final String statement;
+ final Object[] args;
+
+ Statement(String statement, Object... args) {
+ this.statement = statement;
+ this.args = args;
+ }
+}