org.inferred.freebuilder.processor.CodeGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.inferred.freebuilder.processor.CodeGenerator.java

Source

/*
 * 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.Metadata.Property.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.PropertyCodeGenerator.IS_TEMPLATE_REQUIRED_IN_CLEAR;
import static org.inferred.freebuilder.processor.util.SourceBuilders.withIndent;

import java.io.Serializable;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;

import javax.annotation.Generated;
import javax.lang.model.element.TypeElement;

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.SourceBuilder;
import org.inferred.freebuilder.processor.util.TypeReference;

import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

/**
 * Code generation for the @{@link FreeBuilder} annotation.
 */
public class CodeGenerator {

    private static final TypeReference CUSTOM_FIELD_SERIALIZER = TypeReference.to("com.google.gwt.user.client.rpc",
            "CustomFieldSerializer");
    private static final TypeReference SERIALIZATION_EXCEPTION = TypeReference.to("com.google.gwt.user.client.rpc",
            "SerializationException");
    private static final TypeReference SERIALIZATION_STREAM_READER = TypeReference
            .to("com.google.gwt.user.client.rpc", "SerializationStreamReader");
    private static final TypeReference SERIALIZATION_STREAM_WRITER = TypeReference
            .to("com.google.gwt.user.client.rpc", "SerializationStreamWriter");

