diff --git a/build.gradle b/build.gradle index 1dea80afc..e237f4d30 100644 --- a/build.gradle +++ b/build.gradle @@ -192,6 +192,8 @@ configurations { dependencies { vanillaCompile guava vanillaCompile gwtUser + vanillaCompile jackson('core') + vanillaCompile jackson('databind') vanillaTestCompile junit } task vanillaTest { diff --git a/generated/main/java/org/inferred/freebuilder/processor/property/Property_Builder.java b/generated/main/java/org/inferred/freebuilder/processor/property/Property_Builder.java index a7bb13533..7820d2892 100644 --- a/generated/main/java/org/inferred/freebuilder/processor/property/Property_Builder.java +++ b/generated/main/java/org/inferred/freebuilder/processor/property/Property_Builder.java @@ -76,6 +76,8 @@ public String toString() { private String getterName; private boolean fullyCheckedCast; private List accessorAnnotations = ImmutableList.of(); + private List getterAnnotations = ImmutableList.of(); + private List putAnnotations = ImmutableList.of(); private final EnumSet _unsetProperties = EnumSet.allOf(Property.class); /** @@ -362,31 +364,28 @@ public boolean isUsingBeanConvention() { * * @return this {@code Builder} object */ - public org.inferred.freebuilder.processor.property.Property.Builder setInToString(boolean inToString) { + public org.inferred.freebuilder.processor.property.Property.Builder setInToString( + boolean inToString) { this.inToString = inToString; return (org.inferred.freebuilder.processor.property.Property.Builder) this; } /** * Replaces the value to be returned by {@link - * org.inferred.freebuilder.processor.property.Property#isInToString()} by applying {@code mapper} to it - * and using the result. + * org.inferred.freebuilder.processor.property.Property#isInToString()} by applying {@code mapper} + * to it and using the result. * * @return this {@code Builder} object * @throws NullPointerException if {@code mapper} is null or returns null - * @throws IllegalStateException if the field has not been set */ public org.inferred.freebuilder.processor.property.Property.Builder mapInToString( UnaryOperator mapper) { - Objects.requireNonNull(mapper); return setInToString(mapper.apply(isInToString())); } /** * Returns the value that will be returned by {@link * org.inferred.freebuilder.processor.property.Property#isInToString()}. - * - * @throws IllegalStateException if the field has not been set */ public boolean isInToString() { return inToString; @@ -406,24 +405,20 @@ public org.inferred.freebuilder.processor.property.Property.Builder setInEqualsA /** * Replaces the value to be returned by {@link - * org.inferred.freebuilder.processor.property.Property#isInEqualsAndHashCode()} by applying {@code - * mapper} to it and using the result. + * org.inferred.freebuilder.processor.property.Property#isInEqualsAndHashCode()} by applying + * {@code mapper} to it and using the result. * * @return this {@code Builder} object * @throws NullPointerException if {@code mapper} is null or returns null - * @throws IllegalStateException if the field has not been set */ public org.inferred.freebuilder.processor.property.Property.Builder mapInEqualsAndHashCode( UnaryOperator mapper) { - Objects.requireNonNull(mapper); return setInEqualsAndHashCode(mapper.apply(isInEqualsAndHashCode())); } /** * Returns the value that will be returned by {@link * org.inferred.freebuilder.processor.property.Property#isInEqualsAndHashCode()}. - * - * @throws IllegalStateException if the field has not been set */ public boolean isInEqualsAndHashCode() { return inEqualsAndHashCode; @@ -634,7 +629,253 @@ public List getAccessorAnnotations() { } /** - * Copies values from {@code value}, appending to collections, and skipping empty optionals. + * Adds {@code element} to the list to be returned from {@link + * org.inferred.freebuilder.processor.property.Property#getGetterAnnotations()}. + * + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + public org.inferred.freebuilder.processor.property.Property.Builder addGetterAnnotations( + Excerpt element) { + if (getterAnnotations instanceof ImmutableList) { + getterAnnotations = new ArrayList<>(getterAnnotations); + } + getterAnnotations.add(Objects.requireNonNull(element)); + return (org.inferred.freebuilder.processor.property.Property.Builder) this; + } + + /** + * Adds each element of {@code elements} to the list to be returned from {@link + * org.inferred.freebuilder.processor.property.Property#getGetterAnnotations()}. + * + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + public org.inferred.freebuilder.processor.property.Property.Builder addGetterAnnotations( + Excerpt... elements) { + return addAllGetterAnnotations(Arrays.asList(elements)); + } + + /** + * Adds each element of {@code elements} to the list to be returned from {@link + * org.inferred.freebuilder.processor.property.Property#getGetterAnnotations()}. + * + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + public org.inferred.freebuilder.processor.property.Property.Builder addAllGetterAnnotations( + Spliterator elements) { + if ((elements.characteristics() & Spliterator.SIZED) != 0) { + long elementsSize = elements.estimateSize(); + if (elementsSize > 0 && elementsSize <= Integer.MAX_VALUE) { + if (getterAnnotations instanceof ImmutableList) { + getterAnnotations = new ArrayList<>(getterAnnotations); + } + ((ArrayList) getterAnnotations) + .ensureCapacity(getterAnnotations.size() + (int) elementsSize); + } + } + elements.forEachRemaining(this::addGetterAnnotations); + return (org.inferred.freebuilder.processor.property.Property.Builder) this; + } + + /** + * Adds each element of {@code elements} to the list to be returned from {@link + * org.inferred.freebuilder.processor.property.Property#getGetterAnnotations()}. + * + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + public org.inferred.freebuilder.processor.property.Property.Builder addAllGetterAnnotations( + BaseStream elements) { + return addAllGetterAnnotations(elements.spliterator()); + } + + /** + * Adds each element of {@code elements} to the list to be returned from {@link + * org.inferred.freebuilder.processor.property.Property#getGetterAnnotations()}. + * + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + public org.inferred.freebuilder.processor.property.Property.Builder addAllGetterAnnotations( + Iterable elements) { + return addAllGetterAnnotations(elements.spliterator()); + } + + /** + * Applies {@code mutator} to the list to be returned from {@link + * org.inferred.freebuilder.processor.property.Property#getGetterAnnotations()}. + * + *

This method mutates the list in-place. {@code mutator} is a void consumer, so any value + * returned from a lambda will be ignored. Take care not to call pure functions, like {@link + * Collection#stream()}. + * + * @return this {@code Builder} object + * @throws NullPointerException if {@code mutator} is null + */ + public org.inferred.freebuilder.processor.property.Property.Builder mutateGetterAnnotations( + Consumer> mutator) { + if (getterAnnotations instanceof ImmutableList) { + getterAnnotations = new ArrayList<>(getterAnnotations); + } + // If addGetterAnnotations is overridden, this method will be updated to delegate to it + mutator.accept(getterAnnotations); + return (org.inferred.freebuilder.processor.property.Property.Builder) this; + } + + /** + * Clears the list to be returned from {@link + * org.inferred.freebuilder.processor.property.Property#getGetterAnnotations()}. + * + * @return this {@code Builder} object + */ + public org.inferred.freebuilder.processor.property.Property.Builder clearGetterAnnotations() { + if (getterAnnotations instanceof ImmutableList) { + getterAnnotations = ImmutableList.of(); + } else { + getterAnnotations.clear(); + } + return (org.inferred.freebuilder.processor.property.Property.Builder) this; + } + + /** + * Returns an unmodifiable view of the list that will be returned by {@link + * org.inferred.freebuilder.processor.property.Property#getGetterAnnotations()}. Changes to this + * builder will be reflected in the view. + */ + public List getGetterAnnotations() { + if (getterAnnotations instanceof ImmutableList) { + getterAnnotations = new ArrayList<>(getterAnnotations); + } + return Collections.unmodifiableList(getterAnnotations); + } + + /** + * Adds {@code element} to the list to be returned from {@link + * org.inferred.freebuilder.processor.property.Property#getPutAnnotations()}. + * + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + public org.inferred.freebuilder.processor.property.Property.Builder addPutAnnotations( + Excerpt element) { + if (putAnnotations instanceof ImmutableList) { + putAnnotations = new ArrayList<>(putAnnotations); + } + putAnnotations.add(Objects.requireNonNull(element)); + return (org.inferred.freebuilder.processor.property.Property.Builder) this; + } + + /** + * Adds each element of {@code elements} to the list to be returned from {@link + * org.inferred.freebuilder.processor.property.Property#getPutAnnotations()}. + * + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + public org.inferred.freebuilder.processor.property.Property.Builder addPutAnnotations( + Excerpt... elements) { + return addAllPutAnnotations(Arrays.asList(elements)); + } + + /** + * Adds each element of {@code elements} to the list to be returned from {@link + * org.inferred.freebuilder.processor.property.Property#getPutAnnotations()}. + * + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + public org.inferred.freebuilder.processor.property.Property.Builder addAllPutAnnotations( + Spliterator elements) { + if ((elements.characteristics() & Spliterator.SIZED) != 0) { + long elementsSize = elements.estimateSize(); + if (elementsSize > 0 && elementsSize <= Integer.MAX_VALUE) { + if (putAnnotations instanceof ImmutableList) { + putAnnotations = new ArrayList<>(putAnnotations); + } + ((ArrayList) putAnnotations).ensureCapacity(putAnnotations.size() + (int) elementsSize); + } + } + elements.forEachRemaining(this::addPutAnnotations); + return (org.inferred.freebuilder.processor.property.Property.Builder) this; + } + + /** + * Adds each element of {@code elements} to the list to be returned from {@link + * org.inferred.freebuilder.processor.property.Property#getPutAnnotations()}. + * + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + public org.inferred.freebuilder.processor.property.Property.Builder addAllPutAnnotations( + BaseStream elements) { + return addAllPutAnnotations(elements.spliterator()); + } + + /** + * Adds each element of {@code elements} to the list to be returned from {@link + * org.inferred.freebuilder.processor.property.Property#getPutAnnotations()}. + * + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + public org.inferred.freebuilder.processor.property.Property.Builder addAllPutAnnotations( + Iterable elements) { + return addAllPutAnnotations(elements.spliterator()); + } + + /** + * Applies {@code mutator} to the list to be returned from {@link + * org.inferred.freebuilder.processor.property.Property#getPutAnnotations()}. + * + *

This method mutates the list in-place. {@code mutator} is a void consumer, so any value + * returned from a lambda will be ignored. Take care not to call pure functions, like {@link + * Collection#stream()}. + * + * @return this {@code Builder} object + * @throws NullPointerException if {@code mutator} is null + */ + public org.inferred.freebuilder.processor.property.Property.Builder mutatePutAnnotations( + Consumer> mutator) { + if (putAnnotations instanceof ImmutableList) { + putAnnotations = new ArrayList<>(putAnnotations); + } + // If addPutAnnotations is overridden, this method will be updated to delegate to it + mutator.accept(putAnnotations); + return (org.inferred.freebuilder.processor.property.Property.Builder) this; + } + + /** + * Clears the list to be returned from {@link + * org.inferred.freebuilder.processor.property.Property#getPutAnnotations()}. + * + * @return this {@code Builder} object + */ + public org.inferred.freebuilder.processor.property.Property.Builder clearPutAnnotations() { + if (putAnnotations instanceof ImmutableList) { + putAnnotations = ImmutableList.of(); + } else { + putAnnotations.clear(); + } + return (org.inferred.freebuilder.processor.property.Property.Builder) this; + } + + /** + * Returns an unmodifiable view of the list that will be returned by {@link + * org.inferred.freebuilder.processor.property.Property#getPutAnnotations()}. Changes to this + * builder will be reflected in the view. + */ + public List getPutAnnotations() { + if (putAnnotations instanceof ImmutableList) { + putAnnotations = new ArrayList<>(putAnnotations); + } + return Collections.unmodifiableList(putAnnotations); + } + + /** + * Copies values from {@code value}, appending to collections, and skipping defaults and empty + * optionals. * * @return this {@code Builder} object */ @@ -662,10 +903,10 @@ public org.inferred.freebuilder.processor.property.Property.Builder mergeFrom( || value.isUsingBeanConvention() != defaults.isUsingBeanConvention()) { setUsingBeanConvention(value.isUsingBeanConvention()); } - if (!Objects.equals(value.isInToString(), defaults.isInToString())) { + if (value.isInToString() != defaults.isInToString()) { setInToString(value.isInToString()); } - if (!Objects.equals(value.isInEqualsAndHashCode(), defaults.isInEqualsAndHashCode())) { + if (value.isInEqualsAndHashCode() != defaults.isInEqualsAndHashCode()) { setInEqualsAndHashCode(value.isInEqualsAndHashCode()); } if (defaults._unsetProperties.contains(Property.GETTER_NAME) @@ -681,12 +922,22 @@ public org.inferred.freebuilder.processor.property.Property.Builder mergeFrom( } else { addAllAccessorAnnotations(value.getAccessorAnnotations()); } + if (value instanceof Value && getterAnnotations == ImmutableList.of()) { + getterAnnotations = ImmutableList.copyOf(value.getGetterAnnotations()); + } else { + addAllGetterAnnotations(value.getGetterAnnotations()); + } + if (value instanceof Value && putAnnotations == ImmutableList.of()) { + putAnnotations = ImmutableList.copyOf(value.getPutAnnotations()); + } else { + addAllPutAnnotations(value.getPutAnnotations()); + } return (org.inferred.freebuilder.processor.property.Property.Builder) this; } /** - * Copies values from {@code template}, appending to collections, and skipping empty optionals and - * unset properties. + * Copies values from {@code template}, appending to collections, and skipping defaults, empty + * optionals and unset properties. * * @return this {@code Builder} object */ @@ -738,6 +989,8 @@ public org.inferred.freebuilder.processor.property.Property.Builder mergeFrom( setFullyCheckedCast(template.isFullyCheckedCast()); } addAllAccessorAnnotations(base.accessorAnnotations); + addAllGetterAnnotations(base.getterAnnotations); + addAllPutAnnotations(base.putAnnotations); return (org.inferred.freebuilder.processor.property.Property.Builder) this; } @@ -759,6 +1012,8 @@ public org.inferred.freebuilder.processor.property.Property.Builder clear() { getterName = defaults.getterName; fullyCheckedCast = defaults.fullyCheckedCast; clearAccessorAnnotations(); + clearGetterAnnotations(); + clearPutAnnotations(); _unsetProperties.clear(); _unsetProperties.addAll(defaults._unsetProperties); return (org.inferred.freebuilder.processor.property.Property.Builder) this; @@ -815,6 +1070,8 @@ private static final class Value extends Rebuildable { private final String getterName; private final boolean fullyCheckedCast; private final ImmutableList accessorAnnotations; + private final ImmutableList getterAnnotations; + private final ImmutableList putAnnotations; private Value(Property_Builder builder) { this.type = builder.type; @@ -828,6 +1085,8 @@ private Value(Property_Builder builder) { this.getterName = builder.getterName; this.fullyCheckedCast = builder.fullyCheckedCast; this.accessorAnnotations = ImmutableList.copyOf(builder.accessorAnnotations); + this.getterAnnotations = ImmutableList.copyOf(builder.getterAnnotations); + this.putAnnotations = ImmutableList.copyOf(builder.putAnnotations); } @Override @@ -885,6 +1144,16 @@ public ImmutableList getAccessorAnnotations() { return accessorAnnotations; } + @Override + public ImmutableList getGetterAnnotations() { + return getterAnnotations; + } + + @Override + public ImmutableList getPutAnnotations() { + return putAnnotations; + } + @Override public Builder toBuilder() { Property_Builder builder = new Builder(); @@ -899,6 +1168,8 @@ public Builder toBuilder() { builder.getterName = getterName; builder.fullyCheckedCast = fullyCheckedCast; builder.accessorAnnotations = accessorAnnotations; + builder.getterAnnotations = getterAnnotations; + builder.putAnnotations = putAnnotations; builder._unsetProperties.clear(); return (Builder) builder; } @@ -919,7 +1190,9 @@ public boolean equals(Object obj) { && inEqualsAndHashCode == other.inEqualsAndHashCode && Objects.equals(getterName, other.getterName) && fullyCheckedCast == other.fullyCheckedCast - && Objects.equals(accessorAnnotations, other.accessorAnnotations); + && Objects.equals(accessorAnnotations, other.accessorAnnotations) + && Objects.equals(getterAnnotations, other.getterAnnotations) + && Objects.equals(putAnnotations, other.putAnnotations); } @Override @@ -935,7 +1208,9 @@ public int hashCode() { inEqualsAndHashCode, getterName, fullyCheckedCast, - accessorAnnotations); + accessorAnnotations, + getterAnnotations, + putAnnotations); } @Override @@ -963,6 +1238,10 @@ public String toString() { .append(fullyCheckedCast) .append(", accessorAnnotations=") .append(accessorAnnotations) + .append(", getterAnnotations=") + .append(getterAnnotations) + .append(", putAnnotations=") + .append(putAnnotations) .append("}") .toString(); } @@ -983,6 +1262,8 @@ private static final class Partial extends Rebuildable { private final String getterName; private final boolean fullyCheckedCast; private final ImmutableList accessorAnnotations; + private final ImmutableList getterAnnotations; + private final ImmutableList putAnnotations; private final EnumSet _unsetProperties; Partial(Property_Builder builder) { @@ -997,6 +1278,8 @@ private static final class Partial extends Rebuildable { this.getterName = builder.getterName; this.fullyCheckedCast = builder.fullyCheckedCast; this.accessorAnnotations = ImmutableList.copyOf(builder.accessorAnnotations); + this.getterAnnotations = ImmutableList.copyOf(builder.getterAnnotations); + this.putAnnotations = ImmutableList.copyOf(builder.putAnnotations); this._unsetProperties = builder._unsetProperties.clone(); } @@ -1076,6 +1359,16 @@ public ImmutableList getAccessorAnnotations() { return accessorAnnotations; } + @Override + public ImmutableList getGetterAnnotations() { + return getterAnnotations; + } + + @Override + public ImmutableList getPutAnnotations() { + return putAnnotations; + } + private static class PartialBuilder extends Builder { @Override public org.inferred.freebuilder.processor.property.Property build() { @@ -1097,6 +1390,8 @@ public Builder toBuilder() { builder.getterName = getterName; builder.fullyCheckedCast = fullyCheckedCast; builder.accessorAnnotations = accessorAnnotations; + builder.getterAnnotations = getterAnnotations; + builder.putAnnotations = putAnnotations; builder._unsetProperties.clear(); builder._unsetProperties.addAll(_unsetProperties); return (Builder) builder; @@ -1119,6 +1414,8 @@ public boolean equals(Object obj) { && Objects.equals(getterName, other.getterName) && fullyCheckedCast == other.fullyCheckedCast && Objects.equals(accessorAnnotations, other.accessorAnnotations) + && Objects.equals(getterAnnotations, other.getterAnnotations) + && Objects.equals(putAnnotations, other.putAnnotations) && Objects.equals(_unsetProperties, other._unsetProperties); } @@ -1136,6 +1433,8 @@ public int hashCode() { getterName, fullyCheckedCast, accessorAnnotations, + getterAnnotations, + putAnnotations, _unsetProperties); } @@ -1160,17 +1459,24 @@ public String toString() { if (!_unsetProperties.contains(Property.USING_BEAN_CONVENTION)) { result.append("usingBeanConvention=").append(usingBeanConvention).append(", "); } - result.append("inToString=").append(inToString).append(", "); - result.append("inEqualsAndHashCode=").append(inEqualsAndHashCode).append(", "); + result + .append("inToString=") + .append(inToString) + .append(", inEqualsAndHashCode=") + .append(inEqualsAndHashCode); if (!_unsetProperties.contains(Property.GETTER_NAME)) { - result.append("getterName=").append(getterName).append(", "); + result.append(", getterName=").append(getterName); } if (!_unsetProperties.contains(Property.FULLY_CHECKED_CAST)) { - result.append("fullyCheckedCast=").append(fullyCheckedCast).append(", "); + result.append(", fullyCheckedCast=").append(fullyCheckedCast); } return result - .append("accessorAnnotations=") + .append(", accessorAnnotations=") .append(accessorAnnotations) + .append(", getterAnnotations=") + .append(getterAnnotations) + .append(", putAnnotations=") + .append(putAnnotations) .append("}") .toString(); } diff --git a/src/it/vanilla/src/main/java/org/inferred/freebuilder/JacksonAnyGetterType.java b/src/it/vanilla/src/main/java/org/inferred/freebuilder/JacksonAnyGetterType.java new file mode 100644 index 000000000..bbab3d5a6 --- /dev/null +++ b/src/it/vanilla/src/main/java/org/inferred/freebuilder/JacksonAnyGetterType.java @@ -0,0 +1,19 @@ +package org.inferred.freebuilder; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +import java.util.Map; + +@FreeBuilder +@JsonDeserialize(builder = JacksonAnyGetterType.Builder.class) +public interface JacksonAnyGetterType { + @JsonProperty("simple_property") + String getSimpleProperty(); + @JsonAnyGetter + Map getUnknownProperties(); + + class Builder extends JacksonAnyGetterType_Builder {} +} diff --git a/src/it/vanilla/src/test/java/org/inferred/freebuilder/JacksonAnyGetterTypeTest.java b/src/it/vanilla/src/test/java/org/inferred/freebuilder/JacksonAnyGetterTypeTest.java new file mode 100644 index 000000000..aaeae822d --- /dev/null +++ b/src/it/vanilla/src/test/java/org/inferred/freebuilder/JacksonAnyGetterTypeTest.java @@ -0,0 +1,45 @@ +package org.inferred.freebuilder; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.IntNode; +import com.fasterxml.jackson.databind.node.TextNode; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class JacksonAnyGetterTypeTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void testDeserializeJsonWithAnyGetter() throws IOException { + JacksonAnyGetterType parsed = objectMapper.readValue( + "{\"simple_property\": \"simpleValue\", \"other_property\": 3}", + JacksonAnyGetterType.class); + + assertEquals("simpleValue", parsed.getSimpleProperty()); + assertNotNull(parsed.getUnknownProperties()); + assertEquals(1, parsed.getUnknownProperties().size()); + assertEquals(new IntNode(3), parsed.getUnknownProperties().get("other_property")); + } + + @Test + public void testSerializeJsonWithAnyGetter() throws JsonProcessingException { + JacksonAnyGetterType getterType = new JacksonAnyGetterType.Builder() + .setSimpleProperty("checkValue") + .putUnknownProperties("propertyOne", new TextNode("abc")) + .putUnknownProperties("propertyTwo", new IntNode(2)).build(); + + String json = objectMapper.writeValueAsString(getterType); + assertTrue("should contain simple_property", + json.contains("\"simple_property\":\"checkValue\"")); + assertTrue("should contain propertyOne", json.contains("\"propertyOne\":\"abc\"")); + assertTrue("should contain propertyTwo", json.contains("\"propertyTwo\":2")); + } + +} diff --git a/src/main/java/org/inferred/freebuilder/processor/JacksonSupport.java b/src/main/java/org/inferred/freebuilder/processor/JacksonSupport.java index 4e14c18eb..f2ad62eb5 100644 --- a/src/main/java/org/inferred/freebuilder/processor/JacksonSupport.java +++ b/src/main/java/org/inferred/freebuilder/processor/JacksonSupport.java @@ -19,15 +19,24 @@ class JacksonSupport { + private enum GenerateAnnotation { + DEFAULT, + JSON_ANY, + NONE + } + private static final String JSON_DESERIALIZE = "com.fasterxml.jackson.databind.annotation.JsonDeserialize"; private static final QualifiedName JSON_PROPERTY = QualifiedName.of("com.fasterxml.jackson.annotation", "JsonProperty"); private static final String JACKSON_XML_ANNOTATION_PACKAGE = "com.fasterxml.jackson.dataformat.xml.annotation"; + private static final QualifiedName JSON_ANY_GETTER = + QualifiedName.of("com.fasterxml.jackson.annotation", "JsonAnyGetter"); + private static final QualifiedName JSON_ANY_SETTER = + QualifiedName.of("com.fasterxml.jackson.annotation", "JsonAnySetter"); /** Annotations which disable automatic generation of JsonProperty annotations. */ private static final Set DISABLE_PROPERTY_ANNOTATIONS = ImmutableSet.of( - QualifiedName.of("com.fasterxml.jackson.annotation", "JsonAnyGetter"), QualifiedName.of("com.fasterxml.jackson.annotation", "JsonIgnore"), QualifiedName.of("com.fasterxml.jackson.annotation", "JsonUnwrapped"), QualifiedName.of("com.fasterxml.jackson.annotation", "JsonValue")); @@ -50,9 +59,20 @@ public void addJacksonAnnotations( JSON_PROPERTY); if (jsonPropertyAnnotation.isPresent()) { resultBuilder.addAccessorAnnotations(Excerpts.add("%s%n", jsonPropertyAnnotation.get())); - } else if (generateDefaultAnnotations(getterMethod)) { - resultBuilder.addAccessorAnnotations(Excerpts.add( - "@%s(\"%s\")%n", JSON_PROPERTY, resultBuilder.getName())); + } else { + switch (generateDefaultAnnotations(getterMethod)) { + case DEFAULT: + resultBuilder.addAccessorAnnotations(Excerpts.add( + "@%s(\"%s\")%n", JSON_PROPERTY, resultBuilder.getName())); + break; + case JSON_ANY: + resultBuilder.addPutAnnotations(Excerpts.add("@%s%n", JSON_ANY_SETTER)); + resultBuilder.addGetterAnnotations(Excerpts.add("@%s%n", JSON_ANY_GETTER)); + break; + case NONE: + default: + break; + } } getterMethod @@ -69,15 +89,18 @@ private boolean isXmlAnnotation(AnnotationMirror mirror) { return pkg.contentEquals(JACKSON_XML_ANNOTATION_PACKAGE); } - private static boolean generateDefaultAnnotations(ExecutableElement getterMethod) { + private static GenerateAnnotation generateDefaultAnnotations(ExecutableElement getterMethod) { for (AnnotationMirror annotationMirror : getterMethod.getAnnotationMirrors()) { TypeElement annotationTypeElement = (TypeElement) (annotationMirror.getAnnotationType().asElement()); QualifiedName annotationType = QualifiedName.of(annotationTypeElement); if (DISABLE_PROPERTY_ANNOTATIONS.contains(annotationType)) { - return false; + return GenerateAnnotation.NONE; + } + if (JSON_ANY_GETTER.equals(annotationType)) { + return GenerateAnnotation.JSON_ANY; } } - return true; + return GenerateAnnotation.DEFAULT; } } diff --git a/src/main/java/org/inferred/freebuilder/processor/property/MapProperty.java b/src/main/java/org/inferred/freebuilder/processor/property/MapProperty.java index d1e1bd025..be1b49fc6 100644 --- a/src/main/java/org/inferred/freebuilder/processor/property/MapProperty.java +++ b/src/main/java/org/inferred/freebuilder/processor/property/MapProperty.java @@ -192,8 +192,9 @@ private void addPut(SourceBuilder code) { } code.add(" null\n"); } - code.addLine(" */") - .addLine("public %s %s(%s key, %s value) {", + code.addLine(" */"); + addPutAnnotations(code); + code.addLine("public %s %s(%s key, %s value) {", datatype.getBuilder(), putMethod(property), unboxedKeyType.orElse(keyType), diff --git a/src/main/java/org/inferred/freebuilder/processor/property/NullableProperty.java b/src/main/java/org/inferred/freebuilder/processor/property/NullableProperty.java index 0ffca90b8..73c06c3b0 100644 --- a/src/main/java/org/inferred/freebuilder/processor/property/NullableProperty.java +++ b/src/main/java/org/inferred/freebuilder/processor/property/NullableProperty.java @@ -214,6 +214,7 @@ public Set getMergeActions() { @Override public void addGetterAnnotations(SourceBuilder code) { + super.addGetterAnnotations(code); for (TypeElement nullableAnnotation : nullables) { code.add("@%s ", nullableAnnotation); } diff --git a/src/main/java/org/inferred/freebuilder/processor/property/Property.java b/src/main/java/org/inferred/freebuilder/processor/property/Property.java index ba992d023..23ad4b748 100644 --- a/src/main/java/org/inferred/freebuilder/processor/property/Property.java +++ b/src/main/java/org/inferred/freebuilder/processor/property/Property.java @@ -62,6 +62,19 @@ public FieldAccess getField() { */ public abstract ImmutableList getAccessorAnnotations(); + /** + * Returns a list of annotations that should be applied to the getter method of this + * property. + */ + public abstract ImmutableList getGetterAnnotations(); + + /** + * Returns a list of annotations that should be applied to the put method of this + * property. This only applies to map properties. + * Annotation will be added to put(key, value) method. + */ + public abstract ImmutableList getPutAnnotations(); + public Property.Builder toBuilder() { return new Builder().mergeFrom(this); } diff --git a/src/main/java/org/inferred/freebuilder/processor/property/PropertyCodeGenerator.java b/src/main/java/org/inferred/freebuilder/processor/property/PropertyCodeGenerator.java index d1970fa7a..523dba348 100644 --- a/src/main/java/org/inferred/freebuilder/processor/property/PropertyCodeGenerator.java +++ b/src/main/java/org/inferred/freebuilder/processor/property/PropertyCodeGenerator.java @@ -166,7 +166,11 @@ public void addPartialFieldAssignment( public abstract Set getMergeActions(); /** Adds method annotations for the value type getter method. */ - public void addGetterAnnotations(@SuppressWarnings("unused") SourceBuilder code) {} + public void addGetterAnnotations(SourceBuilder code) { + for (Excerpt annotation : property.getGetterAnnotations()) { + code.add(annotation); + } + } /** Adds a fragment converting the value object's field to the property's type. */ public void addReadValueFragment(SourceBuilder code, Excerpt finalField) { @@ -203,6 +207,12 @@ public void addAccessorAnnotations(SourceBuilder code) { } } + public void addPutAnnotations(SourceBuilder code) { + for (Excerpt annotation : property.getPutAnnotations()) { + code.add(annotation); + } + } + @Override public boolean equals(Object obj) { if (obj == null || !getClass().isInstance(obj)) {