Skip to content

Commit

Permalink
first steps in record support #1484
Browse files Browse the repository at this point in the history
  • Loading branch information
elucash committed Feb 29, 2024
1 parent 71c0f61 commit 1021ae3
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 55 deletions.
24 changes: 19 additions & 5 deletions builder/src/org/immutables/builder/Builder.java
Expand Up @@ -22,20 +22,34 @@
import org.immutables.value.Value.Style;

/**
* This umbrella annotation does nothing. Use nested annotations, such as {@literal @}
* This annotation works on records to create builders and to create builders
* for POJO constructors or factory methods, use nested annotations, such as {@literal @}
* {@code Builder.Factory} to generate builders for arbitrary static factory methods.
* and is used for static factory methods to generate arbitrary builders.
* Place this {@code Builder} annotation on record to generate builder from its
* canonical constructor parameters (record components).
* <pre>
* // since, immutables 2.11.0
* &#064;Builder
* record A(int b, String c) {}
*
* // or older syntax, before &#064;Builder annotation became functional
* // for records in 2.11.0, we would use &#064;Constructor placed on compact constructor
* record A(int b, String c) {
* &#064;Builder.Constructor A {}
* }
* </pre>
* Immutable values as {@link Immutable Value.Immutable} generate builder by default, unless
* turned off using {@literal @}{@link Immutable#builder() Value.Immutable(builder=false)}
* turned off using {@literal @}{@link Immutable#builder() Value.Immutable(builder=false)},
* so this annotation does nothing for {@code Value.Immutable} types.
* @see Factory
*/
@Target({})
@Target(ElementType.TYPE)
public @interface Builder {

/**
* Annotate nested static builder to get access to the builder's fields to avoid building the
* entire
* object. The fields will be protected rather then private as they are by default.
* object. The fields will be protected rather than private as they are by default.
*
* <pre>
* &#064;Immutable
Expand Down
10 changes: 5 additions & 5 deletions pom.xml
Expand Up @@ -39,18 +39,18 @@
<module>ordinal</module>
<module>builder</module>
<module>android-stub</module>
<module>gson</module>
<module>criteria</module>
<module>mongo</module>
<!-- <module>gson</module>-->
<!-- <module>criteria</module>-->
<!-- <module>mongo</module>-->
<module>func</module>
<module>data</module>
<module>value-processor</module>
<module>processor-testlib</module>
<module>value-annotations</module>
<module>value-fixture</module>
<module>value</module>
<module>serial</module>
<module>trees</module>
<!-- <module>serial</module>-->
<!-- <module>trees</module>-->
<module>encode</module>
<module>bom</module>
</modules>
Expand Down
24 changes: 0 additions & 24 deletions value-fixture/src-java-17/org/immutables/fixture/RecordTests.java

This file was deleted.

@@ -1,4 +1,4 @@
package org.immutables.fixture;
package org.immutables.fixture.j17;

import java.util.Map;
import javax.validation.constraints.Size;
Expand Down
@@ -0,0 +1,6 @@
package org.immutables.fixture.j17;

import org.immutables.builder.Builder;

@Builder
public record RecordOne(int aa, String bb) {}
11 changes: 11 additions & 0 deletions value-fixture/src-java-17/org/immutables/fixture/j17/Recs.java
@@ -0,0 +1,11 @@
package org.immutables.fixture.j17;

import org.immutables.builder.Builder;
import org.immutables.value.Value;

@Value.Enclosing
public sealed interface Recs {
@Builder record One(int aaa) implements Recs {}

@Builder record Two(String bbb) implements Recs {}
}
8 changes: 8 additions & 0 deletions value-fixture/test-java-17/RecordTest.java
@@ -0,0 +1,8 @@
import org.immutables.fixture.j17.ImmutableRecs;
import org.junit.Test;

public class RecordTest {
@Test public void rec1() {

}
}
17 changes: 2 additions & 15 deletions value-processor/src/org/immutables/value/processor/Processor.java
Expand Up @@ -23,22 +23,8 @@
import org.immutables.generator.ForwardingProcessingEnvironment;
import org.immutables.value.processor.encode.EncodingMirror;
import org.immutables.value.processor.encode.Generator_Encodings;
import org.immutables.value.processor.meta.CriteriaMirror;
import org.immutables.value.processor.meta.CriteriaRepositoryMirror;
import org.immutables.value.processor.meta.CustomImmutableAnnotations;
import org.immutables.value.processor.meta.EnclosingMirror;
import org.immutables.value.processor.meta.FConstructorMirror;
import org.immutables.value.processor.meta.FIncludeMirror;
import org.immutables.value.processor.meta.FactoryMirror;
import org.immutables.value.processor.meta.ImmutableMirror;
import org.immutables.value.processor.meta.ImmutableRound;
import org.immutables.value.processor.meta.IncludeMirror;
import org.immutables.value.processor.meta.ModifiableMirror;
import org.immutables.value.processor.meta.*;
import org.immutables.value.processor.meta.Proto.DeclaringPackage;
import org.immutables.value.processor.meta.Round;
import org.immutables.value.processor.meta.UnshadeGuava;
import org.immutables.value.processor.meta.ValueType;
import org.immutables.value.processor.meta.ValueUmbrellaMirror;

import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
Expand All @@ -59,6 +45,7 @@
ValueUmbrellaMirror.QUALIFIED_NAME,
FactoryMirror.QUALIFIED_NAME,
FConstructorMirror.QUALIFIED_NAME,
FBuilderMirror.QUALIFIED_NAME,
FIncludeMirror.QUALIFIED_NAME,
EncodingMirror.QUALIFIED_NAME,
CriteriaMirror.QUALIFIED_NAME,
Expand Down
Expand Up @@ -26,6 +26,9 @@ private BuilderMirrors() {}
@Mirror.Annotation("org.immutables.builder.Builder.Factory")
public @interface Factory {}

@Mirror.Annotation("org.immutables.builder.Builder")
public @interface FBuilder {}

@Mirror.Annotation("org.immutables.builder.Builder.Constructor")
public @interface FConstructor {}

Expand Down
Expand Up @@ -183,7 +183,7 @@ && implementationVisibility().isPublic()
/**
* Value is the canonical outside look of the value type. It should be either
* {@link #typeAbstract()} or {@link #typeImmutable()}.
* For factory it is a special surrogate.
* For a factory, it is a special surrogate.
* @return canonical value type name forms
*/
@Value.Lazy
Expand All @@ -200,6 +200,19 @@ public NameForms typeValue() {

if (isFactory()) {

if (protoclass().kind().isRecord()) {
TypeElement enclosingType = (TypeElement) protoclass().sourceElement();

return ImmutableConstitution.NameForms.builder()
.simple(enclosingType.getSimpleName().toString())
.relativeRaw(enclosingType.getQualifiedName().toString())
.genericArgs(generics().args())
.relativeAlreadyQualified(true)
.packageOf(NA_ERROR)
.visibility(protoclass().visibility())
.build();
}

if (protoclass().kind().isConstructor()) {
TypeElement enclosingType = (TypeElement) protoclass().sourceElement().getEnclosingElement();

Expand Down Expand Up @@ -277,7 +290,7 @@ public boolean hasEnclosingNonvalue() {
*/
@Value.Lazy
public NameForms typeAbstract() {
if (protoclass().kind().isConstructor()) {
if (protoclass().kind().isConstructor() || protoclass().kind().isRecord()) {
return typeValue();
}

Expand Down Expand Up @@ -420,6 +433,20 @@ private boolean isConstantNamingEquals(Naming naming, String name) {
@Value.Lazy
public AppliedNameForms factoryOf() {
if (isFactory()) {
if (protoclass().kind().isRecord()) {
TypeElement recordType = (TypeElement) protoclass().sourceElement();

return ImmutableConstitution.NameForms.builder()
.simple(recordType.getSimpleName().toString())
.relativeRaw(recordType.getQualifiedName().toString())
.genericArgs(generics().args())
.relativeAlreadyQualified(true)
.packageOf(NA_ERROR)
.visibility(protoclass().visibility())
.build()
.applied("new");
}

TypeElement enclosingType = (TypeElement) protoclass().sourceElement().getEnclosingElement();

String invoke = protoclass().kind().isConstructor()
Expand Down
Expand Up @@ -63,6 +63,7 @@
import org.immutables.value.processor.encode.Type;
import org.immutables.value.processor.meta.AnnotationInjections.AnnotationInjection;
import org.immutables.value.processor.meta.AnnotationInjections.InjectionInfo;
import org.immutables.value.processor.meta.BuilderMirrors.FBuilder;
import org.immutables.value.processor.meta.Reporter.About;
import org.immutables.value.processor.meta.Styles.UsingName.TypeNames;
import static com.google.common.base.Verify.verify;
Expand Down Expand Up @@ -1732,7 +1733,9 @@ public enum Kind {
DEFINED_COMPANION,
DEFINED_AND_ENCLOSING_TYPE,
DEFINED_ENCLOSING_TYPE,
DEFINED_NESTED_TYPE;
DEFINED_NESTED_TYPE,
DEFINED_RECORD,
DEFINED_NESTED_RECORD;

public boolean isNested() {
switch (this) {
Expand All @@ -1748,6 +1751,7 @@ public boolean isNestedFactoryOrConstructor() {
switch (this) {
case DEFINED_NESTED_FACTORY:
case DEFINED_NESTED_CONSTRUCTOR:
case DEFINED_NESTED_RECORD:
return true;
default:
return false;
Expand Down Expand Up @@ -1843,6 +1847,8 @@ public boolean isFactory() {
case INCLUDED_FACTORY_IN_PACKAGE:
case INCLUDED_FACTORY_ON_TYPE:
case DEFINED_CONSTRUCTOR:
case DEFINED_RECORD:
case DEFINED_NESTED_RECORD:
case DEFINED_NESTED_CONSTRUCTOR:
case INCLUDED_CONSTRUCTOR_IN_PACKAGE:
case INCLUDED_CONSTRUCTOR_ON_TYPE:
Expand All @@ -1855,6 +1861,10 @@ public boolean isFactory() {
public boolean isEnclosingOnly() {
return this == DEFINED_ENCLOSING_TYPE;
}

public boolean isRecord() {
return this == DEFINED_RECORD || this == DEFINED_NESTED_RECORD;
}
}

@Value.Lazy
Expand Down
@@ -0,0 +1,114 @@
/*
Copyright 2014 Immutables Authors and Contributors
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.immutables.value.processor.meta;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
import javax.lang.model.element.*;
import javax.lang.model.type.TypeMirror;
import com.google.common.base.Functions;
import com.google.common.base.Throwables;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.immutables.value.processor.encode.Instantiator;
import org.immutables.value.processor.encode.Instantiator.InstantiationCreator;
import org.immutables.value.processor.meta.Proto.Protoclass;

final class RecordComponentCollector {
private final Protoclass protoclass;
private final ValueType type;
private final List<ValueAttribute> attributes = Lists.newArrayList();
private final Styles styles;
private final Reporter reporter;

RecordComponentCollector(Protoclass protoclass, ValueType type) {
this.protoclass = protoclass;
this.styles = protoclass.styles();
this.type = type;
this.reporter = protoclass.report();
}

void collect() {
TypeElement recordType = (TypeElement) protoclass.sourceElement();

for (ExecutableElement accessor : recordComponentAssessors(recordType)) {
TypeMirror returnType = accessor.getReturnType();

ValueAttribute attribute = new ValueAttribute();
attribute.isGenerateAbstract = true;
attribute.reporter = reporter;
attribute.returnType = returnType;

attribute.element = accessor;
String parameterName = accessor.getSimpleName().toString();
attribute.names = styles.forAccessorWithRaw(parameterName, parameterName);

attribute.containingType = type;
attributes.add(attribute);
}

Instantiator encodingInstantiator = protoclass.encodingInstantiator();

@Nullable InstantiationCreator instantiationCreator =
encodingInstantiator.creatorFor(recordType);

for (ValueAttribute attribute : attributes) {
attribute.initAndValidate(instantiationCreator);
}

if (instantiationCreator != null) {
type.additionalImports(instantiationCreator.imports);
}

type.attributes.addAll(attributes);
}

// we reflectively access newer annotation processing APIs by still compiling
// to an older version.
private List<ExecutableElement> recordComponentAssessors(TypeElement type) {
type = CachingElements.getDelegate(type);
List<ExecutableElement> accessors = new ArrayList<ExecutableElement>();

try {
List<?> components = (List<?>) type.getClass()
.getMethod("getRecordComponents").invoke(type);

for (Object c : components) {
ExecutableElement accessor = (ExecutableElement) c.getClass().getMethod("getAccessor").invoke(c);
accessors.add(accessor);
}
} catch (IllegalAccessException
| InvocationTargetException
| ClassCastException
| NoSuchMethodException e) {
reporter.withElement(type)
.error("Problem with `TypeElement.getRecordComponents.*.getAccessors`"
+ " from record type mirror, compiler mismatch.\n"
+ Throwables.getStackTraceAsString(e));
}
return accessors;
}

private static ImmutableList<String> extractThrowsClause(ExecutableElement factoryMethodElement) {
return FluentIterable.from(factoryMethodElement.getThrownTypes())
.transform(Functions.toStringFunction())
.toList();
}
}

0 comments on commit 1021ae3

Please sign in to comment.