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

Commit 4756d14

Browse files
authored
#277 Share built objects
Buildable properties now reuse built instances where possible, reducing the memory overhead of the modify-rebuild pattern.
2 parents 900257e + 163b8f1 commit 4756d14

File tree

7 files changed

+354
-55
lines changed

7 files changed

+354
-55
lines changed

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

+115-18
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import static com.google.common.collect.Iterables.tryFind;
2020
import static javax.lang.model.element.Modifier.PUBLIC;
2121
import static javax.lang.model.util.ElementFilter.typesIn;
22-
import static org.inferred.freebuilder.processor.BuilderFactory.TypeInference.INFERRED_TYPES;
22+
import static org.inferred.freebuilder.processor.BuilderFactory.TypeInference.EXPLICIT_TYPES;
2323
import static org.inferred.freebuilder.processor.BuilderMethods.getBuilderMethod;
2424
import static org.inferred.freebuilder.processor.BuilderMethods.mutator;
2525
import static org.inferred.freebuilder.processor.BuilderMethods.setter;
@@ -36,9 +36,12 @@
3636
import org.inferred.freebuilder.processor.Metadata.Property;
3737
import org.inferred.freebuilder.processor.util.Block;
3838
import org.inferred.freebuilder.processor.util.Excerpt;
39+
import org.inferred.freebuilder.processor.util.Excerpts;
40+
import org.inferred.freebuilder.processor.util.ModelUtils;
3941
import org.inferred.freebuilder.processor.util.ParameterizedType;
4042
import org.inferred.freebuilder.processor.util.PreconditionExcerpts;
4143
import org.inferred.freebuilder.processor.util.SourceBuilder;
44+
import org.inferred.freebuilder.processor.util.Variable;
4245

4346
import java.util.List;
4447

