Files
openidea/java/lombok/tests/BuilderCompactConstructor.java
Ega Saputra ba8d224a46 fix(java): support Lombok builders on compact record constructors (#950)
@Builder on a compact record constructor (record R(...) { @Builder R {} }) failed in the
editor: ECJ has not filled in a compact constructor's implicit parameters when Lombok
runs, so the builder got no fields and builder() was undefined. A patch to Lombok's
Eclipse HandleBuilder takes the parameters from the record components in that case.

The bundled agent is now lombok-1.18.48-openidea.3, built from v1.18.48 with #4059,
#4089 and java/lombok/patches/builder-compact-constructor.patch through the documented
Ant build; only HandleBuilder differs from openidea.2. The new agent path gives JDT LS a
fresh workspace. java/lombok/tests/verify.py compiles and runs the regression cases with
javac and with ECJ plus the agent.

Co-authored-by: egasa21 <44431625+egasa21@users.noreply.github.com>
2026-11-06 11:55:21 +08:00

140 lines
4.8 KiB
Java

import java.math.BigDecimal;
import java.util.List;
import lombok.Builder;
import lombok.NonNull;
import lombok.Singular;
import lombok.SneakyThrows;
import lombok.RequiredArgsConstructor;
public class BuilderCompactConstructor {
@Builder(toBuilder = true)
record Payment(String number, String phone, BigDecimal amount,
BigDecimal serviceCharge, BigDecimal total, Status status) {
@Builder
Payment {
if (serviceCharge == null) serviceCharge = BigDecimal.ZERO;
}
}
enum Status { PENDING, SUCCESS, EXPIRED }
record ConstructorOnly(String name, int count) {
@Builder(toBuilder = true, setterPrefix = "with")
ConstructorOnly {
if (count < 0) throw new IllegalArgumentException("count");
}
}
@Builder(toBuilder = true)
record TypeOnly(int count) {
TypeOnly { count++; }
}
record Generic<T>(@NonNull T value, @Singular List<String> tags) {
static final String IGNORED = "not a component";
@Builder(toBuilder = true)
Generic {}
}
record Empty() {
@Builder
Empty {}
}
record Canonical(String name, int count) {
@Builder
Canonical(String name, int count) {
this.name = name.trim();
this.count = count;
}
}
record Explicit(String name, int count) {
@Builder
Explicit(String name) { this(name, 7); }
}
static class Ordinary {
final String name;
@Builder
@SneakyThrows
Ordinary(String name) { this.name = name; }
}
static class Parent {
Parent(String name) {}
}
static class ConstructorCalls extends Parent {
final String name;
@SneakyThrows
ConstructorCalls() {
this("default");
assert name.equals("default");
}
@SneakyThrows
ConstructorCalls(@NonNull String name) {
super(name);
this.name = name;
}
}
@RequiredArgsConstructor
static class GeneratedConstructor {
final int initialized = 7;
@NonNull final String name;
}
@Builder(builderMethodName = "methodBuilder", builderClassName = "MethodBuilder")
static String named(String name) { return name; }
public static void main(String[] args) {
var payment = Payment.builder().number("inquiry").phone("123")
.amount(BigDecimal.TEN).total(BigDecimal.TEN).status(Status.PENDING).build();
assert payment.number().equals("inquiry");
assert payment.phone().equals("123");
assert payment.amount().equals(BigDecimal.TEN);
assert payment.serviceCharge().equals(BigDecimal.ZERO);
assert payment.total().equals(BigDecimal.TEN);
assert payment.status() == Status.PENDING;
assert payment.toBuilder().phone("456").build().phone().equals("456");
assert Payment.builder().serviceCharge(BigDecimal.ONE).build().serviceCharge().equals(BigDecimal.ONE);
var constructor = ConstructorOnly.builder().withName("test").withCount(2).build();
assert constructor.toBuilder().withCount(3).build().count() == 3;
assert constructor.name().equals("test");
try {
ConstructorOnly.builder().withCount(-1).build();
throw new AssertionError("compact constructor validation was bypassed");
} catch (IllegalArgumentException expected) {}
assert TypeOnly.builder().count(2).build().count() == 3;
assert Empty.builder().build().equals(new Empty());
var generic = Generic.<String>builder().value("value").tag("one").build();
assert generic.value().equals("value");
assert generic.tags().equals(List.of("one"));
assert generic.toBuilder().tag("two").build().tags().equals(List.of("one", "two"));
try {
Generic.<String>builder().build();
throw new AssertionError("non-null check was bypassed");
} catch (NullPointerException expected) {}
assert Canonical.builder().name(" test ").count(2).build().equals(new Canonical("test", 2));
assert Explicit.builder().name("test").build().equals(new Explicit("test", 7));
assert Ordinary.builder().name("test").build().name.equals("test");
assert methodBuilder().name("test").build().equals("test");
assert new ConstructorCalls().name.equals("default");
assert new ConstructorCalls("explicit").name.equals("explicit");
assert new GeneratedConstructor("generated").initialized == 7;
assert new GeneratedConstructor("generated").name.equals("generated");
try {
new ConstructorCalls(null);
throw new AssertionError("constructor non-null check was bypassed");
} catch (NullPointerException expected) {}
}
}