/* * Copyright 2014 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.inferred.freebuilder.processor; 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.BuilderFactory.TypeInference.EXPLICIT_TYPES; import static org.inferred.freebuilder.processor.Metadata.GET_CODE_GENERATOR; import static org.inferred.freebuilder.processor.Metadata.UnderrideLevel.ABSENT; import static org.inferred.freebuilder.processor.Metadata.UnderrideLevel.FINAL; import static org.inferred.freebuilder.processor.util.ObjectsExcerpts.Nullability.NOT_NULLABLE; import static org.inferred.freebuilder.processor.util.ObjectsExcerpts.Nullability.NULLABLE; import static org.inferred.freebuilder.processor.util.feature.GuavaLibrary.GUAVA; import static org.inferred.freebuilder.processor.util.feature.SourceLevel.SOURCE_LEVEL; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import org.inferred.freebuilder.FreeBuilder; import org.inferred.freebuilder.processor.Metadata.Property; import org.inferred.freebuilder.processor.Metadata.StandardMethod; import org.inferred.freebuilder.processor.PropertyCodeGenerator.Type; import org.inferred.freebuilder.processor.util.Block; import org.inferred.freebuilder.processor.util.Excerpt; import org.inferred.freebuilder.processor.util.Excerpts; import org.inferred.freebuilder.processor.util.ObjectsExcerpts; import org.inferred.freebuilder.processor.util.PreconditionExcerpts; import org.inferred.freebuilder.processor.util.SourceBuilder; import java.io.Serializable; import java.util.Arrays; import java.util.EnumSet; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; /** * Code generation for the @{@link FreeBuilder} annotation. */ public class CodeGenerator { /** Write the source code for a generated builder. */ void writeBuilderSource(SourceBuilder code, Metadata metadata) { if (!metadata.hasBuilder()) { writeStubSource(code, metadata); return; } addBuilderTypeDeclaration(code, metadata); code.addLine(" {"); addStaticFromMethod(code, metadata); addConstantDeclarations(metadata, code); if (any(metadata.getProperties(), IS_REQUIRED)) { addPropertyEnum(metadata, code); } addFieldDeclarations(code, metadata); addAccessors(metadata, code); addMergeFromValueMethod(code, metadata); addMergeFromBuilderMethod(code, metadata); addClearMethod(code, metadata); addBuildMethod(code, metadata); addBuildPartialMethod(code, metadata); addValueType(code, metadata); addPartialType(code, metadata); for (Function<Metadata, Excerpt> nestedClass : metadata.getNestedClasses()) { code.add(nestedClass.apply(metadata)); } addStaticMethods(code, metadata); code.addLine("}"); } private void addBuilderTypeDeclaration(SourceBuilder code, Metadata metadata) { code.addLine("/**") .addLine(" * Auto-generated superclass of %s,", metadata.getBuilder().javadocLink()) .addLine(" * derived from the API of %s.", metadata.getType().javadocLink()) .addLine(" */") .add(Excerpts.generated(getClass())); for (Excerpt annotation : metadata.getGeneratedBuilderAnnotations()) { code.add(annotation); } code.add("abstract class %s", metadata.getGeneratedBuilder().declaration()); if (metadata.isBuilderSerializable()) { code.add(" implements %s", Serializable.class); } } private static void addStaticFromMethod(SourceBuilder code, Metadata metadata) { BuilderFactory builderFactory = metadata.getBuilderFactory().orNull(); if (builderFactory == null) { return; } code.addLine("") .addLine("/**") .addLine(" * Creates a new builder using {@code value} as a template.") .addLine(" */") .addLine("public static %s %s from(%s value) {", metadata.getBuilder().declarationParameters(), metadata.getBuilder(), metadata.getType()) .addLine(" return %s.mergeFrom(value);", builderFactory.newBuilder(metadata.getBuilder(), EXPLICIT_TYPES)) .addLine("}"); } private static void addConstantDeclarations(Metadata metadata, SourceBuilder body) { if (body.feature(GUAVA).isAvailable() && metadata.getProperties().size() > 1) { body.addLine("") .addLine("private static final %1$s COMMA_JOINER = %1$s.on(\", \").skipNulls();", Joiner.class); } } private static void addFieldDeclarations(SourceBuilder code, Metadata metadata) { code.addLine(""); for (Property property : metadata.getProperties()) { PropertyCodeGenerator codeGenerator = property.getCodeGenerator(); codeGenerator.addBuilderFieldDeclaration(code); } // Unset properties if (any(metadata.getProperties(), IS_REQUIRED)) { code.addLine("private final %s<%s> _unsetProperties =", EnumSet.class, metadata.getPropertyEnum()) .addLine(" %s.allOf(%s.class);", EnumSet.class, metadata.getPropertyEnum()); } } private static void addAccessors(Metadata metadata, SourceBuilder body) { for (Property property : metadata.getProperties()) { property.getCodeGenerator().addBuilderFieldAccessors(body); } } private static void addBuildMethod(SourceBuilder code, Metadata metadata) { boolean hasRequiredProperties = any(metadata.getProperties(), IS_REQUIRED); code.addLine("") .addLine("/**") .addLine(" * Returns a newly-created %s based on the contents of the {@code %s}.", metadata.getType().javadocLink(), metadata.getBuilder().getSimpleName()); if (hasRequiredProperties) { code.addLine(" *") .addLine(" * @throws IllegalStateException if any field has not been set"); } code.addLine(" */") .addLine("public %s build() {", metadata.getType()); if (hasRequiredProperties) { code.add(PreconditionExcerpts.checkState( "_unsetProperties.isEmpty()", "Not set: %s", "_unsetProperties")); } code.addLine(" return %s(this);", metadata.getValueType().constructor()) .addLine("}"); } private static void addMergeFromValueMethod(SourceBuilder code, Metadata metadata) { code.addLine("") .addLine("/**") .addLine(" * Sets all property values using the given {@code %s} as a template.", metadata.getType().getQualifiedName()) .addLine(" */") .addLine("public %s mergeFrom(%s value) {", metadata.getBuilder(), metadata.getType()); Block body = new Block(code); for (Property property : metadata.getProperties()) { property.getCodeGenerator().addMergeFromValue(body, "value"); } code.add(body) .addLine(" return (%s) this;", metadata.getBuilder()) .addLine("}"); } private static void addMergeFromBuilderMethod(SourceBuilder code, Metadata metadata) { code.addLine("") .addLine("/**") .addLine(" * Copies values from the given {@code %s}.", metadata.getBuilder().getSimpleName()) .addLine(" * Does not affect any properties not set on the input.") .addLine(" */") .addLine("public %1$s mergeFrom(%1$s template) {", metadata.getBuilder()); Block body = new Block(code); for (Property property : metadata.getProperties()) { property.getCodeGenerator().addMergeFromBuilder(body, "template"); } code.add(body) .addLine(" return (%s) this;", metadata.getBuilder()) .addLine("}"); } private static void addClearMethod(SourceBuilder code, Metadata metadata) { code.addLine("") .addLine("/**") .addLine(" * Resets the state of this builder.") .addLine(" */") .addLine("public %s clear() {", metadata.getBuilder()); Block body = new Block(code); List<PropertyCodeGenerator> codeGenerators = Lists.transform(metadata.getProperties(), GET_CODE_GENERATOR); for (PropertyCodeGenerator codeGenerator : codeGenerators) { codeGenerator.addClearField(body); } code.add(body); if (any(metadata.getProperties(), IS_REQUIRED)) { Optional<Excerpt> defaults = Declarations.freshBuilder(body, metadata); if (defaults.isPresent()) { code.addLine(" _unsetProperties.clear();") .addLine(" _unsetProperties.addAll(%s._unsetProperties);", defaults.get()); } } code.addLine(" return (%s) this;", metadata.getBuilder()) .addLine("}"); } private static void addBuildPartialMethod(SourceBuilder code, Metadata metadata) { code.addLine("") .addLine("/**") .addLine(" * Returns a newly-created partial %s", metadata.getType().javadocLink()) .addLine(" * for use in unit tests. State checking will not be performed."); if (any(metadata.getProperties(), IS_REQUIRED)) { code.addLine(" * Unset properties will throw an {@link %s}", UnsupportedOperationException.class) .addLine(" * when accessed via the partial object."); } if (metadata.getHasToBuilderMethod() && metadata.getBuilderFactory() == Optional.of(BuilderFactory.NO_ARGS_CONSTRUCTOR)) { code.addLine(" *") .addLine(" * <p>The builder returned by a partial's {@link %s#toBuilder() toBuilder}", metadata.getType()) .addLine(" * method overrides {@link %s#build() build()} to return another partial.", metadata.getBuilder()) .addLine(" * This allows for robust tests of modify-rebuild code."); } code.addLine(" *") .addLine(" * <p>Partials should only ever be used in tests. They permit writing robust") .addLine(" * test cases that won't fail if this type gains more application-level") .addLine(" * constraints (e.g. new required fields) in future. If you require partially") .addLine(" * complete values in production code, consider using a Builder.") .addLine(" */"); if (code.feature(GUAVA).isAvailable()) { code.addLine("@%s()", VisibleForTesting.class); } code.addLine("public %s buildPartial() {", metadata.getType()) .addLine(" return %s(this);", metadata.getPartialType().constructor()) .addLine("}"); } private static void addPropertyEnum(Metadata metadata, SourceBuilder code) { code.addLine("") .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()); } } code.addLine(" ;") .addLine("") .addLine(" private final %s name;", String.class) .addLine("") .addLine(" private %s(%s name) {", metadata.getPropertyEnum().getSimpleName(), String.class) .addLine(" this.name = name;") .addLine(" }") .addLine("") .addLine(" @%s public %s toString() {", Override.class, String.class) .addLine(" return name;") .addLine(" }") .addLine("}"); } private static void addValueType(SourceBuilder code, Metadata metadata) { code.addLine(""); for (Excerpt annotation : metadata.getValueTypeAnnotations()) { code.add(annotation); } code.addLine("%s static final class %s %s {", metadata.getValueTypeVisibility(), metadata.getValueType().declaration(), extending(metadata.getType(), metadata.isInterfaceType())); // Fields for (Property property : metadata.getProperties()) { property.getCodeGenerator().addValueFieldDeclaration(code, property.getName()); } // Constructor code.addLine("") .addLine(" private %s(%s builder) {", metadata.getValueType().getSimpleName(), metadata.getGeneratedBuilder()); for (Property property : metadata.getProperties()) { property.getCodeGenerator() .addFinalFieldAssignment(code, "this." + property.getName(), "builder"); } code.addLine(" }"); // Getters for (Property property : metadata.getProperties()) { code.addLine("") .addLine(" @%s", Override.class); property.getCodeGenerator().addAccessorAnnotations(code); property.getCodeGenerator().addGetterAnnotations(code); code.addLine(" public %s %s() {", property.getType(), property.getGetterName()); code.add(" return "); property.getCodeGenerator().addReadValueFragment(code, property.getName()); code.add(";\n"); code.addLine(" }"); } // toBuilder if (metadata.getHasToBuilderMethod()) { code.addLine("") .addLine(" @%s", Override.class) .addLine(" public %s toBuilder() {", metadata.getBuilder()); BuilderFactory builderFactory = metadata.getBuilderFactory().orNull(); if (builderFactory != null) { code.addLine(" return %s.mergeFrom(this);", builderFactory.newBuilder(metadata.getBuilder(), EXPLICIT_TYPES)); } else { code.addLine(" throw new %s();", UnsupportedOperationException.class); } code.addLine(" }"); } // Equals switch (metadata.standardMethodUnderride(StandardMethod.EQUALS)) { case ABSENT: addValueTypeEquals(code, metadata); break; case OVERRIDEABLE: // Partial-respecting override if a non-final user implementation exists. code.addLine("") .addLine(" @%s", Override.class) .addLine(" public boolean equals(Object obj) {") .addLine(" return (!(obj instanceof %s) && super.equals(obj));", metadata.getPartialType().getQualifiedName()) .addLine(" }"); break; case FINAL: // Cannot override if a final user implementation exists. break; } // Hash code if (metadata.standardMethodUnderride(StandardMethod.HASH_CODE) == ABSENT) { String properties = Joiner.on(", ").join(getNames(metadata.getProperties())); code.addLine("") .addLine(" @%s", Override.class) .addLine(" public int hashCode() {"); if (code.feature(SOURCE_LEVEL).javaUtilObjects().isPresent()) { code.addLine(" return %s.hash(%s);", code.feature(SOURCE_LEVEL).javaUtilObjects().get(), properties); } else { code.addLine(" return %s.hashCode(new Object[] { %s });", Arrays.class, properties); } code.addLine(" }"); } // toString if (metadata.standardMethodUnderride(StandardMethod.TO_STRING) == ABSENT) { addValueTypeToString(code, metadata); } code.addLine("}"); } private static void addValueTypeEquals(SourceBuilder code, Metadata metadata) { // Default implementation if no user implementation exists. code.addLine("") .addLine(" @%s", Override.class) .addLine(" public boolean equals(Object obj) {") .addLine(" if (!(obj instanceof %s)) {", metadata.getValueType().getQualifiedName()) .addLine(" return false;") .addLine(" }") .addLine(" %1$s other = (%1$s) obj;", metadata.getValueType().withWildcards()); if (metadata.getProperties().isEmpty()) { code.addLine(" return true;"); } else if (code.feature(SOURCE_LEVEL).javaUtilObjects().isPresent()) { String prefix = " return "; for (Property property : metadata.getProperties()) { code.add(prefix); code.add("%1$s.equals(%2$s, other.%2$s)", code.feature(SOURCE_LEVEL).javaUtilObjects().get(), property.getName()); prefix = "\n && "; } code.add(";\n"); } else { for (Property property : metadata.getProperties()) { code.addLine(" if (%s) {", ObjectsExcerpts.notEquals( property.getName(), "other." + property.getName(), property.getType().getKind(), (property.getCodeGenerator().getType() == Type.OPTIONAL) ? NULLABLE : NOT_NULLABLE)) .addLine(" return false;") .addLine(" }"); } code.addLine(" return true;"); } code.addLine(" }"); } private static void addValueTypeToString(SourceBuilder code, Metadata metadata) { code.addLine("") .addLine(" @%s", Override.class) .addLine(" public %s toString() {", String.class); switch (metadata.getProperties().size()) { case 0: { code.addLine(" return \"%s{}\";", metadata.getType().getSimpleName()); break; } case 1: { code.add(" return \"%s{", metadata.getType().getSimpleName()); Property property = getOnlyElement(metadata.getProperties()); if (property.getCodeGenerator().getType() == Type.OPTIONAL) { code.add("\" + (%1$s != null ? \"%1$s=\" + %1$s : \"\") + \"}\";\n", property.getName()); } else { code.add("%1$s=\" + %1$s + \"}\";\n", property.getName()); } break; } default: { if (!any(metadata.getProperties(), IS_OPTIONAL)) { // If none of the properties are optional, use string concatenation for performance. code.addLine(" return \"%s{\"", metadata.getType().getSimpleName()); Property lastProperty = getLast(metadata.getProperties()); for (Property property : metadata.getProperties()) { code.add(" + \"%1$s=\" + %1$s", property.getName()); if (property != lastProperty) { code.add(" + \", \"\n"); } else { code.add(" + \"}\";\n"); } } } else if (code.feature(GUAVA).isAvailable()) { // If Guava is available, use COMMA_JOINER for readability. code.addLine(" return \"%s{\"", metadata.getType().getSimpleName()) .addLine(" + COMMA_JOINER.join("); Property lastProperty = getLast(metadata.getProperties()); for (Property property : metadata.getProperties()) { code.add(" "); if (property.getCodeGenerator().getType() == Type.OPTIONAL) { code.add("(%s != null ? ", property.getName()); } code.add("\"%1$s=\" + %1$s", property.getName()); if (property.getCodeGenerator().getType() == Type.OPTIONAL) { code.add(" : null)"); } if (property != lastProperty) { code.add(",\n"); } else { code.add(")\n"); } } code.addLine(" + \"}\";"); } else { // Use StringBuilder if no better choice is available. writeToStringWithBuilder(code, metadata, false); } break; } } code.addLine(" }"); } private static void addPartialType(SourceBuilder code, Metadata metadata) { boolean hasRequiredProperties = any(metadata.getProperties(), IS_REQUIRED); code.addLine("") .addLine("private static final class %s %s {", metadata.getPartialType().declaration(), extending(metadata.getType(), metadata.isInterfaceType())); // Fields for (Property property : metadata.getProperties()) { property.getCodeGenerator().addValueFieldDeclaration(code, property.getName()); } if (hasRequiredProperties) { code.addLine(" private final %s<%s> _unsetProperties;", EnumSet.class, metadata.getPropertyEnum()); } // Constructor code.addLine("") .addLine(" %s(%s builder) {", metadata.getPartialType().getSimpleName(), metadata.getGeneratedBuilder()); for (Property property : metadata.getProperties()) { property.getCodeGenerator() .addPartialFieldAssignment(code, "this." + property.getName(), "builder"); } if (hasRequiredProperties) { code.addLine(" this._unsetProperties = builder._unsetProperties.clone();"); } code.addLine(" }"); // Getters for (Property property : metadata.getProperties()) { code.addLine("") .addLine(" @%s", Override.class); property.getCodeGenerator().addAccessorAnnotations(code); property.getCodeGenerator().addGetterAnnotations(code); code.addLine(" public %s %s() {", property.getType(), property.getGetterName()); if (property.getCodeGenerator().getType() == Type.REQUIRED) { code.addLine(" if (_unsetProperties.contains(%s.%s)) {", metadata.getPropertyEnum(), property.getAllCapsName()) .addLine(" throw new %s(\"%s not set\");", UnsupportedOperationException.class, property.getName()) .addLine(" }"); } code.add(" return "); property.getCodeGenerator().addReadValueFragment(code, property.getName()); code.add(";\n"); code.addLine(" }"); } addPartialToBuilderMethod(code, metadata); // Equals if (metadata.standardMethodUnderride(StandardMethod.EQUALS) != FINAL) { code.addLine("") .addLine(" @%s", Override.class) .addLine(" public boolean equals(Object obj) {") .addLine(" if (!(obj instanceof %s)) {", metadata.getPartialType().getQualifiedName()) .addLine(" return false;") .addLine(" }") .addLine(" %1$s other = (%1$s) obj;", metadata.getPartialType().withWildcards()); if (metadata.getProperties().isEmpty()) { code.addLine(" return true;"); } else if (code.feature(SOURCE_LEVEL).javaUtilObjects().isPresent()) { String prefix = " return "; for (Property property : metadata.getProperties()) { code.add(prefix); code.add("%1$s.equals(%2$s, other.%2$s)", code.feature(SOURCE_LEVEL).javaUtilObjects().get(), property.getName()); prefix = "\n && "; } if (hasRequiredProperties) { code.add(prefix); code.add("%1$s.equals(_unsetProperties, other._unsetProperties)", code.feature(SOURCE_LEVEL).javaUtilObjects().get()); } code.add(";\n"); } else { for (Property property : metadata.getProperties()) { switch (property.getType().getKind()) { case FLOAT: case DOUBLE: code.addLine(" if (%s.doubleToLongBits(%s)", Double.class, property.getName()) .addLine(" != %s.doubleToLongBits(other.%s)) {", Double.class, property.getName()); break; default: if (property.getType().getKind().isPrimitive()) { code.addLine(" if (%1$s != other.%1$s) {", property.getName()); } else if (property.getCodeGenerator().getType() == Type.HAS_DEFAULT) { code.addLine(" if (!%1$s.equals(other.%1$s)) {", property.getName()); } else { code.addLine(" if (%1$s != other.%1$s", property.getName()) .addLine(" && (%1$s == null || !%1$s.equals(other.%1$s))) {", property.getName()); } } code.addLine(" return false;") .addLine(" }"); } if (hasRequiredProperties) { code.addLine(" return _unsetProperties.equals(other._unsetProperties);"); } else { code.addLine(" return true;"); } } code.addLine(" }"); } // Hash code if (metadata.standardMethodUnderride(StandardMethod.HASH_CODE) != FINAL) { code.addLine("") .addLine(" @%s", Override.class) .addLine(" public int hashCode() {"); List<String> namesList = getNames(metadata.getProperties()); if (hasRequiredProperties) { namesList = ImmutableList.<String>builder().addAll(namesList).add("_unsetProperties").build(); } String properties = Joiner.on(", ").join(namesList); if (code.feature(SOURCE_LEVEL).javaUtilObjects().isPresent()) { code.addLine(" return %s.hash(%s);", code.feature(SOURCE_LEVEL).javaUtilObjects().get(), properties); } else { code.addLine(" return %s.hashCode(new Object[] { %s });", Arrays.class, properties); } code.addLine(" }"); } // toString if (metadata.standardMethodUnderride(StandardMethod.TO_STRING) != FINAL) { code.addLine("") .addLine(" @%s", Override.class) .addLine(" public %s toString() {", String.class); if (metadata.getProperties().size() > 1 && !code.feature(GUAVA).isAvailable()) { writeToStringWithBuilder(code, metadata, true); } else { writePartialToStringWithConcatenation(code, metadata); } code.addLine(" }"); } code.addLine("}"); } private static void addPartialToBuilderMethod(SourceBuilder code, Metadata metadata) { if (!metadata.getHasToBuilderMethod()) { return; } BuilderFactory builderFactory = metadata.getBuilderFactory().orNull(); if (builderFactory == BuilderFactory.NO_ARGS_CONSTRUCTOR) { code.addLine("") .addLine(" private static class PartialBuilder%s extends %s {", metadata.getBuilder().declarationParameters(), metadata.getBuilder()) .addLine(" @Override public %s build() {", metadata.getType()) .addLine(" return buildPartial();") .addLine(" }") .addLine(" }"); } code.addLine("") .addLine(" @%s", Override.class) .addLine(" public %s toBuilder() {", metadata.getBuilder()); if (builderFactory == BuilderFactory.NO_ARGS_CONSTRUCTOR) { code.addLine(" %s builder = new PartialBuilder%s();", metadata.getBuilder(), metadata.getBuilder().typeParametersOrDiamondOperator()); Block block = new Block(code); for (Property property : metadata.getProperties()) { property.getCodeGenerator().addSetBuilderFromPartial(block, "builder"); } code.add(block) .addLine(" return builder;"); } else { code.addLine(" throw new %s();", UnsupportedOperationException.class); } code.addLine(" }"); } private static void writeToStringWithBuilder( SourceBuilder code, Metadata metadata, boolean isPartial) { code.addLine("%1$s result = new %1$s(\"%2$s%3$s{\");", StringBuilder.class, isPartial ? "partial " : "", metadata.getType().getSimpleName()); boolean noDefaults = !any(metadata.getProperties(), HAS_DEFAULT); if (noDefaults) { // We need to keep track of whether to output a separator code.addLine("String separator = \"\";"); } boolean seenDefault = false; Property first = metadata.getProperties().get(0); Property last = Iterables.getLast(metadata.getProperties()); for (Property property : metadata.getProperties()) { boolean hadSeenDefault = seenDefault; switch (property.getCodeGenerator().getType()) { case HAS_DEFAULT: seenDefault = true; break; case OPTIONAL: code.addLine("if (%s != null) {", property.getName()); break; case REQUIRED: if (isPartial) { code.addLine("if (!_unsetProperties.contains(%s.%s)) {", metadata.getPropertyEnum(), property.getAllCapsName()); } break; } if (noDefaults && property != first) { code.addLine("result.append(separator);"); } else if (!noDefaults && hadSeenDefault) { code.addLine("result.append(\", \");"); } code.addLine("result.append(\"%1$s=\").append(%1$s);", property.getName()); if (!noDefaults && !seenDefault) { code.addLine("result.append(\", \");"); } else if (noDefaults && property != last) { code.addLine("separator = \", \";"); } switch (property.getCodeGenerator().getType()) { case HAS_DEFAULT: break; case OPTIONAL: code.addLine("}"); break; case REQUIRED: if (isPartial) { code.addLine("}"); } break; } } code.addLine("result.append(\"}\");") .addLine("return result.toString();"); } private static void writePartialToStringWithConcatenation(SourceBuilder code, Metadata metadata) { code.add(" return \"partial %s{", metadata.getType().getSimpleName()); switch (metadata.getProperties().size()) { case 0: { code.add("}\";\n"); break; } case 1: { Property property = getOnlyElement(metadata.getProperties()); switch (property.getCodeGenerator().getType()) { case HAS_DEFAULT: code.add("%1$s=\" + %1$s + \"}\";\n", property.getName()); break; case OPTIONAL: code.add("\"\n") .addLine(" + (%1$s != null ? \"%1$s=\" + %1$s : \"\")", property.getName()) .addLine(" + \"}\";"); break; case REQUIRED: code.add("\"\n") .addLine(" + (!_unsetProperties.contains(%s.%s)", metadata.getPropertyEnum(), property.getAllCapsName()) .addLine(" ? \"%1$s=\" + %1$s : \"\")", property.getName()) .addLine(" + \"}\";"); break; } break; } default: { code.add("\"\n") .add(" + COMMA_JOINER.join(\n"); Property lastProperty = getLast(metadata.getProperties()); for (Property property : metadata.getProperties()) { code.add(" "); switch (property.getCodeGenerator().getType()) { case HAS_DEFAULT: code.add("\"%1$s=\" + %1$s", property.getName()); break; case OPTIONAL: code.add("(%1$s != null ? \"%1$s=\" + %1$s : null)", property.getName()); break; case REQUIRED: code.add("(!_unsetProperties.contains(%s.%s)\n", metadata.getPropertyEnum(), property.getAllCapsName()) .add(" ? \"%1$s=\" + %1$s : null)", property.getName()); break; } if (property != lastProperty) { code.add(",\n"); } else { code.add(")\n"); } } code.addLine(" + \"}\";"); break; } } } private static void addStaticMethods(SourceBuilder code, Metadata metadata) { SortedSet<Excerpt> staticMethods = new TreeSet<Excerpt>(); for (Property property : metadata.getProperties()) { staticMethods.addAll(property.getCodeGenerator().getStaticExcerpts()); } for (Excerpt staticMethod : staticMethods) { code.add(staticMethod); } } private void writeStubSource(SourceBuilder code, Metadata metadata) { code.addLine("/**") .addLine(" * Placeholder. Create {@code %s.Builder} and subclass this type.", metadata.getType()) .addLine(" */") .add(Excerpts.generated(getClass())) .addLine("abstract class %s {}", metadata.getGeneratedBuilder().declaration()); } /** Returns an {@link Excerpt} of "implements/extends {@code type}". */ private static Excerpt extending(final Object type, final boolean isInterface) { return Excerpts.add(isInterface ? "implements %s" : "extends %s", type); } private static ImmutableList<String> getNames(Iterable<Property> properties) { ImmutableList.Builder<String> result = ImmutableList.builder(); for (Property property : properties) { result.add(property.getName()); } return result.build(); } private static final Predicate<Property> IS_REQUIRED = new Predicate<Property>() { @Override public boolean apply(Property property) { return property.getCodeGenerator().getType() == Type.REQUIRED; } }; private static final Predicate<Property> IS_OPTIONAL = new Predicate<Property>() { @Override public boolean apply(Property property) { return property.getCodeGenerator().getType() == Type.OPTIONAL; } }; private static final Predicate<Property> HAS_DEFAULT = new Predicate<Property>() { @Override public boolean apply(Property property) { return property.getCodeGenerator().getType() == Type.HAS_DEFAULT; } }; }