diff --git a/core/src/main/java/org/jboss/jandex/ClassInfo.java b/core/src/main/java/org/jboss/jandex/ClassInfo.java index 070e5064..ff2e22e2 100644 --- a/core/src/main/java/org/jboss/jandex/ClassInfo.java +++ b/core/src/main/java/org/jboss/jandex/ClassInfo.java @@ -746,6 +746,50 @@ public final List constructors() { return constructors; } + /** + * Returns the canonical constructor of this record. If this class is not a record, returns {@code null}. + *

+ * Note that this method has the same limitations as {@link #unsortedRecordComponents()}. That is, + * at most 256 record components may be present, and an assumption is made that bytecode order + * of record components corresponds to the declaration order. + * + * @return the canonical constructor of this record, or {@code null} if this class is not a record + */ + public MethodInfo canonicalConstructor() { + if (!isRecord()) { + return null; + } + + RecordComponentInternal[] recordComponents = recordComponentArray(); + byte[] recordComponentPositions = recordComponentPositionArray(); + + if (recordComponents.length == recordComponentPositions.length) { + outer: for (MethodInternal method : methods) { + if (!Arrays.equals(Utils.INIT_METHOD_NAME, method.nameBytes())) { + // not a constructor + continue; + } + + Type[] parameters = method.parameterTypesArray(); + if (parameters.length != recordComponents.length) { + // not a constructor with the right number of parameters + continue; + } + + for (int i = 0; i < parameters.length; i++) { + if (!parameters[i].equals(recordComponents[recordComponentPositions[i] & 0xFF].type())) { + // not a constructor with matching parameter types + continue outer; + } + } + + return new MethodInfo(this, method); + } + } + + throw new IllegalStateException("Could not determine the canonical constructor"); + } + final MethodInternal[] methodArray() { return methods; } diff --git a/core/src/test/java/org/jboss/jandex/test/RecordTestCase.java b/core/src/test/java/org/jboss/jandex/test/RecordTestCase.java index 3ab735ca..2847b9f8 100644 --- a/core/src/test/java/org/jboss/jandex/test/RecordTestCase.java +++ b/core/src/test/java/org/jboss/jandex/test/RecordTestCase.java @@ -28,9 +28,11 @@ import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.ClassType; import org.jboss.jandex.DotName; import org.jboss.jandex.Index; import org.jboss.jandex.Indexer; +import org.jboss.jandex.PrimitiveType; import org.jboss.jandex.RecordComponentInfo; import org.jboss.jandex.test.util.IndexingUtil; import org.junit.jupiter.api.BeforeEach; @@ -152,6 +154,53 @@ public void testRecordSignatureProcessed() { assertEquals("T", rec.typeParameters().get(0).identifier()); } + @Test + public void canonicalCtor() { + ClassInfo rec = index.getClassByName("test.RecordWithNoComponentsAndDefaultCanonicalCtor"); + assertEquals(1, rec.constructors().size()); + assertEquals(rec.constructors().get(0), rec.canonicalConstructor()); + + rec = index.getClassByName("test.RecordWithNoComponentsAndCompactCanonicalCtor"); + assertEquals(1, rec.constructors().size()); + assertEquals(rec.constructors().get(0), rec.canonicalConstructor()); + + rec = index.getClassByName("test.RecordWithNoComponentsAndCustomCanonicalCtor"); + assertEquals(1, rec.constructors().size()); + assertEquals(rec.constructors().get(0), rec.canonicalConstructor()); + + rec = index.getClassByName("test.RecordWithDefaultCanonicalCtor"); + assertEquals(1, rec.constructors().size()); + assertEquals(rec.constructors().get(0), rec.canonicalConstructor()); + + rec = index.getClassByName("test.RecordWithCompactCanonicalCtor"); + assertEquals(1, rec.constructors().size()); + assertEquals(rec.constructors().get(0), rec.canonicalConstructor()); + + rec = index.getClassByName("test.RecordWithCustomCanonicalCtor"); + assertEquals(1, rec.constructors().size()); + assertEquals(rec.constructors().get(0), rec.canonicalConstructor()); + + rec = index.getClassByName("test.RecordWithMultipleCtorsAndDefaultCanonicalCtor"); + assertEquals(4, rec.constructors().size()); + assertEquals(2, rec.canonicalConstructor().parametersCount()); + assertEquals(PrimitiveType.INT, rec.canonicalConstructor().parameterType(0)); + assertEquals(ClassType.create(String.class), rec.canonicalConstructor().parameterType(1)); + + rec = index.getClassByName("test.RecordWithMultipleCtorsAndCompactCanonicalCtor"); + assertEquals(4, rec.constructors().size()); + assertEquals(2, rec.canonicalConstructor().parametersCount()); + assertEquals(PrimitiveType.INT, rec.canonicalConstructor().parameterType(0)); + assertEquals(ClassType.create(String.class), rec.canonicalConstructor().parameterType(1)); + + rec = index.getClassByName("test.RecordWithMultipleCtorsAndCustomCanonicalCtor"); + assertEquals(4, rec.constructors().size()); + assertEquals(2, rec.canonicalConstructor().parametersCount()); + assertEquals(PrimitiveType.INT, rec.canonicalConstructor().parameterType(0)); + assertEquals(ClassType.create(String.class), rec.canonicalConstructor().parameterType(1)); + + assertNull(index.getClassByName(RecordTestCase.class).canonicalConstructor()); + } + private Index buildIndex() throws IOException { Indexer indexer = new Indexer(); indexer.index(getClass().getClassLoader().getResourceAsStream("test/RecordExample.class")); @@ -160,6 +209,17 @@ private Index buildIndex() throws IOException { indexer.index(getClass().getClassLoader().getResourceAsStream("test/RecordExample$ComponentAnnotation.class")); indexer.index(getClass().getClassLoader().getResourceAsStream("test/RecordExample$FieldAnnotation.class")); indexer.index(getClass().getClassLoader().getResourceAsStream("test/RecordExample$AccessorAnnotation.class")); + indexer.index(getClass().getClassLoader().getResourceAsStream("test/RecordWithCompactCanonicalCtor.class")); + indexer.index(getClass().getClassLoader().getResourceAsStream("test/RecordWithCustomCanonicalCtor.class")); + indexer.index(getClass().getClassLoader().getResourceAsStream("test/RecordWithDefaultCanonicalCtor.class")); + indexer.index(getClass().getClassLoader().getResourceAsStream("test/RecordWithMultipleCtorsAndCompactCanonicalCtor.class")); + indexer.index(getClass().getClassLoader().getResourceAsStream("test/RecordWithMultipleCtorsAndCustomCanonicalCtor.class")); + indexer.index(getClass().getClassLoader().getResourceAsStream("test/RecordWithMultipleCtorsAndDefaultCanonicalCtor.class")); + indexer.index(getClass().getClassLoader().getResourceAsStream("test/RecordWithNoComponentsAndCompactCanonicalCtor.class")); + indexer.index(getClass().getClassLoader().getResourceAsStream("test/RecordWithNoComponentsAndCustomCanonicalCtor.class")); + indexer.index(getClass().getClassLoader().getResourceAsStream("test/RecordWithNoComponentsAndDefaultCanonicalCtor.class")); + + indexer.indexClass(RecordTestCase.class); Index index = indexer.complete(); return IndexingUtil.roundtrip(index); diff --git a/test-data/src/main/java/test/RecordWithCompactCanonicalCtor.java b/test-data/src/main/java/test/RecordWithCompactCanonicalCtor.java new file mode 100644 index 00000000..548ddda8 --- /dev/null +++ b/test-data/src/main/java/test/RecordWithCompactCanonicalCtor.java @@ -0,0 +1,12 @@ +package test; + +public record RecordWithCompactCanonicalCtor(int foo, String bar) { + public RecordWithCompactCanonicalCtor { + if (foo < 0) { + throw new IllegalArgumentException(); + } + if (bar == null) { + throw new IllegalArgumentException(); + } + } +} diff --git a/test-data/src/main/java/test/RecordWithCustomCanonicalCtor.java b/test-data/src/main/java/test/RecordWithCustomCanonicalCtor.java new file mode 100644 index 00000000..f155139b --- /dev/null +++ b/test-data/src/main/java/test/RecordWithCustomCanonicalCtor.java @@ -0,0 +1,15 @@ +package test; + +public record RecordWithCustomCanonicalCtor(int foo, String bar) { + public RecordWithCustomCanonicalCtor(int foo, String bar) { + if (foo < 0) { + throw new IllegalArgumentException(); + } + if (bar == null) { + throw new IllegalArgumentException(); + } + + this.foo = foo; + this.bar = bar; + } +} diff --git a/test-data/src/main/java/test/RecordWithDefaultCanonicalCtor.java b/test-data/src/main/java/test/RecordWithDefaultCanonicalCtor.java new file mode 100644 index 00000000..cc8fdc9e --- /dev/null +++ b/test-data/src/main/java/test/RecordWithDefaultCanonicalCtor.java @@ -0,0 +1,4 @@ +package test; + +public record RecordWithDefaultCanonicalCtor(int foo, String bar) { +} diff --git a/test-data/src/main/java/test/RecordWithMultipleCtorsAndCompactCanonicalCtor.java b/test-data/src/main/java/test/RecordWithMultipleCtorsAndCompactCanonicalCtor.java new file mode 100644 index 00000000..bb5b291a --- /dev/null +++ b/test-data/src/main/java/test/RecordWithMultipleCtorsAndCompactCanonicalCtor.java @@ -0,0 +1,24 @@ +package test; + +public record RecordWithMultipleCtorsAndCompactCanonicalCtor(int foo, String bar) { + public RecordWithMultipleCtorsAndCompactCanonicalCtor { + if (foo < 0) { + throw new IllegalArgumentException(); + } + if (bar == null) { + throw new IllegalArgumentException(); + } + } + + public RecordWithMultipleCtorsAndCompactCanonicalCtor(int foo) { + this(foo, ""); + } + + public RecordWithMultipleCtorsAndCompactCanonicalCtor(String bar) { + this(0, bar); + } + + public RecordWithMultipleCtorsAndCompactCanonicalCtor(String bar, int foo) { + this(foo, bar); + } +} diff --git a/test-data/src/main/java/test/RecordWithMultipleCtorsAndCustomCanonicalCtor.java b/test-data/src/main/java/test/RecordWithMultipleCtorsAndCustomCanonicalCtor.java new file mode 100644 index 00000000..48dd2455 --- /dev/null +++ b/test-data/src/main/java/test/RecordWithMultipleCtorsAndCustomCanonicalCtor.java @@ -0,0 +1,27 @@ +package test; + +public record RecordWithMultipleCtorsAndCustomCanonicalCtor(int foo, String bar) { + public RecordWithMultipleCtorsAndCustomCanonicalCtor(int foo, String bar) { + if (foo < 0) { + throw new IllegalArgumentException(); + } + if (bar == null) { + throw new IllegalArgumentException(); + } + + this.foo = foo; + this.bar = bar; + } + + public RecordWithMultipleCtorsAndCustomCanonicalCtor(int foo) { + this(foo, ""); + } + + public RecordWithMultipleCtorsAndCustomCanonicalCtor(String bar) { + this(0, bar); + } + + public RecordWithMultipleCtorsAndCustomCanonicalCtor(String bar, int foo) { + this(foo, bar); + } +} diff --git a/test-data/src/main/java/test/RecordWithMultipleCtorsAndDefaultCanonicalCtor.java b/test-data/src/main/java/test/RecordWithMultipleCtorsAndDefaultCanonicalCtor.java new file mode 100644 index 00000000..5f4afb83 --- /dev/null +++ b/test-data/src/main/java/test/RecordWithMultipleCtorsAndDefaultCanonicalCtor.java @@ -0,0 +1,15 @@ +package test; + +public record RecordWithMultipleCtorsAndDefaultCanonicalCtor(int foo, String bar) { + RecordWithMultipleCtorsAndDefaultCanonicalCtor(int foo) { + this(foo, ""); + } + + public RecordWithMultipleCtorsAndDefaultCanonicalCtor(String bar) { + this(0, bar); + } + + public RecordWithMultipleCtorsAndDefaultCanonicalCtor(String bar, int foo) { + this(foo, bar); + } +} diff --git a/test-data/src/main/java/test/RecordWithNoComponentsAndCompactCanonicalCtor.java b/test-data/src/main/java/test/RecordWithNoComponentsAndCompactCanonicalCtor.java new file mode 100644 index 00000000..caebcacc --- /dev/null +++ b/test-data/src/main/java/test/RecordWithNoComponentsAndCompactCanonicalCtor.java @@ -0,0 +1,6 @@ +package test; + +public record RecordWithNoComponentsAndCompactCanonicalCtor() { + public RecordWithNoComponentsAndCompactCanonicalCtor { + } +} diff --git a/test-data/src/main/java/test/RecordWithNoComponentsAndCustomCanonicalCtor.java b/test-data/src/main/java/test/RecordWithNoComponentsAndCustomCanonicalCtor.java new file mode 100644 index 00000000..4a2c6cc0 --- /dev/null +++ b/test-data/src/main/java/test/RecordWithNoComponentsAndCustomCanonicalCtor.java @@ -0,0 +1,6 @@ +package test; + +public record RecordWithNoComponentsAndCustomCanonicalCtor() { + public RecordWithNoComponentsAndCustomCanonicalCtor() { + } +} diff --git a/test-data/src/main/java/test/RecordWithNoComponentsAndDefaultCanonicalCtor.java b/test-data/src/main/java/test/RecordWithNoComponentsAndDefaultCanonicalCtor.java new file mode 100644 index 00000000..23981aaa --- /dev/null +++ b/test-data/src/main/java/test/RecordWithNoComponentsAndDefaultCanonicalCtor.java @@ -0,0 +1,4 @@ +package test; + +public record RecordWithNoComponentsAndDefaultCanonicalCtor() { +}