diff --git a/src/main/java/org/inferred/freebuilder/processor/Analyser.java b/src/main/java/org/inferred/freebuilder/processor/Analyser.java index 1167ca6ac..c7659744d 100644 --- a/src/main/java/org/inferred/freebuilder/processor/Analyser.java +++ b/src/main/java/org/inferred/freebuilder/processor/Analyser.java @@ -172,7 +172,8 @@ Metadata analyse(TypeElement type) throws CannotGenerateCodeException { .addAllProperties(codeGenerators(properties, baseMetadata, builder.get())); metadataBuilder.addAllSuperBuilderTypes(superBuilders(type)); - metadataBuilder.putAllSuperTypeProperties(processSuperTypeProperties(type, baseMetadata, builder)); + metadataBuilder.putAllSuperTypeProperties( + processSuperTypeProperties(type, baseMetadata, builder)); } return metadataBuilder.build(); } @@ -181,22 +182,24 @@ private ImmutableMap> processSuperTyp TypeElement type, Metadata baseMetadata, Optional builder) throws CannotGenerateCodeException { - // For mergeFrom - iterate all super types, add properties from all supertypes. - Map> toRet = new HashMap>(); + Map> toRet = + new HashMap>(); final ImmutableSet superTypes = MethodFinder.getSupertypes(type); - for(TypeElement superType : superTypes){ - if (superType.equals(type)){ + for (TypeElement superType : superTypes) { + if (superType.equals(type)) { continue; } final ImmutableSet superMethods = methodsOn(superType, elements); - final Map superPropertiesRet = findProperties(superType, superMethods); - if (superPropertiesRet.isEmpty()){ + final Map superPropertiesRet = + findProperties(superType, superMethods); + if (superPropertiesRet.isEmpty()) { continue; } - ParameterizedType pType = QualifiedName.of(superType).withParameters(superType.getTypeParameters()); + ParameterizedType pType = QualifiedName.of(superType).withParameters( + superType.getTypeParameters()); // Code builder dance if (builder.isPresent()) { @@ -225,27 +228,23 @@ private ImmutableMap> processSuperTyp return ImmutableMap.copyOf(toRet); } - private ImmutableSet superBuilders(TypeElement type) throws CannotGenerateCodeException { + private ImmutableSet superBuilders(TypeElement type) + throws CannotGenerateCodeException { Set toRet = new HashSet(); PackageElement pkg = elements.getPackageOf(type); final ImmutableSet superTypes = MethodFinder.getSupertypes(type); - for(TypeElement superType : superTypes){ - if (superType.equals(type)){ + for (TypeElement superType : superTypes) { + if (superType.equals(type)) { continue; } final Optional freeBuilderMirror = findAnnotationMirror(superType, FreeBuilder.class); - // For now we support mergeFrom(superBuilder) only for builders from the same package - // Due to package local visibility of the Enum Property - if (!pkg.getQualifiedName().contentEquals(elements.getPackageOf(superType).getQualifiedName())){ - continue; - } - - if (freeBuilderMirror.isPresent()){ - ParameterizedType pType = QualifiedName.of(superType).withParameters(superType.getTypeParameters()); + if (freeBuilderMirror.isPresent()) { + ParameterizedType pType = QualifiedName.of(superType).withParameters( + superType.getTypeParameters()); toRet.add(pType); } } diff --git a/src/main/java/org/inferred/freebuilder/processor/BuilderMethods.java b/src/main/java/org/inferred/freebuilder/processor/BuilderMethods.java index 08768c659..d1664e8c9 100644 --- a/src/main/java/org/inferred/freebuilder/processor/BuilderMethods.java +++ b/src/main/java/org/inferred/freebuilder/processor/BuilderMethods.java @@ -77,5 +77,9 @@ public static String clearMethod(Property property) { return "clear" + property.getCapitalizedName(); } + public static String isPropertySetMethod(Property property) { + return "isProperty" + property.getCapitalizedName() + "Set"; + } + private BuilderMethods() {} } diff --git a/src/main/java/org/inferred/freebuilder/processor/CodeGenerator.java b/src/main/java/org/inferred/freebuilder/processor/CodeGenerator.java index 12ea0efa5..8bc505b97 100644 --- a/src/main/java/org/inferred/freebuilder/processor/CodeGenerator.java +++ b/src/main/java/org/inferred/freebuilder/processor/CodeGenerator.java @@ -18,6 +18,7 @@ import static com.google.common.collect.Iterables.any; import static com.google.common.collect.Iterables.getLast; import static com.google.common.collect.Iterables.getOnlyElement; +import static org.inferred.freebuilder.processor.BuilderMethods.isPropertySetMethod; import static org.inferred.freebuilder.processor.BuilderFactory.TypeInference.EXPLICIT_TYPES; import static org.inferred.freebuilder.processor.Metadata.GET_CODE_GENERATOR; import static org.inferred.freebuilder.processor.Metadata.UnderrideLevel.ABSENT; @@ -81,7 +82,7 @@ void writeBuilderSource(SourceBuilder code, Metadata metadata) { addMergeFromBuilderMethod(code, metadata); addMergeFromSuperTypes(code, metadata); addClearMethod(code, metadata); - addIsPropertyUnsetMethod(code, metadata); + addPropertiesSetMethods(code, metadata); addBuildMethod(code, metadata); addBuildPartialMethod(code, metadata); @@ -209,16 +210,19 @@ private static void addMergeFromBuilderMethod(SourceBuilder code, Metadata metad } private static void addMergeFromSuperTypes(SourceBuilder code, Metadata metadata) { - for(Map.Entry> e : metadata.getSuperTypeProperties().entrySet()){ + for (Map.Entry> e + : metadata.getSuperTypeProperties().entrySet()) { final ParameterizedType type = e.getKey(); final ImmutableList properties = e.getValue(); // mergeFrom - value code.addLine("") .addLine("/**") - .addLine(" * Sets all property values using the given {@code %s} as a template.", type.getQualifiedName()) + .addLine(" * Sets all property values using the given {@code %s} as a template.", + type.getQualifiedName()) .addLine(" */") - .addLine("public %s mergeFrom(%s value) {", metadata.getBuilder(), type.getQualifiedName()); + .addLine("public %s mergeFrom(%s value) {", + metadata.getBuilder(), type.getQualifiedName()); Block body = new Block(code); for (Property property : properties) { property.getCodeGenerator().addMergeFromSuperValue(body, "value"); @@ -228,7 +232,7 @@ private static void addMergeFromSuperTypes(SourceBuilder code, Metadata metadata .addLine("}"); // has builder ? - if (!metadata.getSuperBuilderTypes().contains(type)){ + if (!metadata.getSuperBuilderTypes().contains(type)) { continue; } @@ -274,18 +278,28 @@ private static void addClearMethod(SourceBuilder code, Metadata metadata) { .addLine("}"); } - private static void addIsPropertyUnsetMethod(SourceBuilder code, Metadata metadata) { + private static void addPropertiesSetMethods(SourceBuilder code, Metadata metadata) { if (!any(metadata.getProperties(), IS_REQUIRED)) { return; } - code.addLine("") - .addLine("/**") - .addLine(" * Returns true if the required property is not set.") - .addLine(" */") - .addLine("public boolean isPropertyUnset(%s property) {", metadata.getPropertyEnum()) - .addLine(" return _unsetProperties.contains(property);") - .addLine("}"); + + for (Property property : metadata.getProperties()) { + if (!IS_REQUIRED.apply(property)) { + continue; + } + + code.addLine("") + .addLine("/**") + .addLine(" * Returns true if the required property corresponding to") + .addLine(" * %s is set. ", metadata.getType().javadocNoArgMethodLink( + property.getGetterName())) + .addLine(" */") + .addLine("public boolean %s() {", isPropertySetMethod(property)) + .addLine(" return _unsetProperties.contains(%s.%s);", + metadata.getPropertyEnum(), property.getAllCapsName()) + .addLine("}"); + } } private static void addBuildPartialMethod(SourceBuilder code, Metadata metadata) { @@ -313,7 +327,7 @@ private static void addBuildPartialMethod(SourceBuilder code, Metadata metadata) private static void addPropertyEnum(Metadata metadata, SourceBuilder code) { code.addLine("") - .addLine("enum %s {", metadata.getPropertyEnum().getSimpleName()); + .addLine("private enum %s {", metadata.getPropertyEnum().getSimpleName()); for (Property property : metadata.getProperties()) { if (property.getCodeGenerator().getType() == Type.REQUIRED) { code.addLine(" %s(\"%s\"),", property.getAllCapsName(), property.getName()); diff --git a/src/main/java/org/inferred/freebuilder/processor/DefaultPropertyFactory.java b/src/main/java/org/inferred/freebuilder/processor/DefaultPropertyFactory.java index c72385bae..fb19e85b1 100644 --- a/src/main/java/org/inferred/freebuilder/processor/DefaultPropertyFactory.java +++ b/src/main/java/org/inferred/freebuilder/processor/DefaultPropertyFactory.java @@ -19,6 +19,7 @@ import static org.inferred.freebuilder.processor.BuilderMethods.getter; import static org.inferred.freebuilder.processor.BuilderMethods.mapper; import static org.inferred.freebuilder.processor.BuilderMethods.setter; +import static org.inferred.freebuilder.processor.BuilderMethods.isPropertySetMethod; import static org.inferred.freebuilder.processor.util.PreconditionExcerpts.checkNotNullInline; import static org.inferred.freebuilder.processor.util.PreconditionExcerpts.checkNotNullPreamble; import static org.inferred.freebuilder.processor.util.feature.FunctionPackage.FUNCTION_PACKAGE; @@ -207,17 +208,26 @@ public void addMergeFromSuperBuilder(Block code, String builder) { } public void addMergeFromBuilder(Block code, String builder, boolean fromSuper) { - Excerpt base = - hasDefault ? null : Declarations.upcastToGeneratedBuilder(code, metadata, builder); + Excerpt base = hasDefault || fromSuper ? null : Declarations.upcastToGeneratedBuilder( + code, metadata, builder); Excerpt defaults = Declarations.freshBuilder(code, metadata).orNull(); - String unsetContains = fromSuper ? "isPropertyUnset" : "_unsetProperties.contains"; + Block unsetContains = new Block(code); + String isPropertySetPositive = fromSuper ? "" : "!"; + String isPropertySetNegative = fromSuper ? "!" : ""; + if (fromSuper) { + unsetContains.add("%s()", isPropertySetMethod(property)); + base = new Block(code).add(builder); + } else { + unsetContains.add("_unsetProperties.contains(%s.%s)", + metadata.getPropertyEnum(), property.getAllCapsName()); + } if (defaults != null) { code.add("if ("); if (!hasDefault) { - code.add("!%s.%s(%s.%s) && ", - base, unsetContains, metadata.getPropertyEnum(), property.getAllCapsName()) - .add("(%s.%s(%s.%s) ||", - defaults, unsetContains, metadata.getPropertyEnum(), property.getAllCapsName()); + code.add("%s%s.%s && ", + isPropertySetPositive, base, unsetContains) + .add("(%s%s.%s ||", + isPropertySetNegative, defaults, unsetContains); } if (isPrimitive) { code.add("%1$s.%2$s() != %3$s.%2$s()", builder, getter(property), defaults); @@ -229,8 +239,8 @@ public void addMergeFromBuilder(Block code, String builder, boolean fromSuper) { } code.add(") {%n"); } else if (!hasDefault) { - code.addLine("if (!%s.%s(%s.%s)) {", - base, unsetContains, metadata.getPropertyEnum(), property.getAllCapsName()); + code.addLine("if (%s%s.%s) {", + isPropertySetPositive, base, unsetContains); } code.addLine(" %s(%s.%s());", setter(property), builder, getter(property)); if (defaults != null || !hasDefault) { diff --git a/src/main/java/org/inferred/freebuilder/processor/ListPropertyFactory.java b/src/main/java/org/inferred/freebuilder/processor/ListPropertyFactory.java index ece5b40c3..4f2e4c9e3 100644 --- a/src/main/java/org/inferred/freebuilder/processor/ListPropertyFactory.java +++ b/src/main/java/org/inferred/freebuilder/processor/ListPropertyFactory.java @@ -369,7 +369,7 @@ public void addMergeFromBuilder(Block code, String builder) { @Override public void addMergeFromSuperBuilder(Block code, String builder) { - Excerpt base = Declarations.upcastToGeneratedBuilder(code, metadata, builder); + Excerpt base = new Block(code).add(builder); code.addLine("%s(%s.%s());", addAllMethod(property), base, getter(property)); } diff --git a/src/test/java/org/inferred/freebuilder/processor/AnalyserTest.java b/src/test/java/org/inferred/freebuilder/processor/AnalyserTest.java index 6fe1c69bb..760f9500a 100644 --- a/src/test/java/org/inferred/freebuilder/processor/AnalyserTest.java +++ b/src/test/java/org/inferred/freebuilder/processor/AnalyserTest.java @@ -1432,7 +1432,8 @@ public void mergeFromSuperType() assertThat(metadata.getSuperTypeProperties()).hasSize(1); assertThat(metadata.getSuperTypeProperties()).containsKey(superType); assertThat(metadata.getSuperTypeProperties().get(superType)).hasSize(1); - assertThat(metadata.getSuperTypeProperties().get(superType).get(0).getName()).isEqualTo("alpha"); + assertThat(metadata.getSuperTypeProperties().get(superType).get(0).getName()). + isEqualTo("alpha"); } private static String asSource(Excerpt annotation) { diff --git a/src/test/java/org/inferred/freebuilder/processor/DefaultedPropertiesSourceTest.java b/src/test/java/org/inferred/freebuilder/processor/DefaultedPropertiesSourceTest.java index 3b490ab21..7679844a4 100644 --- a/src/test/java/org/inferred/freebuilder/processor/DefaultedPropertiesSourceTest.java +++ b/src/test/java/org/inferred/freebuilder/processor/DefaultedPropertiesSourceTest.java @@ -887,6 +887,22 @@ public void testJ6_noGuava_oneDefaultProperty() { " }", "", " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getName()} is set.", + " */", + " public boolean isPropertyNameSet() {", + " return _unsetProperties.contains(Person_Builder.Property.NAME);", + " }", + "", + " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getShoeSize()} is set.", + " */", + " public boolean isPropertyShoeSizeSet() {", + " return _unsetProperties.contains(Person_Builder.Property.SHOE_SIZE);", + " }", + "", + " /**", " * Returns a newly-created {@link Person} based on the contents of the {@code Builder}.", " *", " * @throws IllegalStateException if any field has not been set", diff --git a/src/test/java/org/inferred/freebuilder/processor/GenericTypeSourceTest.java b/src/test/java/org/inferred/freebuilder/processor/GenericTypeSourceTest.java index 904fd9bee..21405e926 100644 --- a/src/test/java/org/inferred/freebuilder/processor/GenericTypeSourceTest.java +++ b/src/test/java/org/inferred/freebuilder/processor/GenericTypeSourceTest.java @@ -178,6 +178,22 @@ public void testJ6() { " }", "", " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getName()} is set.", + " */", + " public boolean isPropertyNameSet() {", + " return _unsetProperties.contains(Person_Builder.Property.NAME);", + " }", + "", + " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getAge()} is set.", + " */", + " public boolean isPropertyAgeSet() {", + " return _unsetProperties.contains(Person_Builder.Property.AGE);", + " }", + "", + " /**", " * Returns a newly-created {@link Person} based on the contents of the {@code Builder}.", " *", " * @throws IllegalStateException if any field has not been set", @@ -448,6 +464,22 @@ public void testJ7() { " }", "", " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getName()} is set.", + " */", + " public boolean isPropertyNameSet() {", + " return _unsetProperties.contains(Person_Builder.Property.NAME);", + " }", + "", + " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getAge()} is set.", + " */", + " public boolean isPropertyAgeSet() {", + " return _unsetProperties.contains(Person_Builder.Property.AGE);", + " }", + "", + " /**", " * Returns a newly-created {@link Person} based on the contents of the {@code Builder}.", " *", " * @throws IllegalStateException if any field has not been set", @@ -733,6 +765,22 @@ public void testJ8() { " }", "", " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getName()} is set.", + " */", + " public boolean isPropertyNameSet() {", + " return _unsetProperties.contains(Person_Builder.Property.NAME);", + " }", + "", + " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getAge()} is set.", + " */", + " public boolean isPropertyAgeSet() {", + " return _unsetProperties.contains(Person_Builder.Property.AGE);", + " }", + "", + " /**", " * Returns a newly-created {@link Person} based on the contents of the {@code Builder}.", " *", " * @throws IllegalStateException if any field has not been set", diff --git a/src/test/java/org/inferred/freebuilder/processor/RequiredPropertiesSourceTest.java b/src/test/java/org/inferred/freebuilder/processor/RequiredPropertiesSourceTest.java index 03ad27465..b90658f77 100644 --- a/src/test/java/org/inferred/freebuilder/processor/RequiredPropertiesSourceTest.java +++ b/src/test/java/org/inferred/freebuilder/processor/RequiredPropertiesSourceTest.java @@ -178,6 +178,22 @@ public void testJ6() { " }", "", " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getName()} is set.", + " */", + " public boolean isPropertyNameSet() {", + " return _unsetProperties.contains(Person_Builder.Property.NAME);", + " }", + "", + " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getAge()} is set.", + " */", + " public boolean isPropertyAgeSet() {", + " return _unsetProperties.contains(Person_Builder.Property.AGE);", + " }", + "", + " /**", " * Returns a newly-created {@link Person} based on the contents of the {@code Builder}.", " *", " * @throws IllegalStateException if any field has not been set", @@ -447,6 +463,22 @@ public void testJ7() { " }", "", " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getName()} is set.", + " */", + " public boolean isPropertyNameSet() {", + " return _unsetProperties.contains(Person_Builder.Property.NAME);", + " }", + "", + " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getAge()} is set.", + " */", + " public boolean isPropertyAgeSet() {", + " return _unsetProperties.contains(Person_Builder.Property.AGE);", + " }", + "", + " /**", " * Returns a newly-created {@link Person} based on the contents of the {@code Builder}.", " *", " * @throws IllegalStateException if any field has not been set", @@ -708,6 +740,22 @@ public void testJ6_noGuava() { " }", "", " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getName()} is set.", + " */", + " public boolean isPropertyNameSet() {", + " return _unsetProperties.contains(Person_Builder.Property.NAME);", + " }", + "", + " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getAge()} is set.", + " */", + " public boolean isPropertyAgeSet() {", + " return _unsetProperties.contains(Person_Builder.Property.AGE);", + " }", + "", + " /**", " * Returns a newly-created {@link Person} based on the contents of the {@code Builder}.", " *", " * @throws IllegalStateException if any field has not been set", @@ -981,6 +1029,22 @@ public void testJ7_noGuava() { " }", "", " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getName()} is set.", + " */", + " public boolean isPropertyNameSet() {", + " return _unsetProperties.contains(Person_Builder.Property.NAME);", + " }", + "", + " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getAge()} is set.", + " */", + " public boolean isPropertyAgeSet() {", + " return _unsetProperties.contains(Person_Builder.Property.AGE);", + " }", + "", + " /**", " * Returns a newly-created {@link Person} based on the contents of the {@code Builder}.", " *", " * @throws IllegalStateException if any field has not been set", @@ -1270,6 +1334,22 @@ public void testJ8() { " }", "", " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getName()} is set.", + " */", + " public boolean isPropertyNameSet() {", + " return _unsetProperties.contains(Person_Builder.Property.NAME);", + " }", + "", + " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#getAge()} is set.", + " */", + " public boolean isPropertyAgeSet() {", + " return _unsetProperties.contains(Person_Builder.Property.AGE);", + " }", + "", + " /**", " * Returns a newly-created {@link Person} based on the contents of the {@code Builder}.", " *", " * @throws IllegalStateException if any field has not been set", @@ -1528,6 +1608,22 @@ public void testPrefixless() { " }", "", " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#name()} is set.", + " */", + " public boolean isPropertyNameSet() {", + " return _unsetProperties.contains(Person_Builder.Property.NAME);", + " }", + "", + " /**", + " * Returns true if the required property corresponding to", + " * {@link Person#age()} is set.", + " */", + " public boolean isPropertyAgeSet() {", + " return _unsetProperties.contains(Person_Builder.Property.AGE);", + " }", + "", + " /**", " * Returns a newly-created {@link Person} based on the contents of the {@code Builder}.", " *", " * @throws IllegalStateException if any field has not been set",