getJandexValueExtractor(ValueTypeDescriptor... classes) {
+ return SourceModelTestHelper.createBuildingContext( classes );
+ }
+
+ @Test
+ void serializeSimpleClass() {
+ final SourceModelBuildingContext buildingContext = createModelContext( SimpleClass.class );
+
+ final ClassDetails classDetails = buildingContext.getClassDetailsRegistry().findClassDetails( SimpleClass.class.getName() );
+ assertThat( classDetails ).isNotNull();
+
+ final StorableContext serialContext = buildingContext.toStorableForm();
+ final StorableContext clonedSerialContext = SerializationHelper.clone( serialContext );
+ assertThat( serialContext ).isNotSameAs( clonedSerialContext );
+
+ final SourceModelContext restored = clonedSerialContext.fromStorableForm( SIMPLE_CLASS_LOADING );
+ assertThat( buildingContext ).isNotSameAs( restored );
+ assertThat( buildingContext.getClassDetailsRegistry() ).isNotSameAs( restored.getClassDetailsRegistry() );
+ assertThat( buildingContext.getAnnotationDescriptorRegistry() ).isNotSameAs( restored.getAnnotationDescriptorRegistry() );
+
+ final ClassDetails cloneCassDetails = restored.getClassDetailsRegistry().findClassDetails( SimpleClass.class.getName() );
+ assertThat( cloneCassDetails ).isNotNull();
+ assertThat( classDetails ).isNotSameAs( cloneCassDetails );
+ }
+
+ @Test
+ void serializeSimpleClassWithMembers() {
+ final SourceModelBuildingContext buildingContext = createModelContext( SimpleClassWithMembers.class );
+
+ final ClassDetails classDetails = buildingContext.getClassDetailsRegistry().findClassDetails( SimpleClassWithMembers.class.getName() );
+ assertThat( classDetails ).isNotNull();
+ assertThat( classDetails.getFields() ).hasSize( 1 );
+ assertThat( classDetails.getMethods() ).hasSize( 3 );
+
+ final StorableContext serialContext = buildingContext.toStorableForm();
+ final StorableContext clonedSerialContext = SerializationHelper.clone( serialContext );
+ assertThat( serialContext ).isNotSameAs( clonedSerialContext );
+
+ final SourceModelContext restored = clonedSerialContext.fromStorableForm( SIMPLE_CLASS_LOADING );
+ assertThat( buildingContext ).isNotSameAs( restored );
+ assertThat( buildingContext.getClassDetailsRegistry() ).isNotSameAs( restored.getClassDetailsRegistry() );
+ assertThat( buildingContext.getAnnotationDescriptorRegistry() ).isNotSameAs( restored.getAnnotationDescriptorRegistry() );
+
+ final ClassDetails cloneCassDetails = restored.getClassDetailsRegistry().findClassDetails( SimpleClassWithMembers.class.getName() );
+ assertThat( cloneCassDetails ).isNotNull();
+ assertThat( classDetails ).isNotSameAs( cloneCassDetails );
+ assertThat( cloneCassDetails.getFields() ).hasSize( 1 );
+ assertThat( cloneCassDetails.getMethods() ).hasSize( 3 );
+ }
+
+ @Test
+ void serializeSimpleClassWithAnnotations() {
+ final SourceModelBuildingContext buildingContext = createModelContext( SimpleClassWithAnnotations.class );
+
+ final ClassDetails classDetails = buildingContext.getClassDetailsRegistry().findClassDetails( SimpleClassWithAnnotations.class.getName() );
+ assertThat( classDetails ).isNotNull();
+ assertThat( classDetails.getDirectAnnotationUsages() ).hasSize( 1 );
+ assertThat( classDetails.getFields() ).hasSize( 1 );
+ assertThat( classDetails.getFields().iterator().next().getDirectAnnotationUsages() ).hasSize( 1 );
+ assertThat( classDetails.getMethods() ).hasSize( 1 );
+ assertThat( classDetails.getMethods().iterator().next().getDirectAnnotationUsages() ).hasSize( 1 );
+
+ final StorableContext serialContext = buildingContext.toStorableForm();
+ final StorableContext clonedSerialContext = SerializationHelper.clone( serialContext );
+ assertThat( serialContext ).isNotSameAs( clonedSerialContext );
+
+ final SourceModelContext restored = clonedSerialContext.fromStorableForm( SIMPLE_CLASS_LOADING );
+ assertThat( restored ).isNotNull();
+ assertThat( buildingContext ).isNotSameAs( restored );
+ assertThat( buildingContext.getClassDetailsRegistry() ).isNotSameAs( restored.getClassDetailsRegistry() );
+ assertThat( buildingContext.getAnnotationDescriptorRegistry() ).isNotSameAs( restored.getAnnotationDescriptorRegistry() );
+
+ final ClassDetails cloneCassDetails = restored.getClassDetailsRegistry().findClassDetails( SimpleClassWithAnnotations.class.getName() );
+ assertThat( classDetails ).isNotSameAs( cloneCassDetails );
+ assertThat( cloneCassDetails.getDirectAnnotationUsages() ).hasSize( 1 );
+ assertThat( cloneCassDetails.getFields() ).hasSize( 1 );
+ assertThat( cloneCassDetails.getFields().iterator().next().getDirectAnnotationUsages() ).hasSize( 1 );
+ assertThat( cloneCassDetails.getMethods() ).hasSize( 1 );
+ assertThat( cloneCassDetails.getMethods().iterator().next().getDirectAnnotationUsages() ).hasSize( 1 );
+
+ }
+
+ public static class SimpleClass {
+ }
+
+ public static class SimpleClassWithMembers {
+ public int anInt;
+
+ public int getAnInt() {
+ return anInt;
+ }
+
+ public void setAnInt(int anInt) {
+ this.anInt = anInt;
+ }
+
+ public void doStuff() {
+ }
+ }
+
+ @Target({ ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface AnAnnotation {
+ }
+
+ @AnAnnotation
+ public static class SimpleClassWithAnnotations {
+ @AnAnnotation
+ private int anInt;
+
+ @AnAnnotation
+ public int getAnInt() {
+ return anInt;
+ }
+
+ }
+}
diff --git a/hibernate-models-testing/src/main/java/org/hibernate/models/SerializationHelper.java b/hibernate-models-testing/src/main/java/org/hibernate/models/SerializationHelper.java
new file mode 100644
index 0000000..f42de4c
--- /dev/null
+++ b/hibernate-models-testing/src/main/java/org/hibernate/models/SerializationHelper.java
@@ -0,0 +1,319 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.models;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.OutputStream;
+import java.io.Serializable;
+
+/**
+ * Assists with the serialization process and performs additional
+ * functionality based on serialization.
+ *
+ *
+ * - Deep clone using serialization
+ *
- Serialize managing finally and IOException
+ *
- Deserialize managing finally and IOException
+ *
+ *
+ * This class throws exceptions for invalid {@code null} inputs.
+ *
+ * @author Nissim Karpenstein
+ * @author Janek Bogucki
+ * @author Daniel Rall
+ * @author Stephen Colebourne
+ * @author Jeff Varszegi
+ * @author Gary Gregory
+ *
+ * @since 1.0
+ */
+public final class SerializationHelper {
+ private SerializationHelper() {
+ }
+
+ // Clone
+ //-----------------------------------------------------------------------
+
+ /**
+ * Deep clone an object using serialization.
+ *
+ * This is many times slower than writing clone methods by hand
+ * on all objects in your object graph. However, for complex object
+ * graphs, or for those that don't support deep cloning this can
+ * be a simple alternative implementation. Of course all the objects
+ * must be {@code Serializable}.
+ *
+ * @param object the {@code Serializable} object to clone
+ *
+ * @return the cloned object
+ */
+ public static T clone(T object) {
+ if ( object == null ) {
+ return null;
+ }
+ return deserialize( serialize( object ), object.getClass().getClassLoader() );
+ }
+
+ // Serialize
+ //-----------------------------------------------------------------------
+
+ /**
+ * Serializes an object to the given stream.
+ *
+ * The stream will be closed once the object is written.
+ * This avoids the need for a finally clause, and maybe also
+ * for exception handling, in the application code.
+ *
+ * The stream passed in is not buffered internally within this
+ * method. This is the responsibility of the caller, if desired.
+ *
+ * @param obj the object to serialize to bytes, may be null
+ * @param outputStream the stream to write to, must not be null
+ *
+ * @throws IllegalArgumentException if {@code outputStream} is null
+ */
+ public static void serialize(Serializable obj, OutputStream outputStream) {
+ if ( outputStream == null ) {
+ throw new IllegalArgumentException( "The OutputStream must not be null" );
+ }
+
+ ObjectOutputStream out = null;
+ try {
+ // stream closed in the finally
+ out = new ObjectOutputStream( outputStream );
+ out.writeObject( obj );
+
+ }
+ catch (IOException ex) {
+ throw new RuntimeException( "could not serialize", ex );
+ }
+ finally {
+ try {
+ if ( out != null ) {
+ out.close();
+ }
+ }
+ catch (IOException ignored) {
+ }
+ }
+ }
+
+ /**
+ * Serializes an object to a byte array for storage or
+ * externalization.
+ *
+ * @param obj the object to serialize to bytes
+ *
+ * @return a byte[] with the converted Serializable
+ */
+ public static byte[] serialize(Serializable obj) {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream( 512 );
+ serialize( obj, byteArrayOutputStream );
+ return byteArrayOutputStream.toByteArray();
+ }
+
+ // Deserialize
+ //-----------------------------------------------------------------------
+
+ /**
+ * Deserializes an object from the given stream using the
+ * Thread Context ClassLoader (TCCL).
+ *
+ * Delegates to {@link #doDeserialize}
+ *
+ * @param inputStream the serialized object input stream, must not be null
+ *
+ * @return the deserialized object
+ *
+ * @throws IllegalArgumentException if {@code inputStream} is null
+ */
+ public static T deserialize(InputStream inputStream) {
+ return doDeserialize( inputStream, defaultClassLoader(), hibernateClassLoader(), null );
+ }
+
+ /**
+ * Returns the Thread Context ClassLoader (TCCL).
+ *
+ * @return The current TCCL
+ */
+ public static ClassLoader defaultClassLoader() {
+ return Thread.currentThread().getContextClassLoader();
+ }
+
+ public static ClassLoader hibernateClassLoader() {
+ return SerializationHelper.class.getClassLoader();
+ }
+
+ /**
+ * Deserializes an object from the given stream using the
+ * Thread Context ClassLoader (TCCL). If there is no TCCL set,
+ * the classloader of the calling class is used.
+ *
+ * The stream will be closed once the object is read. This
+ * avoids the need for a finally clause, and maybe also for
+ * exception handling, in the application code.
+ *
+ * The stream passed in is not buffered internally within this
+ * method. This is the responsibility of the caller, if desired.
+ *
+ * @param inputStream the serialized object input stream, must not be null
+ * @param loader The classloader to use
+ *
+ * @return the deserialized object
+ *
+ * @throws IllegalArgumentException if inputStream
is null
+ */
+ public static Object deserialize(InputStream inputStream, ClassLoader loader) {
+ return doDeserialize( inputStream, loader, defaultClassLoader(), hibernateClassLoader() );
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T doDeserialize(
+ InputStream inputStream,
+ ClassLoader loader,
+ ClassLoader fallbackLoader1,
+ ClassLoader fallbackLoader2) {
+ if ( inputStream == null ) {
+ throw new IllegalArgumentException( "The InputStream must not be null" );
+ }
+
+ try {
+ CustomObjectInputStream in = new CustomObjectInputStream(
+ inputStream,
+ loader,
+ fallbackLoader1,
+ fallbackLoader2
+ );
+ try {
+ return (T) in.readObject();
+ }
+ catch (ClassNotFoundException | IOException e) {
+ throw new RuntimeException( "could not deserialize", e );
+ }
+ finally {
+ try {
+ in.close();
+ }
+ catch (IOException ignore) {
+ // ignore
+ }
+ }
+ }
+ catch (IOException e) {
+ throw new RuntimeException( "could not deserialize", e );
+ }
+ }
+
+ /**
+ * Deserializes an object from an array of bytes using the
+ * Thread Context ClassLoader (TCCL). If there is no TCCL set,
+ * the classloader of the calling class is used.
+ *
+ * Delegates to {@link #deserialize(byte[], ClassLoader)}
+ *
+ * @param objectData the serialized object, must not be null
+ *
+ * @return the deserialized object
+ *
+ * @throws IllegalArgumentException if objectData
is null
+ */
+ public static T deserialize(byte[] objectData) {
+ return doDeserialize( wrap( objectData ), defaultClassLoader(), hibernateClassLoader(), null );
+ }
+
+ private static InputStream wrap(byte[] objectData) {
+ if ( objectData == null ) {
+ throw new IllegalArgumentException( "The byte[] must not be null" );
+ }
+ return new ByteArrayInputStream( objectData );
+ }
+
+ /**
+ * Deserializes an object from an array of bytes.
+ *
+ * Delegates to {@link #deserialize(InputStream, ClassLoader)} using a
+ * {@link ByteArrayInputStream} to wrap the array.
+ *
+ * @param objectData the serialized object, must not be null
+ * @param loader The classloader to use
+ *
+ * @return the deserialized object
+ *
+ * @throws IllegalArgumentException if objectData
is null
+ */
+ public static T deserialize(byte[] objectData, ClassLoader loader) {
+ return doDeserialize( wrap( objectData ), loader, defaultClassLoader(), hibernateClassLoader() );
+ }
+
+
+ /**
+ * By default, to resolve the classes being deserialized JDK serialization uses the
+ * classes loader which loaded the class which initiated the deserialization call. Here
+ * that would be Hibernate classes. However, there are cases where that is not the correct
+ * class loader to use; mainly here we are worried about deserializing user classes in
+ * environments (app servers, etc) where Hibernate is on a parent classes loader. To
+ * facilitate for that we allow passing in the class loader we should use.
+ */
+ private static final class CustomObjectInputStream extends ObjectInputStream {
+ private final ClassLoader loader1;
+ private final ClassLoader loader2;
+ private final ClassLoader loader3;
+
+ private CustomObjectInputStream(
+ InputStream in,
+ ClassLoader loader1,
+ ClassLoader loader2,
+ ClassLoader loader3) throws IOException {
+ super( in );
+ this.loader1 = loader1;
+ this.loader2 = loader2;
+ this.loader3 = loader3;
+ }
+
+ @Override
+ protected Class resolveClass(ObjectStreamClass v) throws IOException, ClassNotFoundException {
+ final String className = v.getName();
+
+ try {
+ return Class.forName( className, false, loader1 );
+ }
+ catch (ClassNotFoundException ignored) {
+ }
+
+ if ( different( loader1, loader2 ) ) {
+ try {
+ return Class.forName( className, false, loader2 );
+ }
+ catch (ClassNotFoundException ignored) {
+ }
+ }
+
+ if ( different( loader1, loader3 ) && different( loader2, loader3 ) ) {
+ try {
+ return Class.forName( className, false, loader3 );
+ }
+ catch (ClassNotFoundException ignored) {
+ }
+ }
+
+ // By default delegate to normal JDK deserialization which will use the class loader
+ // of the class which is calling this deserialization.
+ return super.resolveClass( v );
+ }
+
+ private boolean different(ClassLoader one, ClassLoader other) {
+ if ( one == null ) {
+ return other != null;
+ }
+ return !one.equals( other );
+ }
+ }
+}
diff --git a/hibernate-models/build.gradle b/hibernate-models/build.gradle
index 4626a78..4670f18 100644
--- a/hibernate-models/build.gradle
+++ b/hibernate-models/build.gradle
@@ -2,4 +2,8 @@ plugins {
id "published-java-module"
}
-description = "A de-typed abstraction over reflection and annotations"
\ No newline at end of file
+description = "A de-typed abstraction over reflection and annotations"
+
+dependencies {
+ testImplementation project( ":hibernate-models-testing" )
+}
\ No newline at end of file
diff --git a/hibernate-models/src/main/java/org/hibernate/models/internal/AbstractAnnotationDescriptorRegistry.java b/hibernate-models/src/main/java/org/hibernate/models/internal/AbstractAnnotationDescriptorRegistry.java
index 975a0ed..d6e4179 100644
--- a/hibernate-models/src/main/java/org/hibernate/models/internal/AbstractAnnotationDescriptorRegistry.java
+++ b/hibernate-models/src/main/java/org/hibernate/models/internal/AbstractAnnotationDescriptorRegistry.java
@@ -16,8 +16,8 @@
* @author Steve Ebersole
*/
public abstract class AbstractAnnotationDescriptorRegistry implements AnnotationDescriptorRegistry {
- protected final Map, AnnotationDescriptor>> descriptorMap;
- protected final Map, AnnotationDescriptor>> repeatableByContainerMap;
+ protected final Map, AnnotationDescriptor extends Annotation>> descriptorMap;
+ protected final Map, AnnotationDescriptor extends Annotation>> repeatableByContainerMap;
public AbstractAnnotationDescriptorRegistry() {
this( new ConcurrentHashMap<>(), new ConcurrentHashMap<>() );
diff --git a/hibernate-models/src/main/java/org/hibernate/models/internal/AnnotationDescriptorRegistryStandard.java b/hibernate-models/src/main/java/org/hibernate/models/internal/AnnotationDescriptorRegistryStandard.java
index 91c538f..1efdc81 100644
--- a/hibernate-models/src/main/java/org/hibernate/models/internal/AnnotationDescriptorRegistryStandard.java
+++ b/hibernate-models/src/main/java/org/hibernate/models/internal/AnnotationDescriptorRegistryStandard.java
@@ -17,7 +17,9 @@
*
* @author Steve Ebersole
*/
-public class AnnotationDescriptorRegistryStandard extends AbstractAnnotationDescriptorRegistry implements MutableAnnotationDescriptorRegistry {
+public class AnnotationDescriptorRegistryStandard
+ extends AbstractAnnotationDescriptorRegistry
+ implements MutableAnnotationDescriptorRegistry {
private final SourceModelBuildingContext modelBuildingContext;
public AnnotationDescriptorRegistryStandard(SourceModelBuildingContext modelBuildingContext) {
diff --git a/hibernate-models/src/main/java/org/hibernate/models/internal/BasicModelBuildingContextImpl.java b/hibernate-models/src/main/java/org/hibernate/models/internal/BasicModelBuildingContextImpl.java
index d120096..d3a24f2 100644
--- a/hibernate-models/src/main/java/org/hibernate/models/internal/BasicModelBuildingContextImpl.java
+++ b/hibernate-models/src/main/java/org/hibernate/models/internal/BasicModelBuildingContextImpl.java
@@ -4,6 +4,8 @@
*/
package org.hibernate.models.internal;
+import org.hibernate.models.serial.internal.StorableContextImpl;
+import org.hibernate.models.serial.spi.StorableContext;
import org.hibernate.models.spi.ClassLoading;
import org.hibernate.models.spi.RegistryPrimer;
@@ -38,4 +40,9 @@ public MutableAnnotationDescriptorRegistry getAnnotationDescriptorRegistry() {
public MutableClassDetailsRegistry getClassDetailsRegistry() {
return classDetailsRegistry;
}
+
+ @Override
+ public StorableContext toStorableForm() {
+ return new StorableContextImpl( classDetailsRegistry.classDetailsMap, descriptorRegistry.descriptorMap );
+ }
}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/internal/ClassDetailsRegistryStandard.java b/hibernate-models/src/main/java/org/hibernate/models/internal/ClassDetailsRegistryStandard.java
index 14b58d4..27a3ecb 100644
--- a/hibernate-models/src/main/java/org/hibernate/models/internal/ClassDetailsRegistryStandard.java
+++ b/hibernate-models/src/main/java/org/hibernate/models/internal/ClassDetailsRegistryStandard.java
@@ -15,7 +15,9 @@
*
* @author Steve Ebersole
*/
-public class ClassDetailsRegistryStandard extends AbstractClassDetailsRegistry implements MutableClassDetailsRegistry {
+public class ClassDetailsRegistryStandard
+ extends AbstractClassDetailsRegistry
+ implements MutableClassDetailsRegistry {
private final ClassDetailsBuilder classDetailsBuilder;
public ClassDetailsRegistryStandard(SourceModelBuildingContext context) {
@@ -35,5 +37,4 @@ public ClassDetailsRegistryStandard(ClassDetailsBuilder classDetailsBuilder, Sou
protected ClassDetailsBuilder getClassDetailsBuilder() {
return classDetailsBuilder;
}
-
}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/internal/MissingPackageInfoDetails.java b/hibernate-models/src/main/java/org/hibernate/models/internal/MissingPackageInfoDetails.java
index 2d8b2c2..62465ce 100644
--- a/hibernate-models/src/main/java/org/hibernate/models/internal/MissingPackageInfoDetails.java
+++ b/hibernate-models/src/main/java/org/hibernate/models/internal/MissingPackageInfoDetails.java
@@ -10,6 +10,7 @@
import java.util.function.Consumer;
import org.hibernate.models.internal.util.IndexedConsumer;
+import org.hibernate.models.serial.spi.SerialClassDetails;
import org.hibernate.models.spi.AnnotationDescriptor;
import org.hibernate.models.spi.ClassDetails;
import org.hibernate.models.spi.FieldDetails;
@@ -196,4 +197,34 @@ public void forEachRecordComponent(IndexedConsumer consu
public Class toJavaClass() {
throw new UnsupportedOperationException( "Missing package-info [" + packageInfoClassName + "] cannot be converted to a Java Class" );
}
+
+ @Override
+ public SerialClassDetails toStorableForm() {
+ return new SerialFormImpl( packageName, packageInfoClassName );
+ }
+
+ private static class SerialFormImpl implements SerialClassDetails {
+ private final String packageName;
+ private final String packageInfoClassName;
+
+ public SerialFormImpl(String packageName, String packageInfoClassName) {
+ this.packageName = packageName;
+ this.packageInfoClassName = packageInfoClassName;
+ }
+
+ @Override
+ public String getName() {
+ return packageName;
+ }
+
+ @Override
+ public String getClassName() {
+ return packageInfoClassName;
+ }
+
+ @Override
+ public ClassDetails fromStorableForm(SourceModelBuildingContext context) {
+ return new MissingPackageInfoDetails( packageName, packageInfoClassName );
+ }
+ }
}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/internal/SimpleClassDetails.java b/hibernate-models/src/main/java/org/hibernate/models/internal/SimpleClassDetails.java
index 17d6f10..3bf4d06 100644
--- a/hibernate-models/src/main/java/org/hibernate/models/internal/SimpleClassDetails.java
+++ b/hibernate-models/src/main/java/org/hibernate/models/internal/SimpleClassDetails.java
@@ -10,7 +10,9 @@
import java.util.List;
import java.util.function.Consumer;
+import org.hibernate.models.internal.jdk.SerialJdkClassDetails;
import org.hibernate.models.internal.util.IndexedConsumer;
+import org.hibernate.models.serial.spi.SerialClassDetails;
import org.hibernate.models.spi.AnnotationDescriptor;
import org.hibernate.models.spi.ClassDetails;
import org.hibernate.models.spi.FieldDetails;
@@ -228,4 +230,9 @@ public X getNamedAnnotationUsage(
SourceModelBuildingContext modelContext) {
return null;
}
+
+ @Override
+ public SerialClassDetails toStorableForm() {
+ return new SerialJdkClassDetails( clazz.getName(), clazz );
+ }
}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/internal/SimpleClassLoading.java b/hibernate-models/src/main/java/org/hibernate/models/internal/SimpleClassLoading.java
index e85dc2b..de2f351 100644
--- a/hibernate-models/src/main/java/org/hibernate/models/internal/SimpleClassLoading.java
+++ b/hibernate-models/src/main/java/org/hibernate/models/internal/SimpleClassLoading.java
@@ -4,6 +4,7 @@
*/
package org.hibernate.models.internal;
+import java.io.Serializable;
import java.net.URL;
import java.util.Collection;
import java.util.HashSet;
@@ -19,7 +20,7 @@
*
* @author Steve Ebersole
*/
-public class SimpleClassLoading implements ClassLoading {
+public class SimpleClassLoading implements ClassLoading, Serializable {
public static final SimpleClassLoading SIMPLE_CLASS_LOADING = new SimpleClassLoading();
@Override
diff --git a/hibernate-models/src/main/java/org/hibernate/models/internal/dynamic/DynamicClassDetails.java b/hibernate-models/src/main/java/org/hibernate/models/internal/dynamic/DynamicClassDetails.java
index 202c691..eae133b 100644
--- a/hibernate-models/src/main/java/org/hibernate/models/internal/dynamic/DynamicClassDetails.java
+++ b/hibernate-models/src/main/java/org/hibernate/models/internal/dynamic/DynamicClassDetails.java
@@ -10,6 +10,7 @@
import org.hibernate.models.internal.ClassDetailsSupport;
import org.hibernate.models.internal.ClassTypeDetailsImpl;
+import org.hibernate.models.serial.spi.SerialClassDetails;
import org.hibernate.models.spi.ClassDetails;
import org.hibernate.models.spi.FieldDetails;
import org.hibernate.models.spi.MethodDetails;
@@ -233,4 +234,9 @@ public Class toJavaClass() {
public String toString() {
return "DynamicClassDetails(" + name + " (" + className + "))";
}
+
+ @Override
+ public SerialClassDetails toStorableForm() {
+ throw new UnsupportedOperationException( "Not implemented yet" );
+ }
}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/internal/jdk/JdkBuilders.java b/hibernate-models/src/main/java/org/hibernate/models/internal/jdk/JdkBuilders.java
index 398c4bd..9801a75 100644
--- a/hibernate-models/src/main/java/org/hibernate/models/internal/jdk/JdkBuilders.java
+++ b/hibernate-models/src/main/java/org/hibernate/models/internal/jdk/JdkBuilders.java
@@ -4,6 +4,7 @@
*/
package org.hibernate.models.internal.jdk;
+import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.reflect.Method;
@@ -29,7 +30,7 @@
*
* @author Steve Ebersole
*/
-public class JdkBuilders implements ClassDetailsBuilder {
+public class JdkBuilders implements ClassDetailsBuilder, Serializable {
/**
* Singleton access
*/
diff --git a/hibernate-models/src/main/java/org/hibernate/models/internal/jdk/JdkClassDetails.java b/hibernate-models/src/main/java/org/hibernate/models/internal/jdk/JdkClassDetails.java
index c9cf6a8..914fc31 100644
--- a/hibernate-models/src/main/java/org/hibernate/models/internal/jdk/JdkClassDetails.java
+++ b/hibernate-models/src/main/java/org/hibernate/models/internal/jdk/JdkClassDetails.java
@@ -16,6 +16,7 @@
import org.hibernate.models.internal.ClassDetailsSupport;
import org.hibernate.models.internal.util.CollectionHelper;
+import org.hibernate.models.serial.spi.SerialClassDetails;
import org.hibernate.models.spi.ClassDetails;
import org.hibernate.models.spi.ClassDetailsRegistry;
import org.hibernate.models.spi.FieldDetails;
@@ -183,6 +184,9 @@ public List getFields() {
this.fields = arrayList( reflectionFields.length );
for ( int i = 0; i < reflectionFields.length; i++ ) {
final Field reflectionField = reflectionFields[i];
+ if ( reflectionField.isSynthetic() ) {
+ continue;
+ }
fields.add( new JdkFieldDetails( reflectionField, this, getModelContext() ) );
}
}
@@ -200,6 +204,9 @@ public List getMethods() {
final Method[] reflectionMethods = managedClass.getDeclaredMethods();
this.methods = arrayList( reflectionMethods.length );
for ( int i = 0; i < reflectionMethods.length; i++ ) {
+ if ( reflectionMethods[i].isSynthetic() ) {
+ continue;
+ }
this.methods.add( buildMethodDetails( reflectionMethods[i], this, getModelContext() ) );
}
}
@@ -230,4 +237,9 @@ public List getRecordComponents() {
public String toString() {
return "JdkClassDetails(" + name + ")";
}
+
+ @Override
+ public SerialClassDetails toStorableForm() {
+ return new SerialJdkClassDetails( name, managedClass );
+ }
}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/internal/jdk/SerialJdkClassDetails.java b/hibernate-models/src/main/java/org/hibernate/models/internal/jdk/SerialJdkClassDetails.java
new file mode 100644
index 0000000..0cc20e0
--- /dev/null
+++ b/hibernate-models/src/main/java/org/hibernate/models/internal/jdk/SerialJdkClassDetails.java
@@ -0,0 +1,34 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.models.internal.jdk;
+
+import org.hibernate.models.serial.spi.SerialClassDetails;
+import org.hibernate.models.spi.ClassDetails;
+import org.hibernate.models.spi.SourceModelBuildingContext;
+
+public class SerialJdkClassDetails implements SerialClassDetails {
+ private final String name;
+ private final Class> javaType;
+
+ public SerialJdkClassDetails(String name, Class> javaType) {
+ this.name = name;
+ this.javaType = javaType;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getClassName() {
+ return javaType.getName();
+ }
+
+ @Override
+ public ClassDetails fromStorableForm(SourceModelBuildingContext context) {
+ return new JdkClassDetails( name, javaType, context );
+ }
+}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/internal/util/CollectionHelper.java b/hibernate-models/src/main/java/org/hibernate/models/internal/util/CollectionHelper.java
index 202c36c..832e355 100644
--- a/hibernate-models/src/main/java/org/hibernate/models/internal/util/CollectionHelper.java
+++ b/hibernate-models/src/main/java/org/hibernate/models/internal/util/CollectionHelper.java
@@ -7,6 +7,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@@ -16,6 +17,8 @@
*/
public class CollectionHelper {
public static final int DEFAULT_LIST_CAPACITY = 10;
+ public static final int MINIMUM_INITIAL_CAPACITY = 16;
+ public static final float LOAD_FACTOR = 0.75f;
public static boolean isEmpty(@SuppressWarnings("rawtypes") Collection collection) {
return collection == null || collection.isEmpty();
@@ -86,4 +89,30 @@ public static void forEach(T[] values, Consumer consumer) {
}
}
}
+
+ /**
+ * Determine the proper initial size for a new collection in order for it to hold the given a number of elements.
+ * Specifically we want to account for load size and load factor to prevent immediate resizing.
+ *
+ * @param numberOfElements The number of elements to be stored.
+ *
+ * @return The proper size.
+ */
+ public static int determineProperSizing(int numberOfElements) {
+ int actual = ( (int) ( numberOfElements / LOAD_FACTOR ) ) + 1;
+ return Math.max( actual, MINIMUM_INITIAL_CAPACITY );
+ }
+
+ /**
+ * Build a properly sized linked map, especially handling load size and load factor to prevent immediate resizing.
+ *
+ * Especially helpful for copy map contents.
+ *
+ * @param size The size to make the map.
+ *
+ * @return The sized linked map.
+ */
+ public static LinkedHashMap linkedMapOfSize(int size) {
+ return new LinkedHashMap<>( determineProperSizing( size ), LOAD_FACTOR );
+ }
}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/serial/internal/ClassDetailsBuilderImpl.java b/hibernate-models/src/main/java/org/hibernate/models/serial/internal/ClassDetailsBuilderImpl.java
new file mode 100644
index 0000000..e04f5ec
--- /dev/null
+++ b/hibernate-models/src/main/java/org/hibernate/models/serial/internal/ClassDetailsBuilderImpl.java
@@ -0,0 +1,35 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.models.serial.internal;
+
+import org.hibernate.models.serial.spi.SerialClassDetails;
+import org.hibernate.models.spi.ClassDetails;
+import org.hibernate.models.spi.ClassDetailsBuilder;
+import org.hibernate.models.spi.ClassLoading;
+import org.hibernate.models.spi.SourceModelBuildingContext;
+
+/**
+ * @author Steve Ebersole
+ */
+public class ClassDetailsBuilderImpl implements ClassDetailsBuilder {
+ private StorableContextImpl serialContext;
+
+ public ClassDetailsBuilderImpl(StorableContextImpl serialContext, ClassLoading classLoading) {
+ this.serialContext = serialContext;
+ }
+
+ @Override
+ public ClassDetails buildClassDetails(String name, SourceModelBuildingContext buildingContext) {
+ if ( serialContext == null ) {
+ throw new IllegalStateException( "Building context is now immutable" );
+ }
+ final SerialClassDetails serialClassDetails = serialContext.getSerialClassDetailsMap().get( name );
+ return serialClassDetails.fromStorableForm( buildingContext );
+ }
+
+ public void invalidate() {
+ serialContext = null;
+ }
+}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/serial/internal/RestoredModelContext.java b/hibernate-models/src/main/java/org/hibernate/models/serial/internal/RestoredModelContext.java
new file mode 100644
index 0000000..4653fbe
--- /dev/null
+++ b/hibernate-models/src/main/java/org/hibernate/models/serial/internal/RestoredModelContext.java
@@ -0,0 +1,80 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.models.serial.internal;
+
+import java.lang.annotation.Annotation;
+import java.util.Map;
+
+import org.hibernate.models.internal.AnnotationDescriptorRegistryStandard;
+import org.hibernate.models.internal.ClassDetailsRegistryStandard;
+import org.hibernate.models.internal.MutableAnnotationDescriptorRegistry;
+import org.hibernate.models.internal.MutableClassDetailsRegistry;
+import org.hibernate.models.serial.spi.SerialAnnotationDescriptor;
+import org.hibernate.models.serial.spi.SerialClassDetails;
+import org.hibernate.models.serial.spi.StorableContext;
+import org.hibernate.models.spi.AnnotationDescriptorRegistry;
+import org.hibernate.models.spi.ClassDetailsRegistry;
+import org.hibernate.models.spi.ClassLoading;
+import org.hibernate.models.spi.SourceModelBuildingContext;
+import org.hibernate.models.spi.SourceModelContext;
+
+/**
+ * SourceModelBuildingContext implementation used with serialization support.
+ *
+ * @implNote This implementation is considered immutable after construction. It implements
+ * {@linkplain SourceModelBuildingContext} instead of just {@linkplain SourceModelContext}
+ * simply for ease of coding. From the API point of view, via {@linkplain StorableContext#fromStorableForm},
+ * this implementation is always treated as an immutable {@linkplain SourceModelContext}.
+ *
+ * @author Steve Ebersole
+ */
+public class RestoredModelContext implements SourceModelBuildingContext {
+ private final MutableAnnotationDescriptorRegistry annotationDescriptorRegistry;
+ private final MutableClassDetailsRegistry classDetailsRegistry;
+
+ private ClassLoading classLoading;
+
+ public RestoredModelContext(StorableContextImpl serialContext, ClassLoading classLoading) {
+ this.classLoading = classLoading;
+
+ final ClassDetailsBuilderImpl classDetailsBuilder = new ClassDetailsBuilderImpl( serialContext, classLoading );
+
+ this.annotationDescriptorRegistry = new AnnotationDescriptorRegistryStandard( this );
+ this.classDetailsRegistry = new ClassDetailsRegistryStandard( classDetailsBuilder, this );
+
+ for ( Map.Entry classDetailsEntry : serialContext.getSerialClassDetailsMap().entrySet() ) {
+ classDetailsRegistry.resolveClassDetails( classDetailsEntry.getKey() );
+ }
+
+ for ( Map.Entry, SerialAnnotationDescriptor extends Annotation>> annotationDescriptorEntry
+ : serialContext.getSerialAnnotationDescriptorMap().entrySet() ) {
+ final SerialAnnotationDescriptor extends Annotation> serialDescriptor = annotationDescriptorEntry.getValue();
+ annotationDescriptorRegistry.register( serialDescriptor.fromStorableForm( this ) );
+ }
+
+ classLoading = null;
+ classDetailsBuilder.invalidate();
+ }
+
+ @Override
+ public ClassLoading getClassLoading() {
+ return classLoading;
+ }
+
+ @Override
+ public AnnotationDescriptorRegistry getAnnotationDescriptorRegistry() {
+ return annotationDescriptorRegistry;
+ }
+
+ @Override
+ public ClassDetailsRegistry getClassDetailsRegistry() {
+ return classDetailsRegistry;
+ }
+
+ @Override
+ public StorableContext toStorableForm() {
+ throw new UnsupportedOperationException( );
+ }
+}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/serial/internal/SerialAnnotationDescriptorImpl.java b/hibernate-models/src/main/java/org/hibernate/models/serial/internal/SerialAnnotationDescriptorImpl.java
new file mode 100644
index 0000000..cdbe6be
--- /dev/null
+++ b/hibernate-models/src/main/java/org/hibernate/models/serial/internal/SerialAnnotationDescriptorImpl.java
@@ -0,0 +1,28 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.models.serial.internal;
+
+import java.lang.annotation.Annotation;
+
+import org.hibernate.models.internal.StandardAnnotationDescriptor;
+import org.hibernate.models.serial.spi.SerialAnnotationDescriptor;
+import org.hibernate.models.spi.AnnotationDescriptor;
+import org.hibernate.models.spi.SourceModelBuildingContext;
+
+/**
+ * @author Steve Ebersole
+ */
+public class SerialAnnotationDescriptorImpl implements SerialAnnotationDescriptor {
+ private final Class annotationType;
+
+ public SerialAnnotationDescriptorImpl(Class annotationType) {
+ this.annotationType = annotationType;
+ }
+
+ @Override
+ public AnnotationDescriptor fromStorableForm(SourceModelBuildingContext context) {
+ return new StandardAnnotationDescriptor<>( annotationType, context );
+ }
+}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/serial/internal/StorableContextImpl.java b/hibernate-models/src/main/java/org/hibernate/models/serial/internal/StorableContextImpl.java
new file mode 100644
index 0000000..5911d88
--- /dev/null
+++ b/hibernate-models/src/main/java/org/hibernate/models/serial/internal/StorableContextImpl.java
@@ -0,0 +1,56 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.models.serial.internal;
+
+import java.lang.annotation.Annotation;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.hibernate.models.serial.spi.SerialAnnotationDescriptor;
+import org.hibernate.models.serial.spi.SerialClassDetails;
+import org.hibernate.models.serial.spi.StorableContext;
+import org.hibernate.models.spi.AnnotationDescriptor;
+import org.hibernate.models.spi.ClassDetails;
+import org.hibernate.models.spi.ClassLoading;
+import org.hibernate.models.spi.SourceModelContext;
+
+import static org.hibernate.models.internal.util.CollectionHelper.linkedMapOfSize;
+
+/**
+ * Standard implementation of {@linkplain StorableContext} representing a serializable {@linkplain SourceModelContext}
+ *
+ * @author Steve Ebersole
+ */
+public class StorableContextImpl implements StorableContext {
+ private final LinkedHashMap serialClassDetailsMap;
+ private final LinkedHashMap, SerialAnnotationDescriptor extends Annotation>> serialAnnotationDescriptorMap;
+
+ public StorableContextImpl(
+ Map classDetailsMap,
+ Map, AnnotationDescriptor extends Annotation>> annotationDescriptorMap) {
+ serialClassDetailsMap = linkedMapOfSize( classDetailsMap.size() );
+ serialAnnotationDescriptorMap = linkedMapOfSize( annotationDescriptorMap.size() );
+
+ for ( Map.Entry classDetailsEntry : classDetailsMap.entrySet() ) {
+ serialClassDetailsMap.put( classDetailsEntry.getKey(), classDetailsEntry.getValue().toStorableForm() );
+ }
+ for ( Map.Entry, AnnotationDescriptor extends Annotation>> descriptorEntry : annotationDescriptorMap.entrySet() ) {
+ serialAnnotationDescriptorMap.put( descriptorEntry.getKey(), descriptorEntry.getValue().toStorableForm() );
+ }
+ }
+
+ @Override
+ public SourceModelContext fromStorableForm(ClassLoading classLoading) {
+ return new RestoredModelContext( this, classLoading );
+ }
+
+ public LinkedHashMap getSerialClassDetailsMap() {
+ return serialClassDetailsMap;
+ }
+
+ public LinkedHashMap, SerialAnnotationDescriptor extends Annotation>> getSerialAnnotationDescriptorMap() {
+ return serialAnnotationDescriptorMap;
+ }
+}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/serial/package-info.java b/hibernate-models/src/main/java/org/hibernate/models/serial/package-info.java
new file mode 100644
index 0000000..ea61aab
--- /dev/null
+++ b/hibernate-models/src/main/java/org/hibernate/models/serial/package-info.java
@@ -0,0 +1,11 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+
+/**
+ * Support for serializing and deserializing {@linkplain org.hibernate.models.spi.SourceModelBuildingContext},
+ * {@linkplain org.hibernate.models.spi.ClassDetailsRegistry},
+ * {@linkplain org.hibernate.models.spi.AnnotationDescriptorRegistry}, etc.
+ */
+package org.hibernate.models.serial;
diff --git a/hibernate-models/src/main/java/org/hibernate/models/serial/spi/SerialAnnotationDescriptor.java b/hibernate-models/src/main/java/org/hibernate/models/serial/spi/SerialAnnotationDescriptor.java
new file mode 100644
index 0000000..88de46c
--- /dev/null
+++ b/hibernate-models/src/main/java/org/hibernate/models/serial/spi/SerialAnnotationDescriptor.java
@@ -0,0 +1,16 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.models.serial.spi;
+
+import java.lang.annotation.Annotation;
+
+import org.hibernate.models.spi.AnnotationDescriptor;
+
+/**
+ * @author Steve Ebersole
+ */
+public interface SerialAnnotationDescriptor extends StorableForm> {
+
+}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/serial/spi/SerialClassDetails.java b/hibernate-models/src/main/java/org/hibernate/models/serial/spi/SerialClassDetails.java
new file mode 100644
index 0000000..63d6f23
--- /dev/null
+++ b/hibernate-models/src/main/java/org/hibernate/models/serial/spi/SerialClassDetails.java
@@ -0,0 +1,16 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.models.serial.spi;
+
+import org.hibernate.models.spi.ClassDetails;
+
+/**
+ * @author Steve Ebersole
+ */
+public interface SerialClassDetails extends StorableForm {
+ String getName();
+
+ String getClassName();
+}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/serial/spi/Storable.java b/hibernate-models/src/main/java/org/hibernate/models/serial/spi/Storable.java
new file mode 100644
index 0000000..d5505eb
--- /dev/null
+++ b/hibernate-models/src/main/java/org/hibernate/models/serial/spi/Storable.java
@@ -0,0 +1,16 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.models.serial.spi;
+
+/**
+ * A part of the {@linkplain org.hibernate.models.spi.SourceModelContext model context} which can
+ * be stored in the context's {@linkplain StorableContext serial form}.
+ *
+ * @param The storable's type
+ * @param The storable's serial-type's type
+ */
+public interface Storable> {
+ S toStorableForm();
+}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/serial/spi/StorableContext.java b/hibernate-models/src/main/java/org/hibernate/models/serial/spi/StorableContext.java
new file mode 100644
index 0000000..f4761af
--- /dev/null
+++ b/hibernate-models/src/main/java/org/hibernate/models/serial/spi/StorableContext.java
@@ -0,0 +1,25 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.models.serial.spi;
+
+import java.io.Serializable;
+
+import org.hibernate.models.spi.ClassLoading;
+import org.hibernate.models.spi.SourceModelBuildingContext;
+import org.hibernate.models.spi.SourceModelContext;
+
+/**
+ * Form of {@linkplain SourceModelContext} which is serializable.
+ *
+ * @see SourceModelBuildingContext#toStorableForm()
+ *
+ * @author Steve Ebersole
+ */
+public interface StorableContext extends Serializable {
+ /**
+ * "Re-construct" the model context from the serial form
+ */
+ SourceModelContext fromStorableForm(ClassLoading classLoading);
+}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/serial/spi/StorableForm.java b/hibernate-models/src/main/java/org/hibernate/models/serial/spi/StorableForm.java
new file mode 100644
index 0000000..b3a573f
--- /dev/null
+++ b/hibernate-models/src/main/java/org/hibernate/models/serial/spi/StorableForm.java
@@ -0,0 +1,19 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.models.serial.spi;
+
+import java.io.Serializable;
+
+import org.hibernate.models.spi.SourceModelBuildingContext;
+
+/**
+ * Serial form for various parts of a {@linkplain SourceModelBuildingContext context}
+ * included in its {@linkplain StorableContext serial form}.
+ *
+ * @author Steve Ebersole
+ */
+public interface StorableForm extends Serializable {
+ T fromStorableForm(SourceModelBuildingContext context);
+}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/spi/AnnotationDescriptor.java b/hibernate-models/src/main/java/org/hibernate/models/spi/AnnotationDescriptor.java
index 0ca66a1..df84301 100644
--- a/hibernate-models/src/main/java/org/hibernate/models/spi/AnnotationDescriptor.java
+++ b/hibernate-models/src/main/java/org/hibernate/models/spi/AnnotationDescriptor.java
@@ -13,13 +13,17 @@
import org.hibernate.models.IllegalCastException;
import org.hibernate.models.UnknownAnnotationAttributeException;
import org.hibernate.models.internal.AnnotationProxy;
+import org.hibernate.models.serial.internal.SerialAnnotationDescriptorImpl;
+import org.hibernate.models.serial.spi.SerialAnnotationDescriptor;
+import org.hibernate.models.serial.spi.Storable;
/**
* Describes an annotation type (the Class)
*
* @author Steve Ebersole
*/
-public interface AnnotationDescriptor extends AnnotationTarget {
+public interface AnnotationDescriptor
+ extends AnnotationTarget, Storable, SerialAnnotationDescriptor> {
@Override
default Kind getKind() {
return Kind.ANNOTATION;
@@ -152,4 +156,8 @@ default RecordComponentDetails asRecordComponentDetails() {
throw new IllegalCastException( "AnnotationDescriptor cannot be cast to a RecordComponentDetails" );
}
+ @Override
+ default SerialAnnotationDescriptor toStorableForm() {
+ return new SerialAnnotationDescriptorImpl<>( getAnnotationType() );
+ }
}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/spi/ClassDetails.java b/hibernate-models/src/main/java/org/hibernate/models/spi/ClassDetails.java
index 2db4bb8..cb91904 100644
--- a/hibernate-models/src/main/java/org/hibernate/models/spi/ClassDetails.java
+++ b/hibernate-models/src/main/java/org/hibernate/models/spi/ClassDetails.java
@@ -13,6 +13,8 @@
import org.hibernate.models.internal.AnnotationTargetHelper;
import org.hibernate.models.internal.SimpleClassDetails;
import org.hibernate.models.internal.util.IndexedConsumer;
+import org.hibernate.models.serial.spi.SerialClassDetails;
+import org.hibernate.models.serial.spi.Storable;
/**
* Abstraction for what Hibernate understands about a "class", generally before it has access to
@@ -21,7 +23,7 @@
* @author Steve Ebersole
* @see ClassDetailsRegistry
*/
-public interface ClassDetails extends AnnotationTarget, TypeVariableScope {
+public interface ClassDetails extends AnnotationTarget, TypeVariableScope, Storable {
/**
* Details for {@code Object.class}
*/
@@ -321,5 +323,4 @@ default MethodDetails asMethodDetails() {
default RecordComponentDetails asRecordComponentDetails() {
throw new IllegalCastException( "ClassDetails cannot be cast to RecordComponentDetails" );
}
-
}
diff --git a/hibernate-models/src/main/java/org/hibernate/models/spi/SourceModelBuildingContext.java b/hibernate-models/src/main/java/org/hibernate/models/spi/SourceModelBuildingContext.java
index 0e0e648..a310035 100644
--- a/hibernate-models/src/main/java/org/hibernate/models/spi/SourceModelBuildingContext.java
+++ b/hibernate-models/src/main/java/org/hibernate/models/spi/SourceModelBuildingContext.java
@@ -4,6 +4,8 @@
*/
package org.hibernate.models.spi;
+import org.hibernate.models.serial.spi.StorableContext;
+
/**
* Context object used while building references for {@link AnnotationDescriptor},
* {@link ClassDetails} and friends.
@@ -22,4 +24,6 @@ default S as(Class type) {
//noinspection unchecked
return (S) this;
}
+
+ StorableContext toStorableForm();
}
diff --git a/hibernate-models/src/test/java/org/hibernate/models/SimpleSerializationTests.java b/hibernate-models/src/test/java/org/hibernate/models/SimpleSerializationTests.java
new file mode 100644
index 0000000..26cb1e9
--- /dev/null
+++ b/hibernate-models/src/test/java/org/hibernate/models/SimpleSerializationTests.java
@@ -0,0 +1,136 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.models;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.hibernate.models.serial.spi.StorableContext;
+import org.hibernate.models.spi.ClassDetails;
+import org.hibernate.models.spi.SourceModelBuildingContext;
+import org.hibernate.models.spi.SourceModelContext;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hibernate.models.internal.SimpleClassLoading.SIMPLE_CLASS_LOADING;
+
+public class SimpleSerializationTests {
+ protected SourceModelBuildingContext createModelContext(Class>... classes) {
+ return SourceModelTestHelper.createBuildingContext( classes );
+ }
+
+ @Test
+ void serializeSimpleClass() {
+ final SourceModelBuildingContext buildingContext = createModelContext( SimpleClass.class );
+ final ClassDetails classDetails = buildingContext.getClassDetailsRegistry().findClassDetails( SimpleClass.class.getName() );
+ assertThat( classDetails ).isNotNull();
+
+ final StorableContext serialContext = buildingContext.toStorableForm();
+ final StorableContext clonedSerialContext = SerializationHelper.clone( serialContext );
+ assertThat( serialContext ).isNotSameAs( clonedSerialContext );
+
+ final SourceModelContext restored = clonedSerialContext.fromStorableForm( SIMPLE_CLASS_LOADING );
+ assertThat( buildingContext.getClassDetailsRegistry() ).isNotSameAs( restored.getClassDetailsRegistry() );
+ assertThat( buildingContext.getAnnotationDescriptorRegistry() ).isNotSameAs( restored.getAnnotationDescriptorRegistry() );
+
+ final ClassDetails restoredClassDetails = restored.getClassDetailsRegistry().findClassDetails( SimpleClass.class.getName() );
+ assertThat( restoredClassDetails ).isNotNull();
+ assertThat( classDetails ).isNotSameAs( restoredClassDetails );
+ }
+
+ @Test
+ void serializeSimpleClassWithMembers() {
+ final SourceModelBuildingContext buildingContext = createModelContext( SimpleClassWithMembers.class );
+
+ final ClassDetails classDetails = buildingContext.getClassDetailsRegistry().findClassDetails( SimpleClassWithMembers.class.getName() );
+ assertThat( classDetails ).isNotNull();
+ assertThat( classDetails.getFields() ).hasSize( 1 );
+ assertThat( classDetails.getMethods() ).hasSize( 3 );
+
+ final StorableContext serialContext = buildingContext.toStorableForm();
+ final StorableContext clonedSerialContext = SerializationHelper.clone( serialContext );
+ assertThat( serialContext ).isNotSameAs( clonedSerialContext );
+
+ final SourceModelContext restored = clonedSerialContext.fromStorableForm( SIMPLE_CLASS_LOADING );
+ assertThat( buildingContext ).isNotSameAs( restored );
+ assertThat( buildingContext.getClassDetailsRegistry() ).isNotSameAs( restored.getClassDetailsRegistry() );
+ assertThat( buildingContext.getAnnotationDescriptorRegistry() ).isNotSameAs( restored.getAnnotationDescriptorRegistry() );
+
+ final ClassDetails cloneCassDetails = restored.getClassDetailsRegistry().findClassDetails( SimpleClassWithMembers.class.getName() );
+ assertThat( cloneCassDetails ).isNotNull();
+ assertThat( cloneCassDetails.getFields() ).hasSize( 1 );
+ assertThat( cloneCassDetails.getMethods() ).hasSize( 3 );
+
+ assertThat( classDetails ).isNotSameAs( cloneCassDetails );
+ }
+
+ @Test
+ void serializeSimpleClassWithAnnotations() {
+ final SourceModelBuildingContext buildingContext = createModelContext( SimpleClassWithAnnotations.class );
+
+ final ClassDetails classDetails = buildingContext.getClassDetailsRegistry().findClassDetails( SimpleClassWithAnnotations.class.getName() );
+ assertThat( classDetails ).isNotNull();
+ assertThat( classDetails.getDirectAnnotationUsages() ).hasSize( 1 );
+ assertThat( classDetails.getFields() ).hasSize( 1 );
+ assertThat( classDetails.getFields().iterator().next().getDirectAnnotationUsages() ).hasSize( 1 );
+ assertThat( classDetails.getMethods() ).hasSize( 1 );
+ assertThat( classDetails.getMethods().iterator().next().getDirectAnnotationUsages() ).hasSize( 1 );
+
+ final StorableContext serialContext = buildingContext.toStorableForm();
+ final StorableContext clonedSerialContext = SerializationHelper.clone( serialContext );
+ assertThat( serialContext ).isNotSameAs( clonedSerialContext );
+
+ final SourceModelContext restored = clonedSerialContext.fromStorableForm( SIMPLE_CLASS_LOADING );
+ assertThat( buildingContext ).isNotSameAs( restored );
+ assertThat( buildingContext.getClassDetailsRegistry() ).isNotSameAs( restored.getClassDetailsRegistry() );
+ assertThat( buildingContext.getAnnotationDescriptorRegistry() ).isNotSameAs( restored.getAnnotationDescriptorRegistry() );
+
+ final ClassDetails cloneCassDetails = restored.getClassDetailsRegistry().findClassDetails( SimpleClassWithAnnotations.class.getName() );
+ assertThat( classDetails ).isNotSameAs( cloneCassDetails );
+ assertThat( cloneCassDetails.getDirectAnnotationUsages() ).hasSize( 1 );
+ assertThat( cloneCassDetails.getFields() ).hasSize( 1 );
+ assertThat( cloneCassDetails.getFields().iterator().next().getDirectAnnotationUsages() ).hasSize( 1 );
+ assertThat( cloneCassDetails.getMethods() ).hasSize( 1 );
+ assertThat( cloneCassDetails.getMethods().iterator().next().getDirectAnnotationUsages() ).hasSize( 1 );
+ }
+
+ public static class SimpleClass {
+ }
+
+ public static class SimpleClassWithMembers {
+ public int anInt;
+
+ public int getAnInt() {
+ return anInt;
+ }
+
+ public void setAnInt(int anInt) {
+ this.anInt = anInt;
+ }
+
+ public void doStuff() {
+ }
+ }
+
+ @Target({ ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface AnAnnotation {
+ }
+
+ @AnAnnotation
+ public static class SimpleClassWithAnnotations {
+ @AnAnnotation
+ private int anInt;
+
+ @AnAnnotation
+ public int getAnInt() {
+ return anInt;
+ }
+
+ }
+}
diff --git a/hibernate-models/src/test/java/org/hibernate/models/SourceModelTestHelper.java b/hibernate-models/src/test/java/org/hibernate/models/SourceModelTestHelper.java
new file mode 100644
index 0000000..b36c43d
--- /dev/null
+++ b/hibernate-models/src/test/java/org/hibernate/models/SourceModelTestHelper.java
@@ -0,0 +1,42 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.models;
+
+import org.hibernate.models.internal.BasicModelBuildingContextImpl;
+import org.hibernate.models.internal.util.CollectionHelper;
+import org.hibernate.models.orm.JpaAnnotations;
+import org.hibernate.models.spi.ClassLoading;
+import org.hibernate.models.spi.SourceModelBuildingContext;
+
+import static org.hibernate.models.internal.SimpleClassLoading.SIMPLE_CLASS_LOADING;
+
+public class SourceModelTestHelper {
+
+ public static SourceModelBuildingContext createBuildingContext(Class> modelClass) {
+ return createBuildingContext( SIMPLE_CLASS_LOADING, modelClass );
+ }
+
+ public static SourceModelBuildingContext createBuildingContext(Class>... modelClasses) {
+ return createBuildingContext( SIMPLE_CLASS_LOADING, modelClasses );
+ }
+
+ public static SourceModelBuildingContext createBuildingContext(
+ ClassLoading classLoadingAccess,
+ Class>... modelClasses) {
+ final SourceModelBuildingContext ctx = new BasicModelBuildingContextImpl(
+ classLoadingAccess,
+ (contributions, buildingContext1) -> JpaAnnotations.forEachAnnotation( contributions::registerAnnotation )
+ );
+
+ if ( CollectionHelper.isNotEmpty( modelClasses ) ) {
+ for ( Class> modelClass : modelClasses ) {
+ ctx.getClassDetailsRegistry().resolveClassDetails( modelClass.getName() );
+ }
+ }
+
+ return ctx;
+ }
+
+}