org.inferred.freebuilder.processor.property.BuildableProperty Maven / Gradle / Ivy
Show all versions of freebuilder Show documentation
/*
* 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.property;
import static org.inferred.freebuilder.processor.BuildableType.maybeBuilder;
import static org.inferred.freebuilder.processor.BuilderFactory.TypeInference.EXPLICIT_TYPES;
import static org.inferred.freebuilder.processor.BuilderMethods.getBuilderMethod;
import static org.inferred.freebuilder.processor.BuilderMethods.mutator;
import static org.inferred.freebuilder.processor.BuilderMethods.setter;
import static org.inferred.freebuilder.processor.model.ModelUtils.maybeAsTypeElement;
import static org.inferred.freebuilder.processor.model.ModelUtils.maybeDeclared;
import static org.inferred.freebuilder.processor.source.FunctionalType.consumer;
import static org.inferred.freebuilder.processor.source.FunctionalType.functionalTypeAcceptedByMethod;
import com.google.common.collect.ImmutableSet;
import org.inferred.freebuilder.processor.BuildableType;
import org.inferred.freebuilder.processor.BuildableType.MergeBuilderMethod;
import org.inferred.freebuilder.processor.BuildableType.PartialToBuilderMethod;
import org.inferred.freebuilder.processor.Datatype;
import org.inferred.freebuilder.processor.Declarations;
import org.inferred.freebuilder.processor.source.Excerpt;
import org.inferred.freebuilder.processor.source.Excerpts;
import org.inferred.freebuilder.processor.source.FunctionalType;
import org.inferred.freebuilder.processor.source.SourceBuilder;
import org.inferred.freebuilder.processor.source.Variable;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import javax.lang.model.type.DeclaredType;
/**
* {@link PropertyCodeGenerator} providing fluent methods for fields of a {@link BuildableType}.
*/
class BuildableProperty extends PropertyCodeGenerator {
static class Factory implements PropertyCodeGenerator.Factory {
@Override
public Optional create(Config config) {
DeclaredType datatype = maybeDeclared(config.getProperty().getType()).orElse(null);
if (datatype == null) {
return Optional.empty();
}
DeclaredType builder = maybeBuilder(datatype, config.getElements(), config.getTypes())
.orElse(null);
if (builder == null) {
return Optional.empty();
}
BuildableType type = BuildableType.create(
datatype, builder, config.getElements(), config.getTypes());
// Find any mutate method override
FunctionalType mutatorType = functionalTypeAcceptedByMethod(
config.getBuilder(),
mutator(config.getProperty()),
consumer(builder),
config.getElements(),
config.getTypes());
return Optional.of(new BuildableProperty(
config.getDatatype(), config.getProperty(), type, mutatorType));
}
}
private final BuildableType type;
private final FunctionalType mutatorType;
private BuildableProperty(
Datatype datatype,
Property property,
BuildableType type,
FunctionalType mutatorType) {
super(datatype, property);
this.type = type;
this.mutatorType = mutatorType;
}
@Override
public void addValueFieldDeclaration(SourceBuilder code) {
code.addLine("private final %s %s;", property.getType(), property.getField());
}
@Override
public void addBuilderFieldDeclaration(SourceBuilder code) {
code.addLine("private Object %s = null;", property.getField());
}
@Override
public void addBuilderFieldAccessors(SourceBuilder code) {
addSetter(code);
addSetterTakingBuilder(code);
addMutate(code);
addGetter(code);
}
private void addSetter(SourceBuilder code) {
Variable builder = new Variable("builder");
code.addLine("")
.addLine("/**")
.addLine(" * Sets the value to be returned by %s.",
datatype.getType().javadocNoArgMethodLink(property.getGetterName()))
.addLine(" *")
.addLine(" * @return this {@code %s} object", datatype.getBuilder().getSimpleName())
.addLine(" * @throws NullPointerException if {@code %s} is null", property.getName())
.addLine(" */");
addAccessorAnnotations(code);
code.addLine("public %s %s(%s %s) {",
datatype.getBuilder(),
setter(property),
property.getType(),
property.getName())
.addLine(" %s.requireNonNull(%s);", Objects.class, property.getName())
.addLine(" if (%1$s == null || %1$s instanceof %2$s) {",
property.getField(), maybeAsTypeElement(property.getType()).get())
.addLine(" %s = %s;", property.getField(), property.getName())
.addLine(" } else {")
.addLine(" %1$s %2$s %3$s = (%2$s) %4$s;",
type.suppressUnchecked(), type.builderType(), builder, property.getField())
.addLine(" %s.clear();", builder)
.addLine(" %s.mergeFrom(%s);", builder, property.getName())
.addLine(" }")
.addLine(" return (%s) this;", datatype.getBuilder())
.addLine("}");
}
private void addSetterTakingBuilder(SourceBuilder code) {
code.addLine("")
.addLine("/**")
.addLine(" * Sets the value to be returned by %s.",
datatype.getType().javadocNoArgMethodLink(property.getGetterName()))
.addLine(" *")
.addLine(" * @return this {@code %s} object", datatype.getBuilder().getSimpleName())
.addLine(" * @throws NullPointerException if {@code builder} is null")
.addLine(" */")
.addLine("public %s %s(%s builder) {",
datatype.getBuilder(),
setter(property),
type.builderType())
.addLine(" return %s(builder.build());", setter(property))
.addLine("}");
}
private void addMutate(SourceBuilder code) {
code.addLine("")
.addLine("/**")
.addLine(" * Applies {@code mutator} to the builder for the value that will be")
.addLine(" * returned by %s.",
datatype.getType().javadocNoArgMethodLink(property.getGetterName()))
.addLine(" *")
.addLine(" * This method mutates the builder in-place. {@code mutator} is a void")
.addLine(" * consumer, so any value returned from a lambda will be ignored.")
.addLine(" *")
.addLine(" * @return this {@code %s} object", datatype.getBuilder().getSimpleName())
.addLine(" * @throws NullPointerException if {@code mutator} is null")
.addLine(" */")
.addLine("public %s %s(%s mutator) {",
datatype.getBuilder(),
mutator(property),
mutatorType.getFunctionalInterface())
.addLine(" mutator.%s(%s());", mutatorType.getMethodName(), getBuilderMethod(property))
.addLine(" return (%s) this;", datatype.getBuilder())
.addLine("}");
}
private void addGetter(SourceBuilder code) {
Variable builder = new Variable("builder");
Variable value = new Variable("value");
code.addLine("")
.addLine("/**")
.addLine(" * Returns a builder for the value that will be returned by %s.",
datatype.getType().javadocNoArgMethodLink(property.getGetterName()))
.addLine(" */")
.addLine("public %s %s() {", type.builderType(), getBuilderMethod(property))
.addLine(" if (%s == null) {", property.getField())
.addLine(" %s = %s;", property.getField(), type.newBuilder(EXPLICIT_TYPES))
.addLine(" } else if (%s instanceof %s) {",
property.getField(), maybeAsTypeElement(property.getType()).get())
.addLine(" %1$s %2$s %3$s = (%2$s) %4$s;",
type.suppressUnchecked(), property.getType(), value, property.getField());
if (type.partialToBuilder() == PartialToBuilderMethod.TO_BUILDER_AND_MERGE) {
code.addLine(" %s = %s.toBuilder();", property.getField(), value);
} else {
code.addLine(" %s = %s", property.getField(), type.newBuilder(EXPLICIT_TYPES))
.addLine(" .mergeFrom(%s);", value);
}
code.addLine(" }")
.addLine(" %1$s %2$s %3$s = (%2$s) %4$s;",
type.suppressUnchecked(), type.builderType(), builder, property.getField())
.addLine(" return %s;", builder)
.addLine("}");
}
@Override
public void addFinalFieldAssignment(SourceBuilder code, Excerpt finalField, String builder) {
addFieldAssignment(code, finalField, builder, "build");
}
@Override
public void addPartialFieldAssignment(SourceBuilder code, Excerpt finalField, String builder) {
addFieldAssignment(code, finalField, builder, "buildPartial");
}
private void addFieldAssignment(
SourceBuilder code,
Excerpt finalField,
String builder,
String buildMethod) {
Variable fieldBuilder = new Variable(property.getName() + "Builder");
Variable fieldValue = new Variable(property.getName() + "Value");
code.addLine("if (%s == null) {", property.getField().on(builder))
.addLine(" %s = %s.%s();", finalField, type.newBuilder(EXPLICIT_TYPES), buildMethod)
.addLine("} else if (%s instanceof %s) {",
property.getField().on(builder), maybeAsTypeElement(property.getType()).get());
if (type.suppressUnchecked() != Excerpts.EMPTY) {
code.addLine(" %1$s %2$s %3$s = (%2$s) %4$s;",
type.suppressUnchecked(),
property.getType(),
fieldValue,
property.getField().on(builder))
.addLine(" %s = %s;", finalField, fieldValue);
} else {
code.addLine(" %s = (%s) %s;",
finalField, property.getType(), property.getField().on(builder));
}
code.addLine("} else {")
.addLine(" %1$s %2$s %3$s = (%2$s) %4$s;",
type.suppressUnchecked(),
type.builderType(),
fieldBuilder,
property.getField().on(builder))
.addLine(" %s = %s.%s();", finalField, fieldBuilder, buildMethod)
.addLine("}");
}
@Override
public void addAssignToBuilder(SourceBuilder code, Variable builder) {
if (type.partialToBuilder() == PartialToBuilderMethod.TO_BUILDER_AND_MERGE) {
code.add("%s = %s.toBuilder();",
property.getField().on(builder), property.getField());
} else {
code.add("%s = %s.mergeFrom(%s);",
property.getField().on(builder), type.newBuilder(EXPLICIT_TYPES), property.getField());
}
}
@Override
public void addMergeFromValue(SourceBuilder code, String value) {
code.addLine("if (%s == null) {", property.getField())
.addLine(" %s = %s.%s();", property.getField(), value, property.getGetterName())
.addLine("} else {")
.addLine(" %s().mergeFrom(%s.%s());",
getBuilderMethod(property), value, property.getGetterName())
.addLine("}");
}
@Override
public void addMergeFromBuilder(SourceBuilder code, String builder) {
Excerpt base = Declarations.upcastToGeneratedBuilder(code, datatype, builder);
Variable fieldValue = new Variable(property.getName() + "Value");
code.addLine("if (%s == null) {", property.getField().on(base))
.addLine(" // Nothing to merge")
.addLine("} else if (%s instanceof %s) {",
property.getField().on(base), maybeAsTypeElement(property.getType()).get())
.addLine(" %1$s %2$s %3$s = (%2$s) %4$s;",
type.suppressUnchecked(), property.getType(), fieldValue, property.getField().on(base))
.addLine(" if (%s == null) {", property.getField())
.addLine(" %s = %s;", property.getField(), fieldValue)
.addLine(" } else {")
.addLine(" %s().mergeFrom(%s);", getBuilderMethod(property), fieldValue)
.addLine(" }")
.addLine("} else {")
.add(" %s().mergeFrom(%s.%s()",
getBuilderMethod(property), base, getBuilderMethod(property));
if (type.mergeBuilder() == MergeBuilderMethod.BUILD_PARTIAL_AND_MERGE) {
code.add(".buildPartial()");
}
code.add(");\n")
.addLine("}");
}
@Override
public Set getMergeActions() {
return ImmutableSet.of();
}
@Override
public void addSetFromResult(SourceBuilder code, Excerpt builder, Excerpt variable) {
code.addLine("%s.%s(%s);", builder, setter(property), variable);
}
@Override
public void addClearField(SourceBuilder code) {
Variable fieldBuilder = new Variable(property.getName() + "Builder");
code.addLine(" if (%1$s == null || %1$s instanceof %2$s) {",
property.getField(), maybeAsTypeElement(property.getType()).get())
.addLine(" %s = null;", property.getField())
.addLine(" } else {")
.addLine(" %1$s %2$s %3$s = (%2$s) %4$s;",
type.suppressUnchecked(), type.builderType(), fieldBuilder, property.getField())
.addLine(" %s.clear();", fieldBuilder)
.addLine(" }");
}
}