@@ -172,6 +175,7 @@ public Optional<BuildableProperty> create(Config config) {
172175
private final BuilderFactory builderFactory;
173176
private final MergeBuilderMethod mergeFromBuilderMethod;
174177
private final PartialToBuilderMethod partialToBuilderMethod;
178+
private final Excerpt suppressUnchecked;
175179

176180
private BuildableProperty(
177181
Metadata metadata,
@@ -185,12 +189,16 @@ private BuildableProperty(
185189
this.builderFactory = builderFactory;
186190
this.mergeFromBuilderMethod = mergeFromBuilderMethod;
187191
this.partialToBuilderMethod = partialToBuilderMethod;
192+
if (ModelUtils.needsSafeVarargs(property.getType())) {
193+
suppressUnchecked = Excerpts.add("@SuppressWarnings(\"unchecked\")");
194+
} else {
195+
suppressUnchecked = Excerpts.empty();
196+
}
188197
}
189198

190199
@Override
191200
public void addBuilderFieldDeclaration(SourceBuilder code) {
192-
code.addLine("private final %s %s = %s;",
193-
builderType, property.getField(), builderFactory.newBuilder(builderType, INFERRED_TYPES));
201+
code.addLine("private Object %s = null;", property.getField());
194202
}
195203

196204
@Override
@@ -202,6 +210,7 @@ public void addBuilderFieldAccessors(SourceBuilder code) {
202210
}
203211

204212
private void addSetter(SourceBuilder code, Metadata metadata) {
213+
Variable builder = new Variable("builder");
205214
code.addLine("")
206215
.addLine("/**")
207216
.addLine(" * Sets the value to be returned by %s.",
@@ -215,12 +224,20 @@ private void addSetter(SourceBuilder code, Metadata metadata) {
215224
metadata.getBuilder(),
216225
setter(property),
217226
property.getType(),
218-
property.getName())
219-
.add(methodBody(code, property.getName())
220-
.add(PreconditionExcerpts.checkNotNull(property.getName()))
221-
.addLine(" %s.clear();", property.getField())
222-
.addLine(" %s.mergeFrom(%s);", property.getField(), property.getName())
223-
.addLine(" return (%s) this;", metadata.getBuilder()))
227+
property.getName());
228+
Block body = methodBody(code, property.getName())
229+
.add(PreconditionExcerpts.checkNotNull(property.getName()))
230+
.addLine(" if (%1$s == null || %1$s instanceof %2$s) {",
231+
property.getField(), ModelUtils.maybeAsTypeElement(property.getType()).get())
232+
.addLine(" %s = %s;", property.getField(), property.getName())
233+
.addLine(" } else {")
234+
.addLine(" %1$s %2$s %3$s = (%2$s) %4$s;",
235+
suppressUnchecked, builderType, builder, property.getField())
236+
.addLine(" %s.clear();", builder)
237+
.addLine(" %s.mergeFrom(%s);", builder, property.getName())
238+
.addLine(" }")
239+
.addLine(" return (%s) this;", metadata.getBuilder());
240+
code.add(body)
224241
.addLine("}");
225242
}
226243

@@ -264,44 +281,115 @@ private void addMutate(SourceBuilder code, Metadata metadata) {
264281
consumer.getQualifiedName(),
265282
builderType)
266283
.add(methodBody(code, "mutator")
267-
.addLine(" mutator.accept(%s);", property.getField())
284+
.addLine(" mutator.accept(%s());", getBuilderMethod(property))
268285
.addLine(" return (%s) this;", metadata.getBuilder()))
269286
.addLine("}");
270287
}
271288

272289
private void addGetter(SourceBuilder code, Metadata metadata) {
290+
Variable builder = new Variable("builder");
291+
Variable value = new Variable("value");
273292
code.addLine("")
274293
.addLine("/**")
275294
.addLine(" * Returns a builder for the value that will be returned by %s.",
276295
metadata.getType().javadocNoArgMethodLink(property.getGetterName()))
277296
.addLine(" */")
278-
.addLine("public %s %s() {", builderType, getBuilderMethod(property))
279-
.addLine(" return %s;", property.getField())
297+
.addLine("public %s %s() {", builderType, getBuilderMethod(property));
298+
Block body = methodBody(code)
299+
.addLine(" if (%s == null) {", property.getField())
300+
.addLine(" %s = %s;",
301+
property.getField(), builderFactory.newBuilder(builderType, EXPLICIT_TYPES))
302+
.addLine(" } else if (%s instanceof %s) {",
303+
property.getField(), ModelUtils.maybeAsTypeElement(property.getType()).get())
304+
.addLine(" %1$s %2$s %3$s = (%2$s) %4$s;",
305+
suppressUnchecked, property.getType(), value, property.getField());
306+
if (partialToBuilderMethod == PartialToBuilderMethod.TO_BUILDER_AND_MERGE) {
307+
body.addLine(" %s = %s.toBuilder();", property.getField(), value);
308+
} else {
309+
body.addLine(" %s = %s",
310+
property.getField(), builderFactory.newBuilder(builderType, EXPLICIT_TYPES))
311+
.addLine(" .mergeFrom(%s);", value);
312+
}
313+
body.addLine(" }")
314+
.addLine(" %1$s %2$s %3$s = (%2$s) %4$s;",
315+
suppressUnchecked, builderType, builder, property.getField())
316+
.addLine(" return %s;", builder);
317+
code.add(body)
280318
.addLine("}");
281319
}
282320

283321
@Override
284322
public void addFinalFieldAssignment(SourceBuilder code, Excerpt finalField, String builder) {
285-
code.addLine("%s = %s.build();", finalField, property.getField().on(builder));
323+
addFieldAssignment(code, finalField, builder, "build");
286324
}
287325

288326
@Override
289327
public void addPartialFieldAssignment(SourceBuilder code, Excerpt finalField, String builder) {
290-
code.addLine("%s = %s.buildPartial();", finalField, property.getField().on(builder));
328+
addFieldAssignment(code, finalField, builder, "buildPartial");
329+
}
330+
331+
private void addFieldAssignment(
332+
SourceBuilder code,
333+
Excerpt finalField,
334+
String builder,
335+
String buildMethod) {
336+
Variable fieldBuilder = new Variable(property.getName() + "Builder");
337+
Variable fieldValue = new Variable(property.getName() + "Value");
338+
code.addLine("if (%s == null) {", property.getField().on(builder))
339+
.addLine(" %s = %s.%s();",
340+
finalField, builderFactory.newBuilder(builderType, EXPLICIT_TYPES), buildMethod)
341+
.addLine("} else if (%s instanceof %s) {",
342+
property.getField().on(builder),
343+
ModelUtils.maybeAsTypeElement(property.getType()).get());
344+
if (suppressUnchecked != Excerpts.empty()) {
345+
code.addLine(" %1$s %2$s %3$s = (%2$s) %4$s;",
346+
suppressUnchecked, property.getType(), fieldValue, property.getField().on(builder))
347+
.addLine(" %s = %s;", finalField, fieldValue);
348+
} else {
349+
code.addLine(" %s = (%s) %s;",
350+
finalField, property.getType(), property.getField().on(builder));
351+
}
352+
code.addLine("} else {")
353+
.addLine(" %1$s %2$s %3$s = (%2$s) %4$s;",
354+
suppressUnchecked, builderType, fieldBuilder, property.getField().on(builder))
355+
.addLine(" %s = %s.%s();", finalField, fieldBuilder, buildMethod)
356+
.addLine("}");
291357
}
292358

293359
@Override
294360
public void addMergeFromValue(Block code, String value) {
295-
code.addLine("%s.mergeFrom(%s.%s());", property.getField(), value, property.getGetterName());
361+
code.addLine("if (%s == null) {", property.getField())
362+
.addLine(" %s = %s.%s();", property.getField(), value, property.getGetterName())
363+
.addLine("} else {")
364+
.addLine(" %s().mergeFrom(%s.%s());",
365+
getBuilderMethod(property), value, property.getGetterName())
366+
.addLine("}");
296367
}
297368

298369
@Override
299370
public void addMergeFromBuilder(Block code, String builder) {
300-
code.add("%s.mergeFrom(%s.%s()", property.getField(), builder, getBuilderMethod(property));
371+
Excerpt base = Declarations.upcastToGeneratedBuilder(code, metadata, builder);
372+
Variable fieldValue = new Variable(property.getName() + "Value");
373+
code.addLine("if (%s == null) {", property.getField().on(base))
374+
.addLine(" // Nothing to merge")
375+
.addLine("} else if (%s instanceof %s) {",
376+
property.getField().on(base),
377+
ModelUtils.maybeAsTypeElement(property.getType()).get())
378+
.addLine(" %1$s %2$s %3$s = (%2$s) %4$s;",
379+
suppressUnchecked, property.getType(), fieldValue, property.getField().on(base))
380+
.addLine(" if (%s == null) {", property.getField())
381+
.addLine(" %s = %s;", property.getField(), fieldValue)
382+
.addLine(" } else {")
383+
.addLine(" %s().mergeFrom(%s);", getBuilderMethod(property), fieldValue)
384+
.addLine(" }")
385+
.addLine("} else {")
386+
.add(" %s().mergeFrom(%s.%s()",
387+
getBuilderMethod(property), base, getBuilderMethod(property));
301388
if (mergeFromBuilderMethod == MergeBuilderMethod.BUILD_PARTIAL_AND_MERGE) {
302389
code.add(".buildPartial()");
303390
}
304-
code.add(");\n");
391+
code.add(");\n")
392+
.addLine("}");
305393
}
306394

307395
@Override
@@ -322,7 +410,16 @@ public void addSetFromResult(SourceBuilder code, Excerpt builder, Excerpt variab
322410

323411
@Override
324412
public void addClearField(Block code) {
325-
code.addLine("%s.clear();", property.getField());
413+
Variable fieldBuilder = new Variable(property.getName() + "Builder");
414+
code.addLine(" if (%1$s == null || %1$s instanceof %2$s) {",
415+
property.getField(),
416+
ModelUtils.maybeAsTypeElement(property.getType()).get())
417+
.addLine(" %s = null;", property.getField())
418+
.addLine(" } else {")
419+
.addLine(" %1$s %2$s %3$s = (%2$s) %4$s;",
420+
suppressUnchecked, builderType, fieldBuilder, property.getField())
421+
.addLine(" %s.clear();", fieldBuilder)
422+
.addLine(" }");
326423
}
327424

328425
private static final class IsCallableMethod implements Predicate<ExecutableElement> {

src/main/java/org/inferred/freebuilder/processor/util/Block.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class Block extends Excerpt implements SourceBuilder {
1616
public static Block methodBody(SourceBuilder parent, String... paramNames) {
1717
Scope methodScope = new Scope.MethodScope(parent.scope());
1818
for (String paramName : paramNames) {
19-
methodScope.add(new Variable(paramName));
19+
methodScope.add(new VariableName(paramName));
2020
}
2121
return new Block(parent, methodScope);
2222
}
@@ -58,7 +58,7 @@ public Excerpt declare(Excerpt typeAndPreamble, String preferredName, Excerpt va
5858
} else {
5959
name = pickName(preferredName);
6060
variableNames.put(preferredName, name);
61-
body.scope().add(new Variable(name));
61+
body.scope().add(new VariableName(name));
6262
Excerpt declaration = Excerpts.add("%s %s = %s;%n", typeAndPreamble, name, value);
6363
declarations.put(name, declaration);
6464
declarationsBlock.add(declaration);
@@ -89,7 +89,7 @@ private String pickName(String preferredName) {
8989
}
9090

9191
private boolean nameCollides(String name) {
92-
return body.scope().contains(new Variable(name))
92+
return body.scope().contains(new VariableName(name))
9393
|| body.scope().contains(new FieldAccess(name));
9494
}
9595

src/main/java/org/inferred/freebuilder/processor/util/FieldAccess.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public Level level() {
5252

5353
@Override
5454
public void addTo(SourceBuilder source) {
55-
if (source.scope().contains(new Variable(fieldName))) {
55+
if (source.scope().contains(new VariableName(fieldName))) {
5656
source.add("this.");
5757
} else {
5858
// Prevent a new variable being declared and obscuring this field access

src/main/java/org/inferred/freebuilder/processor/util/ValueType.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public void add(String name, Object value) {
9696
protected abstract void addFields(FieldReceiver fields);
9797

9898
@Override
99-
public final boolean equals(Object obj) {
99+
public boolean equals(Object obj) {
100100
if ((obj == null) || (obj.getClass() != this.getClass())) {
101101
return false;
102102
}
@@ -108,7 +108,7 @@ public final boolean equals(Object obj) {
108108
}
109109

110110
@Override
111-
public final int hashCode() {
111+
public int hashCode() {
112112
ReceiverIntoHashCode receiver = new ReceiverIntoHashCode();
113113
addFields(receiver);
114114
return receiver.get();

src/main/java/org/inferred/freebuilder/processor/util/Variable.java

+46-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017 Google Inc. All rights reserved.
2+
* Copyright 2018 Google Inc. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,21 +17,61 @@
1717

1818
import org.inferred.freebuilder.processor.util.Scope.Level;
1919

20-
public class Variable extends ValueType implements Scope.Element<Variable> {
20+
public class Variable extends Excerpt implements Scope.Element<VariableName> {
2121

22-
private final String name;
22+
private final String preferredName;
2323

24-
public Variable(String name) {
25-
this.name = name;
24+
public Variable(String preferredName) {
25+
this.preferredName = preferredName;
2626
}
2727

2828
@Override
2929
public Level level() {
3030
return Level.METHOD;
3131
}
3232

33+
@Override
34+
public boolean equals(Object obj) {
35+
return this == obj;
36+
}
37+
38+
@Override
39+
public int hashCode() {
40+
return super.hashCode();
41+
}
42+
43+
@Override
44+
public void addTo(SourceBuilder code) {
45+
VariableName name = code.scope().get(this);
46+
if (name == null) {
47+
name = new VariableName(pickName(code));
48+
code.scope().putIfAbsent(name, name);
49+
code.scope().putIfAbsent(this, name);
50+
}
51+
code.add("%s", name.name());
52+
}
53+
3354
@Override
3455
protected void addFields(FieldReceiver fields) {
35-
fields.add("name", name);
56+
fields.add("preferredName", preferredName);
57+
}
58+
59+
private String pickName(SourceBuilder code) {
60+
if (!nameCollides(code, preferredName)) {
61+
return preferredName;
62+
}
63+
if (!nameCollides(code, "_" + preferredName)) {
64+
return "_" + preferredName;
65+
}
66+
int suffix = 2;
67+
while (nameCollides(code, "_" + preferredName + suffix)) {
68+
suffix++;
69+
}
70+
return "_" + preferredName + suffix;
71+
}
72+
73+
private static boolean nameCollides(SourceBuilder code, String name) {
74+
return code.scope().contains(new VariableName(name))
75+
|| code.scope().contains(new FieldAccess(name));
3676
}
3777
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2017 Google Inc. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.inferred.freebuilder.processor.util;
17+
18+
import org.inferred.freebuilder.processor.util.Scope.Level;
19+
20+
class VariableName extends ValueType implements Scope.Element<VariableName> {
21+
22+
private final String name;
23+
24+
VariableName(String name) {
25+
this.name = name;
26+
}
27+
28+
String name() {
29+
return name;
30+
}
31+
32+
@Override
33+
public Level level() {
34+
return Level.METHOD;
35+
}
36+
37+
@Override
38+
protected void addFields(FieldReceiver fields) {
39+
fields.add("name", name);
40+
}
41+
}

0 commit comments

Comments
 (0)