You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ss...@apache.org on 2024/06/25 11:22:21 UTC
(sling-org-apache-sling-models-impl) branch master updated: SLING-12359 - Extend records support (#51)
This is an automated email from the ASF dual-hosted git repository.
sseifert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-models-impl.git
The following commit(s) were added to refs/heads/master by this push:
new 9d1c725 SLING-12359 - Extend records support (#51)
9d1c725 is described below
commit 9d1c72501226d3dfdfb0644c5632f5e29be5370e
Author: Herman Ciechanowiec <he...@ciechanowiec.eu>
AuthorDate: Tue Jun 25 13:22:16 2024 +0200
SLING-12359 - Extend records support (#51)
---
.../sling/models/impl/ModelAdapterFactory.java | 3 +
.../apache/sling/models/impl/ReflectionUtil.java | 33 ++++-
.../apache/sling/models/impl/model/ModelClass.java | 2 +-
.../models/impl/model/ModelClassConstructor.java | 6 +
.../sling/models/impl/ReflectionUtilTest.java | 148 +++++++++++++++++++++
5 files changed, 187 insertions(+), 5 deletions(-)
diff --git a/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java b/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java
index 9724013..94e6be6 100644
--- a/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java
+++ b/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java
@@ -812,6 +812,9 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable, ModelFacto
if (constructor.hasInjectAnnotation()) {
return constructor;
}
+ if (constructor.isCanonicalRecordConstructor()) {
+ return constructor;
+ }
// compatibility mode for sling models implementation <= 1.0.6:
// support constructor without @Inject if it has exactly one parameter matching the adaptable class
final Class<?>[] paramTypes = constructor.getConstructor().getParameterTypes();
diff --git a/src/main/java/org/apache/sling/models/impl/ReflectionUtil.java b/src/main/java/org/apache/sling/models/impl/ReflectionUtil.java
index 456b1c7..f8253a1 100644
--- a/src/main/java/org/apache/sling/models/impl/ReflectionUtil.java
+++ b/src/main/java/org/apache/sling/models/impl/ReflectionUtil.java
@@ -19,11 +19,9 @@
package org.apache.sling.models.impl;
import java.lang.annotation.Annotation;
-import java.lang.reflect.AnnotatedElement;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.Type;
+import java.lang.reflect.*;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
@@ -125,4 +123,31 @@ public final class ReflectionUtil {
}
}
+ public static boolean isRecord(Class<?> checkedType) {
+ try {
+ Class<?> recordType = Class.forName("java.lang.Record");
+ return recordType.isAssignableFrom(checkedType);
+ } catch (@SuppressWarnings("squid:S1166") ClassNotFoundException exception) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the number of parameters in the specified constructor matches the number of fields in the class
+ * that declares the constructor. Can be useful for detection of canonical constructors in records.
+ * Synthetic fields are ignored.
+ *
+ * @param constructor the constructor to check
+ * @return {@code true} if the number of constructor parameters equals the number of fields in the declaring class,
+ * {@code false} otherwise
+ */
+ public static boolean areBalancedCtorParamsAndFields(Constructor<?> constructor) {
+ int numOfCtorParams = constructor.getParameterCount();
+ Class<?> declaringClass = constructor.getDeclaringClass();
+ Field[] fields = declaringClass.getDeclaredFields();
+ long numOfFields = Arrays.stream(fields)
+ .filter(field -> !field.isSynthetic())
+ .count();
+ return numOfCtorParams == numOfFields;
+ }
}
diff --git a/src/main/java/org/apache/sling/models/impl/model/ModelClass.java b/src/main/java/org/apache/sling/models/impl/model/ModelClass.java
index 4f81a62..f3d776c 100644
--- a/src/main/java/org/apache/sling/models/impl/model/ModelClass.java
+++ b/src/main/java/org/apache/sling/models/impl/model/ModelClass.java
@@ -77,7 +77,7 @@ public class ModelClass<ModelType> {
}
private static InjectableField[] getInjectableFields(Class<?> type, StaticInjectAnnotationProcessorFactory[] processorFactories, DefaultInjectionStrategy defaultInjectionStrategy) {
- if (type.isInterface()) {
+ if (type.isInterface() || ReflectionUtil.isRecord(type)) {
return new InjectableField[0];
}
List<Field> injectableFields = ReflectionUtil.collectInjectableFields(type);
diff --git a/src/main/java/org/apache/sling/models/impl/model/ModelClassConstructor.java b/src/main/java/org/apache/sling/models/impl/model/ModelClassConstructor.java
index 23460be..ffb6a30 100644
--- a/src/main/java/org/apache/sling/models/impl/model/ModelClassConstructor.java
+++ b/src/main/java/org/apache/sling/models/impl/model/ModelClassConstructor.java
@@ -84,4 +84,10 @@ public class ModelClassConstructor<M> {
return constructorParametersArray;
};
+ public boolean isCanonicalRecordConstructor() {
+ Class<M> declaringClass = constructor.getDeclaringClass();
+ boolean areBalancedCtorParamsAndFields = ReflectionUtil.areBalancedCtorParamsAndFields(constructor);
+ boolean isRecordDeclaringClass = ReflectionUtil.isRecord(declaringClass);
+ return areBalancedCtorParamsAndFields && isRecordDeclaringClass;
+ }
}
diff --git a/src/test/java/org/apache/sling/models/impl/ReflectionUtilTest.java b/src/test/java/org/apache/sling/models/impl/ReflectionUtilTest.java
new file mode 100644
index 0000000..95121da
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/impl/ReflectionUtilTest.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.models.impl;
+
+import org.junit.Test;
+
+import java.lang.reflect.Constructor;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@SuppressWarnings({
+ "PackageVisibleInnerClass", "FieldCanBeLocal", "unused", "java:S1172",
+ "InstanceVariableMayNotBeInitialized", "PublicConstructorInNonPublicClass"
+})
+public class ReflectionUtilTest {
+
+ static class TestClassOne {
+ private final int field1;
+ private final String field2;
+
+ public TestClassOne(int field1, String field2) {
+ this.field1 = field1;
+ this.field2 = field2;
+ }
+ }
+
+ static class TestClassTwo {
+ private final int field1;
+ private final String field2;
+ private boolean field3;
+
+ public TestClassTwo(int field1, String field2) {
+ this.field1 = field1;
+ this.field2 = field2;
+ }
+ }
+
+ static class TestClassThree {
+ private final int field1;
+
+ public TestClassThree(int field1, String field2) {
+ this.field1 = field1;
+ }
+ }
+
+ static class TestClassFour {
+
+ public TestClassFour(int field1, String field2, boolean field3) {
+ }
+ }
+
+ static class TestClassFive {
+ private final int field1;
+
+ public TestClassFive(int field1) {
+ this.field1 = field1;
+ }
+ }
+
+ static class TestClassSix {
+ private final int field1;
+
+ public TestClassSix() {
+ this.field1 = 0;
+ }
+ }
+
+ static class TestClassSeven {
+ private final String field1;
+ private final String field2;
+
+ public TestClassSeven(String field1, String field2) {
+ this.field1 = field1;
+ this.field2 = field2;
+ }
+ }
+
+ static class TestClassEight {
+ private final String field1;
+
+ public TestClassEight(String field1, String field2) {
+ this.field1 = field1;
+ }
+ }
+
+ @Test
+ public void testBalancedConstructor() throws NoSuchMethodException {
+ Constructor<?> constructor = TestClassOne.class.getConstructor(int.class, String.class);
+ assertTrue(ReflectionUtil.areBalancedCtorParamsAndFields(constructor));
+ }
+
+ @Test
+ public void testMoreFieldsThanParams() throws NoSuchMethodException {
+ Constructor<?> constructor = TestClassTwo.class.getConstructor(int.class, String.class);
+ assertFalse(ReflectionUtil.areBalancedCtorParamsAndFields(constructor));
+ }
+
+ @Test
+ public void testMoreParamsThanFields() throws NoSuchMethodException {
+ Constructor<?> constructor = TestClassThree.class.getConstructor(int.class, String.class);
+ assertFalse(ReflectionUtil.areBalancedCtorParamsAndFields(constructor));
+ }
+
+ @Test
+ public void testNoFields() throws NoSuchMethodException {
+ Constructor<?> constructor = TestClassFour.class.getConstructor(int.class, String.class, boolean.class);
+ assertFalse(ReflectionUtil.areBalancedCtorParamsAndFields(constructor));
+ }
+
+ @Test
+ public void testOneFieldOneParam() throws NoSuchMethodException {
+ Constructor<?> constructor = TestClassFive.class.getConstructor(int.class);
+ assertTrue(ReflectionUtil.areBalancedCtorParamsAndFields(constructor));
+ }
+
+ @Test
+ public void testNoParams() throws NoSuchMethodException {
+ Constructor<?> constructor = TestClassSix.class.getConstructor();
+ assertFalse(ReflectionUtil.areBalancedCtorParamsAndFields(constructor));
+ }
+
+ @Test
+ public void testBalancedRepeatableTypes() throws NoSuchMethodException {
+ Constructor<?> constructor = TestClassSeven.class.getConstructor(String.class, String.class);
+ assertTrue(ReflectionUtil.areBalancedCtorParamsAndFields(constructor));
+ }
+
+ @Test
+ public void testUnbalancedRepeatableTypes() throws NoSuchMethodException {
+ Constructor<?> constructor = TestClassEight.class.getConstructor(String.class, String.class);
+ assertFalse(ReflectionUtil.areBalancedCtorParamsAndFields(constructor));
+ }
+}