Skip to content
This repository was archived by the owner on Oct 16, 2024. It is now read-only.

Commit 8843668

Browse files
authored
#320 Custom functional interfaces
Allow users to change the functional interface used in map/mutate methods with an override.
2 parents dcdb2a5 + 9a5165a commit 8843668

40 files changed

+1690
-225
lines changed

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ _Automatic generation of the Builder pattern for Java 1.6+_
2626
- [Collections and Maps](#collections-and-maps)
2727
- [Nested buildable types](#nested-buildable-types)
2828
- [Custom toString method](#custom-tostring-method)
29+
- [Custom functional interfaces](#custom-functional-interfaces)
2930
- [Builder construction](#builder-construction)
3031
- [Partials](#partials)
3132
- [Jackson](#jackson)
@@ -460,6 +461,22 @@ It is rarely a good idea to redefine equality on a value type, as it makes testi
460461
For instance, `assertEquals` in JUnit relies on equality; it will not know to check individual fields, and as a result, tests may be failing to catch bugs that, on the face of it, they looks like they should be.
461462
If you are only testing a subset of your fields for equality, consider separating your class in two, as you may have accidentally combined the key and the value of a map into a single object, and you may find your code becomes healthier after the separation.
462463

464+
### Custom functional interfaces
465+
466+
FreeBuilder's generated map and mutate methods take [UnaryOperator] or [Consumer] functional interfaces. If you need to use a different functional interface, you can override the generated methods in your Builder and change the parameter type. FreeBuilder will spot the incompatible override and change the code it generates to match:
467+
468+
```java
469+
public interface MyType {
470+
String property();
471+
472+
class Builder extends MyType_Builder {
473+
@Override public Builder mapProperty(
474+
com.google.common.base.Function<Integer, Integer> mapper) {
475+
return super.mapProperty(mapper);
476+
}
477+
}
478+
}
479+
463480
### Builder construction
464481

465482
<em>Effective Java</em> recommends passing required parameters in to the Builder

src/main/java/org/inferred/freebuilder/processor/BuildableProperty.java

+19-6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import static org.inferred.freebuilder.processor.BuilderMethods.mutator;
2525
import static org.inferred.freebuilder.processor.BuilderMethods.setter;
2626
import static org.inferred.freebuilder.processor.util.Block.methodBody;
27+
import static org.inferred.freebuilder.processor.util.FunctionalType.consumer;
28+
import static org.inferred.freebuilder.processor.util.FunctionalType.functionalTypeAcceptedByMethod;
2729
import static org.inferred.freebuilder.processor.util.ModelUtils.asElement;
2830
import static org.inferred.freebuilder.processor.util.ModelUtils.findAnnotationMirror;
2931
import static org.inferred.freebuilder.processor.util.ModelUtils.maybeDeclared;
@@ -37,6 +39,7 @@
3739
import org.inferred.freebuilder.processor.util.Block;
3840
import org.inferred.freebuilder.processor.util.Excerpt;
3941
import org.inferred.freebuilder.processor.util.Excerpts;
42+
import org.inferred.freebuilder.processor.util.FunctionalType;
4043
import org.inferred.freebuilder.processor.util.ModelUtils;
4144
import org.inferred.freebuilder.processor.util.ParameterizedType;
4245
import org.inferred.freebuilder.processor.util.PreconditionExcerpts;
@@ -161,18 +164,28 @@ public Optional<BuildableProperty> create(Config config) {
161164
partialToBuilderMethod = PartialToBuilderMethod.MERGE_DIRECTLY;
162165
}
163166

167+
// Find any mutate method override
168+
FunctionalType mutatorType = functionalTypeAcceptedByMethod(
169+
config.getBuilder(),
170+
mutator(config.getProperty()),
171+
consumer(builder.get().asType()),
172+
config.getElements(),
173+
config.getTypes());
174+
164175
return Optional.of(new BuildableProperty(
165176
config.getMetadata(),
166177
config.getProperty(),
167178
ParameterizedType.from(builder.get()),
168179
builderFactory.get(),
180+
mutatorType,
169181
mergeFromBuilderMethod,
170182
partialToBuilderMethod));
171183
}
172184
}
173185

174186
private final ParameterizedType builderType;
175187
private final BuilderFactory builderFactory;
188+
private final FunctionalType mutatorType;
176189
private final MergeBuilderMethod mergeFromBuilderMethod;
177190
private final PartialToBuilderMethod partialToBuilderMethod;
178191
private final Excerpt suppressUnchecked;
@@ -182,11 +195,13 @@ private BuildableProperty(
182195
Property property,
183196
ParameterizedType builderType,
184197
BuilderFactory builderFactory,
198+
FunctionalType mutatorType,
185199
MergeBuilderMethod mergeFromBuilderMethod,
186200
PartialToBuilderMethod partialToBuilderMethod) {
187201
super(metadata, property);
188202
this.builderType = builderType;
189203
this.builderFactory = builderFactory;
204+
this.mutatorType = mutatorType;
190205
this.mergeFromBuilderMethod = mergeFromBuilderMethod;
191206
this.partialToBuilderMethod = partialToBuilderMethod;
192207
if (ModelUtils.needsSafeVarargs(property.getType())) {
@@ -259,8 +274,7 @@ private void addSetterTakingBuilder(SourceBuilder code, Metadata metadata) {
259274
}
260275

261276
private void addMutate(SourceBuilder code, Metadata metadata) {
262-
ParameterizedType consumer = code.feature(FUNCTION_PACKAGE).consumer().orNull();
263-
if (consumer == null) {
277+
if (!code.feature(FUNCTION_PACKAGE).consumer().isPresent()) {
264278
return;
265279
}
266280
code.addLine("")
@@ -275,13 +289,12 @@ private void addMutate(SourceBuilder code, Metadata metadata) {
275289
.addLine(" * @return this {@code %s} object", metadata.getBuilder().getSimpleName())
276290
.addLine(" * @throws NullPointerException if {@code mutator} is null")
277291
.addLine(" */")
278-
.addLine("public %s %s(%s<%s> mutator) {",
292+
.addLine("public %s %s(%s mutator) {",
279293
metadata.getBuilder(),
280294
mutator(property),
281-
consumer.getQualifiedName(),
282-
builderType)
295+
mutatorType.getFunctionalInterface())
283296
.add(methodBody(code, "mutator")
284-
.addLine(" mutator.accept(%s());", getBuilderMethod(property))
297+
.addLine(" mutator.%s(%s());", mutatorType.getMethodName(), getBuilderMethod(property))
285298
.addLine(" return (%s) this;", metadata.getBuilder()))
286299
.addLine("}");
287300
}

src/main/java/org/inferred/freebuilder/processor/DefaultProperty.java

+26-11
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import static org.inferred.freebuilder.processor.BuilderMethods.setter;
2222
import static org.inferred.freebuilder.processor.CodeGenerator.UNSET_PROPERTIES;
2323
import static org.inferred.freebuilder.processor.util.Block.methodBody;
24+
import static org.inferred.freebuilder.processor.util.FunctionalType.functionalTypeAcceptedByMethod;
25+
import static org.inferred.freebuilder.processor.util.FunctionalType.unaryOperator;
2426
import static org.inferred.freebuilder.processor.util.ObjectsExcerpts.Nullability.NOT_NULLABLE;
2527
import static org.inferred.freebuilder.processor.util.PreconditionExcerpts.checkNotNullInline;
2628
import static org.inferred.freebuilder.processor.util.PreconditionExcerpts.checkNotNullPreamble;
@@ -33,13 +35,12 @@
3335
import org.inferred.freebuilder.processor.util.Excerpt;
3436
import org.inferred.freebuilder.processor.util.Excerpts;
3537
import org.inferred.freebuilder.processor.util.FieldAccess;
38+
import org.inferred.freebuilder.processor.util.FunctionalType;
3639
import org.inferred.freebuilder.processor.util.ObjectsExcerpts;
37-
import org.inferred.freebuilder.processor.util.ParameterizedType;
3840
import org.inferred.freebuilder.processor.util.PreconditionExcerpts;
3941
import org.inferred.freebuilder.processor.util.SourceBuilder;
4042

4143
import javax.lang.model.type.TypeKind;
42-
import javax.lang.model.type.TypeMirror;
4344

4445
/** Default {@link PropertyCodeGenerator}, providing reference semantics for any type. */
4546
class DefaultProperty extends PropertyCodeGenerator {
@@ -51,16 +52,29 @@ public Optional<DefaultProperty> create(Config config) {
5152
Property property = config.getProperty();
5253
boolean hasDefault = config.getMethodsInvokedInBuilderConstructor()
5354
.contains(setter(property));
54-
return Optional.of(new DefaultProperty(config.getMetadata(), property, hasDefault));
55+
FunctionalType mapperType = functionalTypeAcceptedByMethod(
56+
config.getBuilder(),
57+
mapper(property),
58+
unaryOperator(firstNonNull(property.getBoxedType(), property.getType())),
59+
config.getElements(),
60+
config.getTypes());
61+
return Optional.of(new DefaultProperty(
62+
config.getMetadata(), property, hasDefault, mapperType));
5563
}
5664
}
5765

5866
private final boolean hasDefault;
67+
private final FunctionalType mapperType;
5968
private final TypeKind kind;
6069

61-
DefaultProperty(Metadata metadata, Property property, boolean hasDefault) {
70+
DefaultProperty(
71+
Metadata metadata,
72+
Property property,
73+
boolean hasDefault,
74+
FunctionalType mapperType) {
6275
super(metadata, property);
6376
this.hasDefault = hasDefault;
77+
this.mapperType = mapperType;
6478
this.kind = property.getType().getKind();
6579
}
6680

@@ -116,8 +130,7 @@ private void addSetter(SourceBuilder code, final Metadata metadata) {
116130
}
117131

118132
private void addMapper(SourceBuilder code, final Metadata metadata) {
119-
ParameterizedType unaryOperator = code.feature(FUNCTION_PACKAGE).unaryOperator().orNull();
120-
if (unaryOperator == null) {
133+
if (!code.feature(FUNCTION_PACKAGE).unaryOperator().isPresent()) {
121134
return;
122135
}
123136
code.addLine("")
@@ -127,21 +140,23 @@ private void addMapper(SourceBuilder code, final Metadata metadata) {
127140
.addLine(" * by applying {@code mapper} to it and using the result.")
128141
.addLine(" *")
129142
.addLine(" * @return this {@code %s} object", metadata.getBuilder().getSimpleName())
130-
.addLine(" * @throws NullPointerException if {@code mapper} is null"
131-
+ " or returns null");
143+
.addLine(" * @throws NullPointerException if {@code mapper} is null");
144+
if (mapperType.canReturnNull()) {
145+
code.addLine(" * or returns null");
146+
}
132147
if (!hasDefault) {
133148
code.addLine(" * @throws IllegalStateException if the field has not been set");
134149
}
135-
TypeMirror typeParam = firstNonNull(property.getBoxedType(), property.getType());
136150
code.addLine(" */")
137151
.add("public %s %s(%s mapper) {",
138152
metadata.getBuilder(),
139153
mapper(property),
140-
unaryOperator.withParameters(typeParam));
154+
mapperType.getFunctionalInterface());
141155
if (!hasDefault) {
142156
code.add(PreconditionExcerpts.checkNotNull("mapper"));
143157
}
144-
code.addLine(" return %s(mapper.apply(%s()));", setter(property), getter(property))
158+
code.addLine(" return %s(mapper.%s(%s()));",
159+
setter(property), mapperType.getMethodName(), getter(property))
145160
.addLine("}");
146161
}
147162

src/main/java/org/inferred/freebuilder/processor/ListMultimapProperty.java

+41-15
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import static org.inferred.freebuilder.processor.Util.erasesToAnyOf;
2626
import static org.inferred.freebuilder.processor.Util.upperBound;
2727
import static org.inferred.freebuilder.processor.util.Block.methodBody;
28+
import static org.inferred.freebuilder.processor.util.FunctionalType.consumer;
29+
import static org.inferred.freebuilder.processor.util.FunctionalType.functionalTypeAcceptedByMethod;
2830
import static org.inferred.freebuilder.processor.util.ModelUtils.maybeDeclared;
2931
import static org.inferred.freebuilder.processor.util.ModelUtils.maybeUnbox;
3032
import static org.inferred.freebuilder.processor.util.ModelUtils.overrides;
@@ -43,14 +45,17 @@
4345
import org.inferred.freebuilder.processor.excerpt.CheckedListMultimap;
4446
import org.inferred.freebuilder.processor.util.Block;
4547
import org.inferred.freebuilder.processor.util.Excerpt;
46-
import org.inferred.freebuilder.processor.util.ParameterizedType;
48+
import org.inferred.freebuilder.processor.util.FunctionalType;
4749
import org.inferred.freebuilder.processor.util.SourceBuilder;
4850

4951
import java.util.Collection;
5052
import java.util.Map.Entry;
5153

54+
import javax.lang.model.element.TypeElement;
5255
import javax.lang.model.type.DeclaredType;
5356
import javax.lang.model.type.TypeMirror;
57+
import javax.lang.model.util.Elements;
58+
import javax.lang.model.util.Types;
5459

5560
/**
5661
* {@link PropertyCodeGenerator} providing fluent methods for {@link ListMultimap} properties.
@@ -61,7 +66,8 @@ static class Factory implements PropertyCodeGenerator.Factory {
6166

6267
@Override
6368
public Optional<ListMultimapProperty> create(Config config) {
64-
DeclaredType type = maybeDeclared(config.getProperty().getType()).orNull();
69+
Property property = config.getProperty();
70+
DeclaredType type = maybeDeclared(property.getType()).orNull();
6571
if (type == null) {
6672
return Optional.absent();
6773
}
@@ -79,14 +85,23 @@ public Optional<ListMultimapProperty> create(Config config) {
7985
Optional<TypeMirror> unboxedValueType = maybeUnbox(valueType, config.getTypes());
8086
boolean overridesPutMethod =
8187
hasPutMethodOverride(config, unboxedKeyType.or(keyType), unboxedValueType.or(valueType));
88+
89+
FunctionalType mutatorType = functionalTypeAcceptedByMethod(
90+
config.getBuilder(),
91+
mutator(property),
92+
consumer(listMultimap(keyType, valueType, config.getElements(), config.getTypes())),
93+
config.getElements(),
94+
config.getTypes());
95+
8296
return Optional.of(new ListMultimapProperty(
8397
config.getMetadata(),
84-
config.getProperty(),
98+
property,
8599
overridesPutMethod,
86100
keyType,
87101
unboxedKeyType,
88102
valueType,
89-
unboxedValueType));
103+
unboxedValueType,
104+
mutatorType));
90105
}
91106

92107
private static boolean hasPutMethodOverride(
@@ -98,13 +113,23 @@ private static boolean hasPutMethodOverride(
98113
keyType,
99114
valueType);
100115
}
116+
117+
private static TypeMirror listMultimap(
118+
TypeMirror keyType,
119+
TypeMirror valueType,
120+
Elements elements,
121+
Types types) {
122+
TypeElement listMultimapType = elements.getTypeElement(ListMultimap.class.getName());
123+
return types.getDeclaredType(listMultimapType, keyType, valueType);
124+
}
101125
}
102126

103127
private final boolean overridesPutMethod;
104128
private final TypeMirror keyType;
105129
private final Optional<TypeMirror> unboxedKeyType;
106130
private final TypeMirror valueType;
107131
private final Optional<TypeMirror> unboxedValueType;
132+
private final FunctionalType mutatorType;
108133

109134
ListMultimapProperty(
110135
Metadata metadata,
@@ -113,13 +138,15 @@ private static boolean hasPutMethodOverride(
113138
TypeMirror keyType,
114139
Optional<TypeMirror> unboxedKeyType,
115140
TypeMirror valueType,
116-
Optional<TypeMirror> unboxedValueType) {
141+
Optional<TypeMirror> unboxedValueType,
142+
FunctionalType mutatorType) {
117143
super(metadata, property);
118144
this.overridesPutMethod = overridesPutMethod;
119145
this.keyType = keyType;
120146
this.unboxedKeyType = unboxedKeyType;
121147
this.valueType = valueType;
122148
this.unboxedValueType = unboxedValueType;
149+
this.mutatorType = mutatorType;
123150
}
124151

125152
@Override
@@ -301,8 +328,7 @@ private void addRemoveAll(SourceBuilder code, Metadata metadata) {
301328
}
302329

303330
private void addMutate(SourceBuilder code, Metadata metadata) {
304-
ParameterizedType consumer = code.feature(FUNCTION_PACKAGE).consumer().orNull();
305-
if (consumer == null) {
331+
if (!code.feature(FUNCTION_PACKAGE).consumer().isPresent()) {
306332
return;
307333
}
308334
code.addLine("")
@@ -316,21 +342,21 @@ private void addMutate(SourceBuilder code, Metadata metadata) {
316342
.addLine(" * @return this {@code Builder} object")
317343
.addLine(" * @throws NullPointerException if {@code mutator} is null")
318344
.addLine(" */")
319-
.addLine("public %s %s(%s<%s<%s, %s>> mutator) {",
345+
.addLine("public %s %s(%s mutator) {",
320346
metadata.getBuilder(),
321347
mutator(property),
322-
consumer.getQualifiedName(),
323-
ListMultimap.class,
324-
keyType,
325-
valueType);
348+
mutatorType.getFunctionalInterface());
326349
Block body = methodBody(code, "mutator");
327350
if (overridesPutMethod) {
328-
body.addLine(" mutator.accept(new %s<>(%s, this::%s));",
329-
CheckedListMultimap.TYPE, property.getField(), putMethod(property));
351+
body.addLine(" mutator.%s(new %s<>(%s, this::%s));",
352+
mutatorType.getMethodName(),
353+
CheckedListMultimap.TYPE,
354+
property.getField(),
355+
putMethod(property));
330356
} else {
331357
body.addLine(" // If %s is overridden, this method will be updated to delegate to it",
332358
putMethod(property))
333-
.addLine(" mutator.accept(%s);", property.getField());
359+
.addLine(" mutator.%s(%s);", mutatorType.getMethodName(), property.getField());
334360
}
335361
body.addLine(" return (%s) this;", metadata.getBuilder());
336362
code.add(body)

0 commit comments

Comments
 (0)