/* * 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.base.MoreObjects.firstNonNull; 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.util.ObjectsExcerpts.Nullability.NOT_NULLABLE; 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; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import org.inferred.freebuilder.processor.Metadata.Property; import org.inferred.freebuilder.processor.PropertyCodeGenerator.Config; 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.ParameterizedType; import org.inferred.freebuilder.processor.util.PreconditionExcerpts; import org.inferred.freebuilder.processor.util.SourceBuilder; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; /** Default {@link PropertyCodeGenerator.Factory}, providing reference semantics for any type. */ public class DefaultPropertyFactory implements PropertyCodeGenerator.Factory { @Override public Optional<? extends PropertyCodeGenerator> create(Config config) { Property property = config.getProperty(); boolean hasDefault = config.getMethodsInvokedInBuilderConstructor().contains(setter(property)); return Optional.of(new CodeGenerator(config.getMetadata(), property, hasDefault)); } @VisibleForTesting static class CodeGenerator extends PropertyCodeGenerator { private final boolean hasDefault; private final TypeKind kind; CodeGenerator(Metadata metadata, Property property, boolean hasDefault) { super(metadata, property); this.hasDefault = hasDefault; this.kind = property.getType().getKind(); } @Override public Type getType() { return hasDefault ? Type.HAS_DEFAULT : Type.REQUIRED; } @Override public void addBuilderFieldDeclaration(SourceBuilder code) { code.addLine("private %s %s;", property.getType(), property.getName()); } @Override public void addBuilderFieldAccessors(SourceBuilder code) { addSetter(code, metadata); addMapper(code, metadata); addGetter(code, metadata); } private void addSetter(SourceBuilder code, final Metadata metadata) { code.addLine("") .addLine("/**") .addLine(" * Sets the value to be returned by %s.", metadata.getType().javadocNoArgMethodLink(property.getGetterName())) .addLine(" *") .addLine(" * @return this {@code %s} object", metadata.getBuilder().getSimpleName()); if (!kind.isPrimitive()) { code.addLine(" * @throws NullPointerException if {@code %s} is null", property.getName()); } code.addLine(" */"); addAccessorAnnotations(code); code.addLine("public %s %s(%s %s) {", metadata.getBuilder(), setter(property), property.getType(), property.getName()); if (kind.isPrimitive()) { code.addLine(" this.%1$s = %1$s;", property.getName()); } else { code.add(checkNotNullPreamble(property.getName())) .addLine(" this.%s = %s;", property.getName(), checkNotNullInline(property.getName())); } if (!hasDefault) { code.addLine(" _unsetProperties.remove(%s.%s);", metadata.getPropertyEnum(), property.getAllCapsName()); } if ((metadata.getBuilder() == metadata.getGeneratedBuilder())) { code.addLine(" return this;"); } else { code.addLine(" return (%s) this;", metadata.getBuilder()); } code.addLine("}"); } private void addMapper(SourceBuilder code, final Metadata metadata) { ParameterizedType unaryOperator = code.feature(FUNCTION_PACKAGE).unaryOperator().orNull(); if (unaryOperator == null) { return; } code.addLine("") .addLine("/**") .addLine(" * Replaces the value to be returned by %s", metadata.getType().javadocNoArgMethodLink(property.getGetterName())) .addLine(" * by applying {@code mapper} to it and using the result.") .addLine(" *") .addLine(" * @return this {@code %s} object", metadata.getBuilder().getSimpleName()) .addLine(" * @throws NullPointerException if {@code mapper} is null" + " or returns null"); if (!hasDefault) { code.addLine(" * @throws IllegalStateException if the field has not been set"); } TypeMirror typeParam = firstNonNull(property.getBoxedType(), property.getType()); code.addLine(" */") .add("public %s %s(%s mapper) {", metadata.getBuilder(), mapper(property), unaryOperator.withParameters(typeParam)); if (!hasDefault) { code.add(PreconditionExcerpts.checkNotNull("mapper")); } code.addLine(" return %s(mapper.apply(%s()));", setter(property), getter(property)) .addLine("}"); } private void addGetter(SourceBuilder code, final Metadata metadata) { code.addLine("") .addLine("/**") .addLine(" * Returns the value that will be returned by %s.", metadata.getType().javadocNoArgMethodLink(property.getGetterName())); if (!hasDefault) { code.addLine(" *") .addLine(" * @throws IllegalStateException if the field has not been set"); } code.addLine(" */") .addLine("public %s %s() {", property.getType(), getter(property)); if (!hasDefault) { Excerpt propertyIsSet = Excerpts.add("!_unsetProperties.contains(%s.%s)", metadata.getPropertyEnum(), property.getAllCapsName()); code.add(PreconditionExcerpts.checkState(propertyIsSet, property.getName() + " not set")); } code.addLine(" return %s;", property.getName()) .addLine("}"); } @Override public void addValueFieldDeclaration(SourceBuilder code, String finalField) { code.add("private final %s %s;\n", property.getType(), finalField); } @Override public void addFinalFieldAssignment(SourceBuilder code, String finalField, String builder) { code.addLine("%s = %s.%s;", finalField, builder, property.getName()); } @Override public void addMergeFromValue(Block code, String value) { Excerpt defaults = Declarations.freshBuilder(code, metadata).orNull(); if (defaults != null) { code.add("if ("); if (!hasDefault) { code.add("%s._unsetProperties.contains(%s.%s) || ", defaults, metadata.getPropertyEnum(), property.getAllCapsName()); } code.add(ObjectsExcerpts.notEquals( Excerpts.add("%s.%s()", value, property.getGetterName()), Excerpts.add("%s.%s()", defaults, getter(property)), kind, NOT_NULLABLE)); code.add(") {%n"); } code.addLine(" %s(%s.%s());", setter(property), value, property.getGetterName()); if (defaults != null) { code.addLine("}"); } } @Override public void addMergeFromBuilder(Block code, String builder) { Excerpt base = hasDefault ? null : Declarations.upcastToGeneratedBuilder(code, metadata, builder); Excerpt defaults = Declarations.freshBuilder(code, metadata).orNull(); if (defaults != null) { code.add("if ("); if (!hasDefault) { code.add("!%s._unsetProperties.contains(%s.%s) && ", base, metadata.getPropertyEnum(), property.getAllCapsName()) .add("(%s._unsetProperties.contains(%s.%s) ||", defaults, metadata.getPropertyEnum(), property.getAllCapsName()); } code.add(ObjectsExcerpts.notEquals( Excerpts.add("%s.%s()", builder, getter(property)), Excerpts.add("%s.%s()", defaults, getter(property)), kind, NOT_NULLABLE)); if (!hasDefault) { code.add(")"); } code.add(") {%n"); } else if (!hasDefault) { code.addLine("if (!%s._unsetProperties.contains(%s.%s)) {", base, metadata.getPropertyEnum(), property.getAllCapsName()); } code.addLine(" %s(%s.%s());", setter(property), builder, getter(property)); if (defaults != null || !hasDefault) { code.addLine("}"); } } @Override public void addSetBuilderFromPartial(Block code, String builder) { if (!hasDefault) { code.add("if (!_unsetProperties.contains(%s.%s)) {", metadata.getPropertyEnum(), property.getAllCapsName()); } code.addLine(" %s.%s(%s);", builder, setter(property), property.getName()); if (!hasDefault) { code.addLine("}"); } } @Override public void addSetFromResult(SourceBuilder code, String builder, String variable) { code.addLine("%s.%s(%s);", builder, setter(property), variable); } @Override public void addClearField(Block code) { Optional<Excerpt> defaults = Declarations.freshBuilder(code, metadata); // Cannot clear property without defaults if (defaults.isPresent()) { code.addLine("%1$s = %2$s.%1$s;", property.getName(), defaults.get()); } } } }