inferred / freebuilder Goto Github PK
View Code? Open in Web Editor NEWAutomatic generation of the Builder pattern for Java
Home Page: https://freebuilder.inferred.org
License: Apache License 2.0
Automatic generation of the Builder pattern for Java
Home Page: https://freebuilder.inferred.org
License: Apache License 2.0
Some dependencies are not shaded and may cause conflicts.
For
@FreeBuilder
public interface Foo {
Bar getBar();
static class Builder extends Foo_Builder() {}
@FreeBuilder
public interface Bar {
String getBlah();
static class Builder extends Foo_Bar_Builder() {}
}
}
this works fine:
java new Foo.Builder().getBarBuilder()
But for:
@FreeBuilder
public interface Foo {
Bar getBar();
static class Builder extends Foo_Builder() { }
}
@FreeBuilder
public interface Bar {
String getBlah();
static class Builder extends Bar_Builder() {}
}
java new Foo.Builder().getBarBuilder()
doesn't exist.
It should be possible to issue errors at compile time for code that fails to set required properties on a builder, albeit on a "best effort" (javac, no complex logic) basis.
I'm having trouble getting FreeBuilder to compile on my Mac:
% java -version
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)
% mvn -version
Apache Maven 3.3.3 (7994120775791599e205a5524ec3e0dfe41d4a06; 2015-04-22T04:57:37-07:00)
Maven home: /usr/local/Cellar/maven/3.3.3/libexec
Java version: 1.8.0_60, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "mac os x", version: "10.10.4", arch: "x86_64", family: "mac"
% mvn test-compile
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building @FreeBuilder 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ freebuilder ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/paul/projects/FreeBuilder/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.2:compile (default-compile) @ freebuilder ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ freebuilder ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/paul/projects/FreeBuilder/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.2:testCompile (default-testCompile) @ freebuilder ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 42 source files to /Users/paul/projects/FreeBuilder/target/test-classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /Users/paul/projects/FreeBuilder/src/test/java/org/inferred/freebuilder/processor/util/PackageElementImpl.java:[34,8] org.inferred.freebuilder.processor.util.PackageElementImpl is not abstract and does not override abstract met
hod <A>getAnnotationsByType(java.lang.Class<A>) in javax.lang.model.AnnotatedConstruct
[ERROR] /Users/paul/projects/FreeBuilder/src/test/java/org/inferred/freebuilder/processor/util/PackageElementImpl.java:[114,3] org.inferred.freebuilder.processor.util.PackageElementImpl.PackageTypeImpl is not abstract and does not over
ride abstract method <A>getAnnotationsByType(java.lang.Class<A>) in javax.lang.model.AnnotatedConstruct
[ERROR] /Users/paul/projects/FreeBuilder/src/test/java/org/inferred/freebuilder/processor/GenericTypeElementImpl.java:[90,3] org.inferred.freebuilder.processor.GenericTypeElementImpl.GenericTypeMirrorImpl is not abstract and does not o
verride abstract method <A>getAnnotationsByType(java.lang.Class<A>) in javax.lang.model.AnnotatedConstruct
[ERROR] /Users/paul/projects/FreeBuilder/src/test/java/org/inferred/freebuilder/processor/util/ClassTypeImpl.java:[42,8] org.inferred.freebuilder.processor.util.ClassTypeImpl is not abstract and does not override abstract method <A>get
AnnotationsByType(java.lang.Class<A>) in javax.lang.model.AnnotatedConstruct
[ERROR] /Users/paul/projects/FreeBuilder/src/test/java/org/inferred/freebuilder/processor/util/ClassTypeImpl.java:[118,10] org.inferred.freebuilder.processor.util.ClassTypeImpl.ClassElementImpl is not abstract and does not override abs
tract method <A>getAnnotationsByType(java.lang.Class<A>) in javax.lang.model.AnnotatedConstruct
[ERROR] /Users/paul/projects/FreeBuilder/src/test/java/org/inferred/freebuilder/processor/util/NoTypes.java:[26,3] <anonymous org.inferred.freebuilder.processor.util.NoTypes$1> is not abstract and does not override abstract method <A>g
etAnnotationsByType(java.lang.Class<A>) in javax.lang.model.AnnotatedConstruct
[INFO] 6 errors
It seems like java 8 changed Element
to extend from the interface AnnotatedConstruct
, which has new methods that need to be implemented:
https://docs.oracle.com/javase/7/docs/api/javax/lang/model/element/Element.html
https://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/Element.html
One issue I came across when looking into migrating to @Freebuilder is that there is no (easy) way to test whether a field has been set on a builder. The builder class has a getFoo() method, but it returns an IllegalStateException if the field "foo" is not set. I could catch this exception and then set a value in the exception handler, but that's ugly. It would be much better to generate a method "hasFoo()" (or some better name) for a field called "foo" which returned true if the field had been set.
The normal way of setting a default value for a field, in the constructor, does not work in the case in which you only want to set the default value if it has not been set by the time build is called. The case I had was in testing, where I extended the builder in a test-only subclass which used a factory to create objects with sequentially-increasing IDs. If the factory creates an object, it increments the ID, so we don't want to set the field until and unless we get to build() and the field has not yet been set.
If you lazily set the field, won't you end up accidentally reusing IDs if the user reuses the Builder?
There are a couple of reasons why it's difficult to add hasX to @Freebuilder in particular. First, there's no way to tell if a field has a default value assigned in the constructor, so you get hasX methods that are both pointless and potentially confusing. Secondly, customisation is done via overrides, so any extra method makes it harder to do.
In your specific case, I suggest adding a boolean hasX field and overriding setX and getX to respect it; this also lets you call super.setX in your build method without changing the state of hasX.
Essentially, what I am looking for is the ability to read the Set of unset fields from subclasses of the generated class. I do think it would be useful to have a way of determining "if I call build, will it work?" without resorting to catching exceptions. Even this may not be perfect if there is any validation done in an overridden build() method.
Set<Foo::Builder::Properties> unsetProperties()
would solve the "pointless methods" issue, but it's a big API to commit to. At the moment, we can switch to "null means unset" internally to trade off space (one less field, one less type) against readability; exposing the enum pins our implementation. Having a single method is definitely a good direction to explore in the design space, though.
User-provided implementations of toString, hashCode and equals should always be overridden on the Partial type (unless declared final), as they should not throw UnsupportedOperationExceptions (which requires care in the user code). On the Value type, equals should be overridden to ensure Partials are treated correctly, then defer to the user implementation.
If the user overrides a Builder method but declares it protected, we should follow suit in the generated builder superclass.
Types generated by other annotation processors, e.g. https://github.com/sviperll/adt4j, cause FreeBuilder to error out. This is especially noticeable for collections of such types, as we cannot even see the type name in the first round of annotation processing.
environment
eclipse Mars.1 Release (4.5.1)
freebuilder-1.2-rc1.jar
jdk1.8
example
@FreeBuilder
public interface Value {
class Builder extends Value_Builder{}
}
@FreeBuilder
public interface Person {
Value getValue();
class Builder extends Person_Builder{
public static Builder of(){
return new Builder();
}
public static Builder of(Person metadata){
return new Builder().mergeFrom(metadata);
}
public static Builder of(Builder builder){
return new Builder().mergeFrom(builder);
}
}
}
Person_Builder can't be compiled.
Reason
The reason is the variable value is ambiguous. as show below.
the Autogenerated method mergeFrom
/**
* Sets all property values using the given {@code Person} as a template.
*/
public Person.Builder mergeFrom(Person value) {
value.mergeFrom(value.getValue());
return (Person.Builder) this;
}
The issue that has blocked this is setting the comparator; TreeMap's comparator is immutable, but we want to return an unmodifiable view from getMySortedMapProperty(). Options:
We've had issues with toolchains mistakenly registering FreeBuilder twice with Eclipse. The resulting Filer errors are currently caught and logged as warnings, but we should eliminate these warnings altogether, for instance by detecting and deduping on re-registration.
Currently, for instance, build() comes after Value, and buildPartial() after Partial, but this is less readable.
FreeBuilder raises errors on the method causing the problem, but does not first check they are on the @FreeBuilder-annotated type, erroneously raise warnings/errors against inoffensive supertypes with no indication of which subclass is at fault.
If I write a value type with an abstract "getInt()" method, FreeBuilder creates a field named "int". We should check for reserved names and modify the field name to something legal.
It currently treats names like 'get()' or 'getter()' as property getters, but the latter is questionable, and the former generates broken code. Should probably ensure there is a non-lowercase character following 'get'/'is'.
Using Guava's Immutable types internally is beneficial for code cleanliness, and more efficient when users are using Guava already (as copying is reduced), but Guava is a very large dependency. We should generate JDK-only code if Guava is not available at compile-time.
@FreeBuilder
public interface Bean {
String getName();
class Builder extends Bean_Builder {
}
}
after compile
@FreeBuilder
public interface Bean {
String getName();
class Builder extends Bean_Builder {
}
static Builder newBuilder() {
return new Builder();
}
}
FreeBuilder is designed to output a stub builder when the user supplies an erroneous type (e.g. private constructor, @Nullable
getter) so the error message(s) won't be swamped by references to undefined methods.
Unfortunately, javac seems to discard all generated code once an error is issued (by any annotation processor). We should see if there is anything that can be done on our side. For instance, does postponing errors to the final round work?
Propagate Java 8 type annotations.
The following generated code is invalid:
public Optional<Class<? extends java.lang.Exception>> getFoo() {
return Optional.fromNullable(foo);
}
where the foo field is of type Class<? extends java.lang.Exception>
Current thinking is to support custom "single-property builder" templates, with the API cloned onto the enclosing builder. For instance, a Set template might look like:
public class SetPropertyTemplate<T>
extends AbstractPropertyTemplate<Set<T>, SetPropertyTemplate<T>> {
private final HashSet<T> property = new HashSet<>();
public void addProperty(T element) {
property.add(checkNotNull(element));
}
public void addAllProperty(Iterable<T> elements) {
for (T element : elements) {
addProperty(element);
}
}
public void clearProperty() {
property.clear();
}
public Set<T> getProperty() {
return unmodifiableSet(property);
}
@Override public void mergeFrom(Set<T> elements) {
addAllProperty(elements);
}
@Override public ImmutableSet<T> getImmutableValue() {
return ImmutableSet.copyOf(property);
}
@Override public void clear() {
property.clear();
}
}
Here, "Property" is a magic string that gets replaced by the actual property name when the API is cloned, so for instance a set property called Values would result in methods addValues, addAllValues, clearValues and getValues. It should also be possible to inline the template definition as part of code generation, making custom extensions indistinguishable from built-in extensions. We could even use templates for all existing customization.
A key motivating example is values like IDs and timestamps that are generally minted
at build time, but should not be overridden during deserialization.
Failure mode is complaints about types not being whitelisted.
Hopefully this can be fixed on the autogenerated code. If not, the requirement to extends Serializable should be documented, and enforced at compile time.
If the Builder class is not marked as static, the compiler generates a "No enclosing instance of type is accessible. Must qualify the allocation with an enclosing instance of type ".
If a source file is in the default package, FreeBuilder errors. The error message I receive is super unhelpful. Whilst it's maybe fair to refuse to compile in the default package (I had forgotten to specify the package in the Eclipse UI) the error message should be better.
Internal error: java.lang.IllegalStateException
@Freebuilder
^
at org.inferred.freebuilder.shaded.com.google.common.base.Preconditions.checkState(Preconditions.java:161)
at org.inferred.freebuilder.processor.Analyser.generatedBuilderSimpleName(Analyser.java:686)
at org.inferred.freebuilder.processor.Analyser.analyse(Analyser.java:149)
at org.inferred.freebuilder.processor.Processor.process(Processor.java:77)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:793)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:722)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.access$1700(JavacProcessingEnvironment.java:97)
at com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1029)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1163)
at com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1108)
at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:824)
at com.sun.tools.javac.main.Main.compile(Main.java:439)
at com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:132)
at org.gradle.api.internal.tasks.compile.JdkJavaCompiler.execute(JdkJavaCompiler.java:45)
at org.gradle.api.internal.tasks.compile.JdkJavaCompiler.execute(JdkJavaCompiler.java:33)
at org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler.delegateAndHandleErrors(NormalizingJavaCompiler.java:101)
at org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler.execute(NormalizingJavaCompiler.java:50)
at org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler.execute(NormalizingJavaCompiler.java:36)
at org.gradle.api.internal.tasks.compile.CleaningJavaCompilerSupport.execute(CleaningJavaCompilerSupport.java:34)
at org.gradle.api.internal.tasks.compile.CleaningJavaCompilerSupport.execute(CleaningJavaCompilerSupport.java:25)
at org.gradle.api.tasks.compile.JavaCompile.performCompilation(JavaCompile.java:157)
at org.gradle.api.tasks.compile.JavaCompile.compile(JavaCompile.java:139)
at org.gradle.api.tasks.compile.JavaCompile.compile(JavaCompile.java:93)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:75)
at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.doExecute(AnnotationProcessingTaskFactory.java:243)
at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:219)
at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.execute(AnnotationProcessingTaskFactory.java:230)
at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:208)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:80)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:61)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:46)
at org.gradle.api.internal.tasks.execution.PostExecutionAnalysisTaskExecuter.execute(PostExecutionAnalysisTaskExecuter.java:35)
at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:64)
at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:58)
at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:52)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:52)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:53)
at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:203)
at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:185)
at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.processTask(AbstractTaskPlanExecutor.java:62)
at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.run(AbstractTaskPlanExecutor.java:50)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.process(DefaultTaskPlanExecutor.java:25)
at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter.execute(DefaultTaskGraphExecuter.java:110)
at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:37)
at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
at org.gradle.execution.DefaultBuildExecuter.access$000(DefaultBuildExecuter.java:23)
at org.gradle.execution.DefaultBuildExecuter$1.proceed(DefaultBuildExecuter.java:43)
at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:32)
at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:30)
at org.gradle.initialization.DefaultGradleLauncher$4.run(DefaultGradleLauncher.java:158)
at org.gradle.internal.Factories$1.create(Factories.java:22)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:90)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:52)
at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:155)
at org.gradle.initialization.DefaultGradleLauncher.access$200(DefaultGradleLauncher.java:36)
at org.gradle.initialization.DefaultGradleLauncher$1.create(DefaultGradleLauncher.java:103)
at org.gradle.initialization.DefaultGradleLauncher$1.create(DefaultGradleLauncher.java:97)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:90)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:62)
at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:97)
at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:86)
at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.run(InProcessBuildActionExecuter.java:102)
at org.gradle.tooling.internal.provider.ExecuteBuildActionRunner.run(ExecuteBuildActionRunner.java:28)
at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:47)
at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:32)
at org.gradle.launcher.exec.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:77)
at org.gradle.launcher.exec.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:47)
at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:52)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:37)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72)
at org.gradle.util.Swapper.swap(Swapper.java:38)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.health.DaemonHealthTracker.execute(DaemonHealthTracker.java:47)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:66)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:71)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.health.HintGCAfterBuild.execute(HintGCAfterBuild.java:41)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)
at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:246)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
This lets the user add extra properties to their type with no @Freebuilder interference.
Disadvantages:
When trying to add a property named template (getTemplate), the generated code doesn't compile because the following line is generated in the clear method:
template = template.template;
Link to the problematic line of code:
https://github.com/google/FreeBuilder/blob/714ba49b21662452ac6cfbc0916500e963ed9d8a/src/main/java/org/inferred/freebuilder/processor/CodeGenerator.java#L351
I've been sporadically seeing tests fail on JDK 8 with the following error:
java.lang.AssertionError: Filling jar:file:/build/work/6bd2fd14349c0a5c80d2dde58b4f5754/google3/runfiles/google3/third_party/java/jdk/jdk8-64/lib/ct.sym!/META-INF/sym/rt.jar/java/util/List.class during ZipFileIndexFileObject[/build/work/6bd2fd14349c0a5c80d2dde58b4f5754/google3/runfiles/google3/third_party/java/jdk/jdk8-64/lib/ct.sym(META-INF/sym/rt.jar/java/util/MissingResourceException.class)]
One of these Google results is suggesting a race condition on a ClassLoader, which may be relevant, as the test harness runs a compiler thread parallel to the test thread.
Use @Freebuilder to generate our internal data types, and commit the generated code to git.
I would like the ability to define a value class where a certain field isn't included in equality.
For example, I recently came to a need to define a helper class that wraps the input object T and its original index in the input List.
IndexedFoo {
int index();
Foo foo();
}
Multimap<IndexedFoo, Foo> overlappings = ...;
I want IndexedFoo's equality to be based on the index, but not the Foo object.
The hardest null bugs to spot are the ones where a method in a different package can return nulls. If you see foo.getBar(), it's unlikely you'll remember the contract of Foo well enough to spot that bar might be null. By contrast, if you see foo.getBar().orNull(), it's impossible to miss—and Optional gives us that. Immutable data types are very likely to be used in exactly these problem areas, so we made a deliberate decision to postpone null support.
That said, with Java 8, we may yet see a truly effective compile-time elimination of NullPointerExceptions from Java code, at which point the key objection to @nullable is no longer valid.
If you have strong reasons for requiring @nullable support sooner, please add a comment to this feature request.
If you're upgrading an existing @Nullable-returning data type to @Freebuilder, Eclipse's "Rename Method" and "Inline Method" refactoring tools make it a cinch to move over to Optional:
If you're upgrading an existing Builder, FreeBuilder generates a null-accepting setNullableBar, so just rename the existing setter method accordingly.
A field like public abstract ImmutableMap<String, String> getFields();
generates a method like:
public My.Builder putAllFields(Map<? extends String, ? extends String> map) {
for (String key : map.keySet()) {
putFields(key, map.get(key));
}
return (My.Builder) this;
}
FindBugs complains about inefficient use of keySet()
and map.get()
instead of entrySet()
.
I tried to submit a patch, but I had a lot of trouble getting this code running locally. I think you essentially want to change https://github.com/google/FreeBuilder/blob/25fac1babaef8363ea10ddd71b4ee921d1f536b0/src/main/java/org/inferred/freebuilder/processor/MapPropertyFactory.java#L162-L164 to something like:
.addLine(" for (Map.Entry<%s, %s> entry : map.entrySet()) {", unboxedKeyType.or(keyType), unboxedValueType.or(valueType))
.addLine(" %s%s(entry.getKey(), entry.getValue());", PUT_PREFIX, property.getCapitalizedName())
.addLine(" }")
Especially @MatchesPattern
and @Nonnegative
.
Note: Optional<@Nonnegative Integer>
can be stored internally as an int instead of an Integer, as -1 can represent Optional.absent. This kind of savings may be too rare an opportunity to pursue, though.
FB uses java.util.Objects to implement its functionality but seems to fail to properly qualify the name when there is a conflicting Objects in scope.
Good candidates:
Cyclic dependencies between API-linked types generated by other annotation processors (e.g. a FreeBuilder clone) are problematic, as both types need to detect the other is a buildable type before they can complete their API. One solution may be to generate the non-property-dependent parts of the API on round 1, and delay generation of the rest by using the superclass trick a second time (Type.Builder -> Type_Builder -> Type_Builder_Superclass)
Java 8 lambdas enable a new idiom: update methods for each field, taking a Function<T, T>. These are particularly useful for nested builders, replacing merge in many cases:
foo.updateBar(b -> b.setBaz(42));
Objects.equals, Objects.hash, <> operator, Objects.requireNonNull, @SafeVarargs (note: requires 'final' keyword)
While it's easy to follow the convention in new code, it's a blocker to using @Freebuilder in established classes. It's also a contentious requirement keeping many from adoption.
If a property is annotated @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
, Jackson will serialize the runtime class name to JSON, which is of course an implementation detail (Foo_Builder$Value). This can be fixed by adding a method, annotated @JsonTypeId
, to the type, which returns the name of the publicly-visible abstract value type instead of the FreeBuilder-generated subclass.
While this can be done by hand, it would be helpful if FreeBuilder generated this automatically.
The current whitespace treatment, while technically correct (the best kind of correct!) for HTML interpretation, allows extra blank lines to be introduced by mistake. The tests should fail somehow.
ACTUAL:
compiling
@FreeBuilder
public abstract static class SomeClass {
public abstract byte[] getBytes();
static class Builder extends SomeClass_Builder {}
}
results in:
SomeClass_Builder.java:114: error: [ArrayEquals] Reference equality used to compare arrays
if (!bytes.equals(other.bytes)) {
EXPECTED:
byte[] supported
OR
a graceful error message during compilation that suggests to use List.
Implement abstract "toBuilder" method if it's present on the value type.
ImpliedClass
and ImpliedNestedClass
implement java.lang.model.element.TypeElement
, but this API changes with each Java version.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.