diff --git a/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexAnnotationDescriptorRegistry.java b/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexAnnotationDescriptorRegistry.java index 468a132..7a51226 100644 --- a/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexAnnotationDescriptorRegistry.java +++ b/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexAnnotationDescriptorRegistry.java @@ -5,6 +5,7 @@ package org.hibernate.models.jandex.internal; import java.lang.annotation.Annotation; +import java.util.Map; import org.hibernate.models.internal.AnnotationDescriptorRegistryStandard; import org.hibernate.models.spi.AnnotationDescriptor; @@ -24,4 +25,8 @@ protected AnnotationDescriptor buildAnnotationDescript AnnotationDescriptor containerDescriptor) { return new JandexAnnotationDescriptorImpl<>( javaType, containerDescriptor, getModelBuildingContext() ); } + + public Map, AnnotationDescriptor> getDescriptorMap() { + return descriptorMap; + } } diff --git a/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexClassDetails.java b/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexClassDetails.java index 5f65db8..990cca4 100644 --- a/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexClassDetails.java +++ b/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexClassDetails.java @@ -9,7 +9,9 @@ import java.util.List; import org.hibernate.models.internal.ClassDetailsSupport; +import org.hibernate.models.internal.jdk.SerialJdkClassDetails; 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.FieldDetails; import org.hibernate.models.spi.MethodDetails; @@ -276,4 +278,9 @@ private static List determineTypeParameters(ClassInfo class } return result; } + + @Override + public SerialClassDetails toStorableForm() { + return new SerialJdkClassDetails( getName(), toJavaClass() ); + } } diff --git a/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexClassDetailsRegistry.java b/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexClassDetailsRegistry.java index 5f94013..0704b69 100644 --- a/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexClassDetailsRegistry.java +++ b/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexClassDetailsRegistry.java @@ -4,6 +4,8 @@ */ package org.hibernate.models.jandex.internal; +import java.util.Map; + import org.hibernate.models.UnknownClassException; import org.hibernate.models.internal.AbstractClassDetailsRegistry; import org.hibernate.models.internal.jdk.JdkBuilders; @@ -50,4 +52,8 @@ protected ClassDetails createClassDetails(String name) { throw new UnknownClassException( "Unable to resolve ClassDetails for `" + name + "`" ); } + + protected Map getClassDetailsMap() { + return classDetailsMap; + } } diff --git a/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexModelBuildingContextImpl.java b/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexModelBuildingContextImpl.java index dadb616..677e657 100644 --- a/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexModelBuildingContextImpl.java +++ b/hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexModelBuildingContextImpl.java @@ -13,6 +13,8 @@ import org.hibernate.models.jandex.spi.JandexModelBuildingContext; import org.hibernate.models.jandex.spi.JandexValueConverter; import org.hibernate.models.jandex.spi.JandexValueExtractor; +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; import org.hibernate.models.spi.ValueTypeDescriptor; @@ -22,6 +24,8 @@ import static org.hibernate.models.internal.ModelsClassLogging.MODELS_CLASS_LOGGER; /** + * SourceModelBuildingContext implementation based on Jandex + * * @author Steve Ebersole */ public class JandexModelBuildingContextImpl extends AbstractModelBuildingContext implements JandexModelBuildingContext { @@ -96,4 +100,9 @@ public JandexValueExtractor 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> descriptorMap; + protected final Map, AnnotationDescriptor> 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> annotationDescriptorEntry + : serialContext.getSerialAnnotationDescriptorMap().entrySet() ) { + final SerialAnnotationDescriptor 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> serialAnnotationDescriptorMap; + + public StorableContextImpl( + Map classDetailsMap, + Map, AnnotationDescriptor> 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> 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> 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; + } + +}