    /** Write the source code for a generated builder. */
    void writeBuilderSource(SourceBuilder code, Metadata metadata) {
        if (!metadata.hasBuilder()) {
            writeStubSource(code, metadata);
            return;
        }
        boolean hasRequiredProperties = any(metadata.getProperties(), IS_REQUIRED);
        code.addLine("/**").addLine(" * Auto-generated superclass of {@link %s},", metadata.getBuilder())
                .addLine(" * derived from the API of {@link %s}.", metadata.getType()).addLine(" */")
                .addLine("@%s(\"%s\")", Generated.class, this.getClass().getName());
        if (metadata.isGwtCompatible()) {
            code.addLine("@%s", GwtCompatible.class);
        }
        code.add("abstract class %s", metadata.getGeneratedBuilder().getSimpleName());
        if (metadata.isBuilderSerializable()) {
            code.add(" implements %s", Serializable.class);
        }
        code.addLine(" {");
        // Static fields
        if (metadata.getProperties().size() > 1) {
            code.addLine("").addLine("  private static final %1$s COMMA_JOINER = %1$s.on(\", \").skipNulls();",
                    Joiner.class);
        }
        // Property enum
        if (hasRequiredProperties) {
            addPropertyEnum(metadata, code);
        }
        // Property fields
        code.addLine("");
        for (Property property : metadata.getProperties()) {
            PropertyCodeGenerator codeGenerator = property.getCodeGenerator();
            codeGenerator.addBuilderFieldDeclaration(code);
        }
        // Unset properties
        if (hasRequiredProperties) {
            code.addLine("  private final %s<%s> _unsetProperties =", EnumSet.class, metadata.getPropertyEnum())
                    .addLine("      %s.allOf(%s.class);", EnumSet.class, metadata.getPropertyEnum());
        }
        // Setters and getters
        for (Property property : metadata.getProperties()) {
            property.getCodeGenerator().addBuilderFieldAccessors(code, metadata);
        }
        // Value type
        String inheritsFrom = getInheritanceKeyword(metadata.getType());
        code.addLine("");
        if (metadata.isGwtSerializable()) {
            // Due to a bug in GWT's handling of nested types, we have to declare Value as package scoped
            // so Value_CustomFieldSerializer can access it.
            code.addLine("  @%s(serializable = true)", GwtCompatible.class).addLine(
                    "  static final class %s %s %s {", metadata.getValueType().getSimpleName(), inheritsFrom,
                    metadata.getType());
        } else {
            code.addLine("  private static final class %s %s %s {", metadata.getValueType().getSimpleName(),
                    inheritsFrom, metadata.getType());
        }
        // 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);
            for (TypeElement nullableAnnotation : property.getNullableAnnotations()) {
                code.addLine("    @%s", nullableAnnotation);
            }
            code.addLine("    public %s %s() {", property.getType(), property.getGetterName());
            code.add("      return ");
            property.getCodeGenerator().addReadValueFragment(code, property.getName());
            code.add(";\n");
            code.addLine("    }");
        }
        // Equals
        switch (metadata.standardMethodUnderride(StandardMethod.EQUALS)) {
        case ABSENT:
            // 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())
                    .addLine("        return false;").addLine("      }")
                    .addLine("      %1$s other = (%1$s) obj;", metadata.getValueType());
            if (metadata.getProperties().isEmpty()) {
                code.addLine("      return true;");
            } else if (code.getSourceLevel().javaUtilObjects().isPresent()) {
                String prefix = "      return ";
                for (Property property : metadata.getProperties()) {
                    code.add(prefix);
                    code.add("%1$s.equals(%2$s, other.%2$s)", code.getSourceLevel().javaUtilObjects().get(),
                            property.getName());
                    prefix = "\n          && ";
                }
                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.OPTIONAL) {
                            code.addLine("      if (%1$s != other.%1$s", property.getName()).addLine(
                                    "          && (%1$s == null || !%1$s.equals(other.%1$s))) {",
                                    property.getName());
                        } else {
                            code.addLine("      if (!%1$s.equals(other.%1$s)) {", property.getName());
                        }
                    }
                    code.addLine("        return false;").addLine("      }");
                }
                code.addLine("      return true;");
            }
            code.addLine("    }");
            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())
                    .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.getSourceLevel().javaUtilObjects().isPresent()) {
                code.addLine("      return %s.hash(%s);", code.getSourceLevel().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) {
            code.addLine("").addLine("    @%s", Override.class).addLine("    public %s toString() {", String.class)
                    .add("      return \"%s{", metadata.getType().getSimpleName());
            switch (metadata.getProperties().size()) {
            case 0: {
                code.add("}\";\n");
                break;
            }

            case 1: {
                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 one or more of the properties are optional, use COMMA_JOINER for readability.
                // Otherwise, use string concatenation for performance.
                if (any(metadata.getProperties(), IS_OPTIONAL)) {
                    code.add("\"\n").add("          + COMMA_JOINER.join(\n");
                    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 {
                    code.add("\"\n");
                    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");
                        }
                    }
                }
                break;
            }
            }
            code.addLine("    }");
        }
        code.addLine("  }");
        if (metadata.isGwtSerializable()) {
            addCustomValueSerializer(metadata, code);
        }
        // build()
        code.addLine("").addLine("  /**").addLine(
                "   * Returns a newly-created {@link %s} based on the contents of the {@code %s}.",
                metadata.getType(), 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.addLine("    %s.checkState(_unsetProperties.isEmpty(), \"Not set: %%s\", _unsetProperties);",
                    Preconditions.class);
        }
        code.addLine("    return new %s(this);", metadata.getValueType()).addLine("  }");
        // mergeFrom(Value)
        code.addLine("").addLine("  /**")
                .addLine("   * Sets all property values using the given {@code %s} as a template.",
                        metadata.getType())
                .addLine("   */")
                .addLine("  public %s mergeFrom(%s value) {", metadata.getBuilder(), metadata.getType());
        for (Property property : metadata.getProperties()) {
            property.getCodeGenerator().addMergeFromValue(code, "value");
        }
        code.addLine("    return (%s) this;", metadata.getBuilder());
        code.addLine("  }");
        // mergeFrom(Builder)
        code.addLine("").addLine("  /**").addLine("   * Copies values from the given {@code %s}.",
                metadata.getBuilder().getSimpleName());
        if (hasRequiredProperties) {
            code.addLine("   * Does not affect any properties not set on the input.");
        }
        code.addLine("   */").addLine("  public %1$s mergeFrom(%1$s template) {", metadata.getBuilder());
        if (hasRequiredProperties) {
            code.addLine("    // Upcast to access the private _unsetProperties field.")
                    .addLine("    // Otherwise, oddly, we get an access violation.")
                    .addLine("    %s<%s> _templateUnset = ((%s) template)._unsetProperties;", EnumSet.class,
                            metadata.getPropertyEnum(), metadata.getGeneratedBuilder());
        }
        for (Property property : metadata.getProperties()) {
            if (property.getCodeGenerator().getType() == Type.REQUIRED) {
                code.addLine("    if (!_templateUnset.contains(%s.%s)) {", metadata.getPropertyEnum(),
                        property.getAllCapsName());
                property.getCodeGenerator().addMergeFromBuilder(withIndent(code, 2), metadata, "template");
                code.addLine("    }");
            } else {
                property.getCodeGenerator().addMergeFromBuilder(code, metadata, "template");
            }
        }
        code.addLine("    return (%s) this;", metadata.getBuilder());
        code.addLine("  }");
        // clear()
        if (metadata.getBuilderFactory().isPresent()) {
            code.addLine("").addLine("  /**").addLine("   * Resets the state of this builder.").addLine("   */")
                    .addLine("  public %s clear() {", metadata.getBuilder());
            List<PropertyCodeGenerator> codeGenerators = Lists.transform(metadata.getProperties(),
                    GET_CODE_GENERATOR);
            if (Iterables.any(codeGenerators, IS_TEMPLATE_REQUIRED_IN_CLEAR)) {
                code.add("    %s _template = ", metadata.getGeneratedBuilder());
                metadata.getBuilderFactory().get().addNewBuilder(code, metadata.getBuilder());
                code.add(";\n");
            }
            for (PropertyCodeGenerator codeGenerator : codeGenerators) {
                if (codeGenerator.isTemplateRequiredInClear()) {
                    codeGenerator.addClear(code, "_template");
                } else {
                    codeGenerator.addClear(code, null);
                }
            }
            if (hasRequiredProperties) {
                code.addLine("    _unsetProperties.clear();").addLine(
                        "    _unsetProperties.addAll(_template._unsetProperties);", metadata.getGeneratedBuilder());
            }
            code.addLine("    return (%s) this;", metadata.getBuilder()).addLine("  }");
        } else {
            code.addLine("").addLine("  /**")
                    .addLine("   * Ensures a subsequent mergeFrom call will make a clone of its input.")
                    .addLine("   *")
                    .addLine("   * <p>The exact implementation of this method is not guaranteed to remain")
                    .addLine("   * stable; it should always be followed directly by a mergeFrom call.")
                    .addLine("   */").addLine("  public %s clear() {", metadata.getBuilder());
            for (Property property : metadata.getProperties()) {
                property.getCodeGenerator().addPartialClear(code);
            }
            code.addLine("    return (%s) this;", metadata.getBuilder()).addLine("  }");
        }
        // GWT whitelist type
        if (metadata.isGwtSerializable()) {
            code.addLine("")
                    .addLine("  /** This class exists solely to ensure GWT whitelists all required types. */")
                    .addLine("  @%s(serializable = true)", GwtCompatible.class)
                    .addLine("  static final class GwtWhitelist %s %s {", inheritsFrom, metadata.getType())
                    .addLine("");
            for (Property property : metadata.getProperties()) {
                code.addLine("    %s %s;", property.getType(), property.getName());
            }
            code.addLine("").addLine("    private GwtWhitelist() {")
                    .addLine("      throw new %s();", UnsupportedOperationException.class).addLine("    }");
            for (Property property : metadata.getProperties()) {
                code.addLine("").addLine("    @%s", Override.class).addLine("    public %s %s() {",
                        property.getType(), property.getGetterName());
                code.addLine("      throw new %s();", UnsupportedOperationException.class).addLine("    }");
            }
            code.addLine("  }");
        }
        // Partial value type
        code.addLine("").addLine("  private static final class %s %s %s {",
                metadata.getPartialType().getSimpleName(), inheritsFrom, metadata.getType());
        // 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);
            for (TypeElement nullableAnnotation : property.getNullableAnnotations()) {
                code.addLine("    @%s", nullableAnnotation);
            }
            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("    }");
        }
        // 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())
                    .addLine("        return false;").addLine("      }")
                    .addLine("      %1$s other = (%1$s) obj;", metadata.getPartialType());
            if (metadata.getProperties().isEmpty()) {
                code.addLine("      return true;");
            } else if (code.getSourceLevel().javaUtilObjects().isPresent()) {
                String prefix = "      return ";
                for (Property property : metadata.getProperties()) {
                    code.add(prefix);
                    code.add("%1$s.equals(%2$s, other.%2$s)", code.getSourceLevel().javaUtilObjects().get(),
                            property.getName());
                    prefix = "\n          && ";
                }
                if (hasRequiredProperties) {
                    code.add(prefix);
                    code.add("%1$s.equals(_unsetProperties, other._unsetProperties)",
                            code.getSourceLevel().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.getSourceLevel().javaUtilObjects().isPresent()) {
                code.addLine("      return %s.hash(%s);", code.getSourceLevel().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);
            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;
            }
            }
            code.addLine("    }");
        }
        code.addLine("  }");
        // buildPartial()
        code.addLine("").addLine("  /**")
                .addLine("   * Returns a newly-created partial {@link %s}", metadata.getType())
                .addLine("   * based on the contents of the {@code %s}.", metadata.getBuilder().getSimpleName())
                .addLine("   * State checking will not be performed.");
        if (hasRequiredProperties) {
            code.addLine("   * Unset properties will throw an {@link %s}", UnsupportedOperationException.class)
                    .addLine("   * when accessed via the partial object.");
        }
        code.addLine("   *").addLine("   * <p>Partials should only ever be used in tests.").addLine("   */")
                .addLine("  @%s()", VisibleForTesting.class)
                .addLine("  public %s buildPartial() {", metadata.getType())
                .addLine("    return new %s(this);", metadata.getPartialType()).addLine("  }").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 addCustomValueSerializer(Metadata metadata, SourceBuilder code) {
        code.addLine("").addLine("  @%s", GwtCompatible.class)
                .addLine("  public static class %s_CustomFieldSerializer", metadata.getValueType().getSimpleName())
                .addLine("      extends %s<%s> {", CUSTOM_FIELD_SERIALIZER, metadata.getValueType()).addLine("")
                .addLine("    @%s", Override.class)
                .addLine("    public void deserializeInstance(%s reader, %s instance) { }",
                        SERIALIZATION_STREAM_READER, metadata.getValueType())
                .addLine("").addLine("    @%s", Override.class)
                .addLine("    public boolean hasCustomInstantiateInstance() {").addLine("      return true;")
                .addLine("    }").addLine("").addLine("    @%s", Override.class)
                .addLine("    public %s instantiateInstance(%s reader)", metadata.getValueType(),
                        SERIALIZATION_STREAM_READER)
                .addLine("        throws %s {", SERIALIZATION_EXCEPTION)
                .addLine("      %1$s builder = new %1$s();", metadata.getBuilder());
        for (Property property : metadata.getProperties()) {
            if (property.getType().getKind().isPrimitive()) {
                code.addLine("        %s %s = reader.read%s();", property.getType(), property.getName(),
                        withInitialCapital(property.getType()));
                property.getCodeGenerator().addSetFromResult(code, "builder", property.getName());
            } else if (String.class.getName().equals(property.getType().toString())) {
                code.addLine("        %s %s = reader.readString();", property.getType(), property.getName());
                property.getCodeGenerator().addSetFromResult(code, "builder", property.getName());
            } else {
                code.addLine("      try {");
                if (!property.isFullyCheckedCast()) {
                    code.addLine("        @SuppressWarnings(\"unchecked\")");
                }
                code.addLine("        %1$s %2$s = (%1$s) reader.readObject();", property.getType(),
                        property.getName());
                property.getCodeGenerator().addSetFromResult(code, "builder", property.getName());
                code.addLine("      } catch (%s e) {", ClassCastException.class)
                        .addLine("        throw new %s(", SERIALIZATION_EXCEPTION)
                        .addLine("            \"Wrong type for property '%s'\", e);", property.getName())
                        .addLine("      }");
            }
        }
        code.addLine("      return (%s) builder.build();", metadata.getValueType()).addLine("    }").addLine("")
                .addLine("    @%s", Override.class)
                .addLine("    public void serializeInstance(%s writer, %s instance)", SERIALIZATION_STREAM_WRITER,
                        metadata.getValueType())
                .addLine("        throws %s {", SERIALIZATION_EXCEPTION);
        for (Property property : metadata.getProperties()) {
            if (property.getType().getKind().isPrimitive()) {
                code.add("      writer.write%s(", withInitialCapital(property.getType()), property.getName());
            } else if (String.class.getName().equals(property.getType().toString())) {
                code.add("      writer.writeString(", property.getName());
            } else {
                code.add("      writer.writeObject(", property.getName());
            }
            property.getCodeGenerator().addReadValueFragment(code, "instance." + property.getName());
            code.add(");\n");
        }
        code.addLine("    }").addLine("")
                .addLine("    private static final Value_CustomFieldSerializer INSTANCE ="
                        + " new Value_CustomFieldSerializer();")
                .addLine("")
                .addLine("    public static void deserialize(%s reader, %s instance) {",
                        SERIALIZATION_STREAM_READER, metadata.getValueType())
                .addLine("      INSTANCE.deserializeInstance(reader, instance);").addLine("    }").addLine("")
                .addLine("    public static %s instantiate(%s reader)", metadata.getValueType(),
                        SERIALIZATION_STREAM_READER)
                .addLine("        throws %s {", SERIALIZATION_EXCEPTION)
                .addLine("      return INSTANCE.instantiateInstance(reader);").addLine("    }").addLine("")
                .addLine("    public static void serialize(%s writer, %s instance)", SERIALIZATION_STREAM_WRITER,
                        metadata.getValueType())
                .addLine("        throws %s {", SERIALIZATION_EXCEPTION)
                .addLine("      INSTANCE.serializeInstance(writer, instance);").addLine("    }").addLine("  }");
    }

    private void writeStubSource(SourceBuilder code, Metadata metadata) {
        code.addLine("/**")
                .addLine(" * Placeholder. Create {@code %s.Builder} and subclass this type.", metadata.getType())
                .addLine(" */").addLine("@%s(\"%s\")", Generated.class, this.getClass().getName())
                .addLine("abstract class %s {}", metadata.getGeneratedBuilder().getSimpleName());
    }

    /** Returns the correct keyword to use to inherit from the given type: implements, or extends. */
    private static String getInheritanceKeyword(TypeElement type) {
        if (type.getKind().isInterface()) {
            return "implements";
        } else {
            return "extends";
        }
    }

    private static String withInitialCapital(Object obj) {
        String s = obj.toString();
        return s.substring(0, 1).toUpperCase() + s.substring(1);
    }

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