From 8f781f14cdbd49c61000ee4c3d27216adf1d7d7b Mon Sep 17 00:00:00 2001 From: evanchooly Date: Fri, 6 Mar 2026 10:11:18 -0500 Subject: [PATCH 1/5] Phase 4: Move critter-core sources into morphia-core - Move all Java source files from critter/core to core preserving package structure - Move all test files from critter/core to core - Add optional bytecode-gen deps to core/pom.xml (gizmo, asm-tree, asm-util, roaster-jdt) - Add morphia-annotation-node plugin execution to core/pom.xml - Refactor Generators from static singleton to instance class with constructor injection - Make CritterGizmoGenerator methods static, add generators parameter to generate() - Update GizmoEntityModelGenerator to accept MorphiaConfig in constructor - Update PropertyFinder to pass config from mapper to propertyModelGenerator() - Update CritterClassLoader to accept configurable parent classloader - Update CritterProcessor (critter-maven) to use new Generators instance Closes #4186 Co-Authored-By: Claude Sonnet 4.6 --- core/pom.xml | 33 +++++++ .../java/dev/morphia/critter/Critter.java | 0 .../morphia/critter/CritterClassLoader.java | 8 +- .../conventions/PropertyConvention.java | 0 .../critter/parser/ExtensionFunctions.java | 0 .../morphia/critter/parser/Generators.java | 26 ++---- .../critter/parser/PropertyFinder.java | 21 +++-- .../parser/asm/AddFieldAccessorMethods.java | 0 .../parser/asm/AddMethodAccessorMethods.java | 0 .../critter/parser/asm/BaseGenerator.java | 0 .../parser/gizmo/BaseGizmoGenerator.java | 0 .../parser/gizmo/CritterGizmoGenerator.java | 88 +++++++++++++++++++ .../gizmo/GizmoEntityModelGenerator.java | 12 +-- .../critter/parser/gizmo/GizmoExtensions.java | 0 .../gizmo/PropertyAccessorGenerator.java | 0 .../parser/gizmo/PropertyModelGenerator.java | 0 .../gizmo/VarHandleAccessorGenerator.java | 0 .../critter/parser/java/CritterParser.java | 0 .../dev/morphia/critter/ClassfileOutput.java | 0 .../critter/parser/BaseCritterTest.java | 0 .../morphia/critter/parser/GeneratorTest.java | 9 +- .../critter/parser/TestAccessorsMutators.java | 0 .../parser/TestEntityModelGenerator.java | 3 +- .../parser/TestPropertyModelGenerator.java | 0 .../critter/parser/TestVarHandleAccessor.java | 6 +- .../dev/morphia/critter/parser/TypesTest.java | 2 +- .../parser/gizmo/TestGizmoGeneration.java | 9 +- .../dev/morphia/critter/sources/Example.java | 0 .../sources/ExampleAgeAccessorTemplate.java | 0 .../ExampleAgePropertyModelTemplate.java | 0 .../sources/ExampleEntityModelTemplate.java | 0 .../sources/ExampleNameAccessorTemplate.java | 0 .../ExampleNamePropertyModelTemplate.java | 0 .../ExampleSalaryAccessorTemplate.java | 0 .../ExampleSalaryPropertyModelTemplate.java | 0 .../critter/sources/MethodExample.java | 0 .../parser/gizmo/CritterGizmoGenerator.java | 80 ----------------- .../morphia/critter/maven/CritterProcessor.kt | 18 +--- 38 files changed, 178 insertions(+), 137 deletions(-) rename {critter/core => core}/src/main/java/dev/morphia/critter/Critter.java (100%) rename {critter/core => core}/src/main/java/dev/morphia/critter/CritterClassLoader.java (93%) rename {critter/core => core}/src/main/java/dev/morphia/critter/conventions/PropertyConvention.java (100%) rename {critter/core => core}/src/main/java/dev/morphia/critter/parser/ExtensionFunctions.java (100%) rename {critter/core => core}/src/main/java/dev/morphia/critter/parser/Generators.java (79%) rename {critter/core => core}/src/main/java/dev/morphia/critter/parser/PropertyFinder.java (85%) rename {critter/core => core}/src/main/java/dev/morphia/critter/parser/asm/AddFieldAccessorMethods.java (100%) rename {critter/core => core}/src/main/java/dev/morphia/critter/parser/asm/AddMethodAccessorMethods.java (100%) rename {critter/core => core}/src/main/java/dev/morphia/critter/parser/asm/BaseGenerator.java (100%) rename {critter/core => core}/src/main/java/dev/morphia/critter/parser/gizmo/BaseGizmoGenerator.java (100%) create mode 100644 core/src/main/java/dev/morphia/critter/parser/gizmo/CritterGizmoGenerator.java rename {critter/core => core}/src/main/java/dev/morphia/critter/parser/gizmo/GizmoEntityModelGenerator.java (95%) rename {critter/core => core}/src/main/java/dev/morphia/critter/parser/gizmo/GizmoExtensions.java (100%) rename {critter/core => core}/src/main/java/dev/morphia/critter/parser/gizmo/PropertyAccessorGenerator.java (100%) rename {critter/core => core}/src/main/java/dev/morphia/critter/parser/gizmo/PropertyModelGenerator.java (100%) rename {critter/core => core}/src/main/java/dev/morphia/critter/parser/gizmo/VarHandleAccessorGenerator.java (100%) rename {critter/core => core}/src/main/java/dev/morphia/critter/parser/java/CritterParser.java (100%) rename {critter/core => core}/src/test/java/dev/morphia/critter/ClassfileOutput.java (100%) rename {critter/core => core}/src/test/java/dev/morphia/critter/parser/BaseCritterTest.java (100%) rename {critter/core => core}/src/test/java/dev/morphia/critter/parser/GeneratorTest.java (85%) rename {critter/core => core}/src/test/java/dev/morphia/critter/parser/TestAccessorsMutators.java (100%) rename {critter/core => core}/src/test/java/dev/morphia/critter/parser/TestEntityModelGenerator.java (93%) rename {critter/core => core}/src/test/java/dev/morphia/critter/parser/TestPropertyModelGenerator.java (100%) rename {critter/core => core}/src/test/java/dev/morphia/critter/parser/TestVarHandleAccessor.java (91%) rename {critter/core => core}/src/test/java/dev/morphia/critter/parser/TypesTest.java (96%) rename {critter/core => core}/src/test/java/dev/morphia/critter/parser/gizmo/TestGizmoGeneration.java (97%) rename {critter/core => core}/src/test/java/dev/morphia/critter/sources/Example.java (100%) rename {critter/core => core}/src/test/java/dev/morphia/critter/sources/ExampleAgeAccessorTemplate.java (100%) rename {critter/core => core}/src/test/java/dev/morphia/critter/sources/ExampleAgePropertyModelTemplate.java (100%) rename {critter/core => core}/src/test/java/dev/morphia/critter/sources/ExampleEntityModelTemplate.java (100%) rename {critter/core => core}/src/test/java/dev/morphia/critter/sources/ExampleNameAccessorTemplate.java (100%) rename {critter/core => core}/src/test/java/dev/morphia/critter/sources/ExampleNamePropertyModelTemplate.java (100%) rename {critter/core => core}/src/test/java/dev/morphia/critter/sources/ExampleSalaryAccessorTemplate.java (100%) rename {critter/core => core}/src/test/java/dev/morphia/critter/sources/ExampleSalaryPropertyModelTemplate.java (100%) rename {critter/core => core}/src/test/java/dev/morphia/critter/sources/MethodExample.java (100%) delete mode 100644 critter/core/src/main/java/dev/morphia/critter/parser/gizmo/CritterGizmoGenerator.java diff --git a/core/pom.xml b/core/pom.xml index 9f1e5686365..d5728b89bda 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -128,6 +128,12 @@ generate-test-resources + + morphia-annotations-asm + + morphia-annotation-node + + @@ -171,6 +177,27 @@ net.bytebuddy byte-buddy + + io.quarkus.gizmo + gizmo + 1.9.0 + true + + + org.ow2.asm + asm-tree + true + + + org.ow2.asm + asm-util + true + + + org.jboss.forge.roaster + roaster-jdt + true + org.slf4j slf4j-api @@ -180,6 +207,12 @@ spotbugs-annotations + + org.jboss.windup.decompiler + decompiler-fernflower + 6.3.9.Final + test + org.testng testng diff --git a/critter/core/src/main/java/dev/morphia/critter/Critter.java b/core/src/main/java/dev/morphia/critter/Critter.java similarity index 100% rename from critter/core/src/main/java/dev/morphia/critter/Critter.java rename to core/src/main/java/dev/morphia/critter/Critter.java diff --git a/critter/core/src/main/java/dev/morphia/critter/CritterClassLoader.java b/core/src/main/java/dev/morphia/critter/CritterClassLoader.java similarity index 93% rename from critter/core/src/main/java/dev/morphia/critter/CritterClassLoader.java rename to core/src/main/java/dev/morphia/critter/CritterClassLoader.java index b9ae8375383..94f34c1ecbd 100644 --- a/critter/core/src/main/java/dev/morphia/critter/CritterClassLoader.java +++ b/core/src/main/java/dev/morphia/critter/CritterClassLoader.java @@ -4,14 +4,16 @@ import java.util.HashMap; import java.util.Map; -import dev.morphia.mapping.codec.pojo.PropertyModel; - import net.bytebuddy.dynamic.loading.ByteArrayClassLoader; public class CritterClassLoader extends ByteArrayClassLoader.ChildFirst { + public CritterClassLoader(ClassLoader parent) { + super(parent, Collections.emptyMap()); + } + public CritterClassLoader() { - super(PropertyModel.class.getClassLoader(), Collections.emptyMap()); + this(Thread.currentThread().getContextClassLoader()); } public void register(String name, byte[] bytes) { diff --git a/critter/core/src/main/java/dev/morphia/critter/conventions/PropertyConvention.java b/core/src/main/java/dev/morphia/critter/conventions/PropertyConvention.java similarity index 100% rename from critter/core/src/main/java/dev/morphia/critter/conventions/PropertyConvention.java rename to core/src/main/java/dev/morphia/critter/conventions/PropertyConvention.java diff --git a/critter/core/src/main/java/dev/morphia/critter/parser/ExtensionFunctions.java b/core/src/main/java/dev/morphia/critter/parser/ExtensionFunctions.java similarity index 100% rename from critter/core/src/main/java/dev/morphia/critter/parser/ExtensionFunctions.java rename to core/src/main/java/dev/morphia/critter/parser/ExtensionFunctions.java diff --git a/critter/core/src/main/java/dev/morphia/critter/parser/Generators.java b/core/src/main/java/dev/morphia/critter/parser/Generators.java similarity index 79% rename from critter/core/src/main/java/dev/morphia/critter/parser/Generators.java rename to core/src/main/java/dev/morphia/critter/parser/Generators.java index 08771cd5c88..673be951d6b 100644 --- a/critter/core/src/main/java/dev/morphia/critter/parser/Generators.java +++ b/core/src/main/java/dev/morphia/critter/parser/Generators.java @@ -1,9 +1,7 @@ package dev.morphia.critter.parser; import dev.morphia.config.MorphiaConfig; -import dev.morphia.config.MorphiaConfigHelper; import dev.morphia.mapping.Mapper; -import dev.morphia.mapping.ReflectiveMapper; import dev.morphia.mapping.conventions.MorphiaDefaultsConvention; import org.objectweb.asm.Type; @@ -11,28 +9,20 @@ import static org.objectweb.asm.Type.ARRAY; public class Generators { - public static final Generators INSTANCE = new Generators(); + private final MorphiaConfig config; + private final Mapper mapper; + public final MorphiaDefaultsConvention convention = new MorphiaDefaultsConvention(); - public String configFile = MorphiaConfigHelper.MORPHIA_CONFIG_PROPERTIES; - - private MorphiaConfig config; - private Mapper mapper; - public MorphiaDefaultsConvention convention = new MorphiaDefaultsConvention(); - - private Generators() { + public Generators(MorphiaConfig config, Mapper mapper) { + this.config = config; + this.mapper = mapper; } - public synchronized MorphiaConfig getConfig() { - if (config == null) { - config = MorphiaConfig.load(configFile); - } + public MorphiaConfig getConfig() { return config; } - public synchronized Mapper getMapper() { - if (mapper == null) { - mapper = new ReflectiveMapper(getConfig()); - } + public Mapper getMapper() { return mapper; } diff --git a/critter/core/src/main/java/dev/morphia/critter/parser/PropertyFinder.java b/core/src/main/java/dev/morphia/critter/parser/PropertyFinder.java similarity index 85% rename from critter/core/src/main/java/dev/morphia/critter/parser/PropertyFinder.java rename to core/src/main/java/dev/morphia/critter/parser/PropertyFinder.java index 4f0595391c6..7daa510f3e6 100644 --- a/critter/core/src/main/java/dev/morphia/critter/parser/PropertyFinder.java +++ b/core/src/main/java/dev/morphia/critter/parser/PropertyFinder.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; +import dev.morphia.config.MorphiaConfig; import dev.morphia.critter.CritterClassLoader; import dev.morphia.critter.parser.gizmo.CritterGizmoGenerator; import dev.morphia.critter.parser.gizmo.PropertyModelGenerator; @@ -22,10 +23,12 @@ public class PropertyFinder { private final Map, Object> providerMap; + private final Mapper mapper; private final CritterClassLoader classLoader; private final boolean runtimeMode; public PropertyFinder(Mapper mapper, CritterClassLoader classLoader, boolean runtimeMode) { + this.mapper = mapper; this.providerMap = new LinkedHashMap<>(); for (var provider : mapper.getConfig().propertyAnnotationProviders()) { providerMap.put(provider.provides(), provider); @@ -34,33 +37,33 @@ public PropertyFinder(Mapper mapper, CritterClassLoader classLoader, boolean run this.runtimeMode = runtimeMode; } - public List find(Class entityType, ClassNode classNode) { + public List find(Class entityType, ClassNode classNode, MorphiaConfig config) { List models = new ArrayList<>(); List methods = discoverPropertyMethods(classNode); if (methods.isEmpty()) { List fields = discoverAllFields(entityType, classNode); if (!runtimeMode) { - classLoader.register(entityType.getName(), CritterGizmoGenerator.INSTANCE.fieldAccessors(entityType, fields)); + classLoader.register(entityType.getName(), CritterGizmoGenerator.fieldAccessors(entityType, fields)); } for (FieldNode field : fields) { if (runtimeMode) { - CritterGizmoGenerator.INSTANCE.varHandleAccessor(entityType, classLoader, field); + CritterGizmoGenerator.varHandleAccessor(entityType, classLoader, field); } else { - CritterGizmoGenerator.INSTANCE.propertyAccessor(entityType, classLoader, field); + CritterGizmoGenerator.propertyAccessor(entityType, classLoader, field); } - models.add(CritterGizmoGenerator.INSTANCE.propertyModelGenerator(entityType, classLoader, field)); + models.add(CritterGizmoGenerator.propertyModelGenerator(config, entityType, classLoader, field)); } } else { if (!runtimeMode) { - classLoader.register(entityType.getName(), CritterGizmoGenerator.INSTANCE.methodAccessors(entityType, methods)); + classLoader.register(entityType.getName(), CritterGizmoGenerator.methodAccessors(entityType, methods)); } for (MethodNode method : methods) { if (runtimeMode) { - CritterGizmoGenerator.INSTANCE.varHandleAccessor(entityType, classLoader, method); + CritterGizmoGenerator.varHandleAccessor(entityType, classLoader, method); } else { - CritterGizmoGenerator.INSTANCE.propertyAccessor(entityType, classLoader, method); + CritterGizmoGenerator.propertyAccessor(entityType, classLoader, method); } - models.add(CritterGizmoGenerator.INSTANCE.propertyModelGenerator(entityType, classLoader, method)); + models.add(CritterGizmoGenerator.propertyModelGenerator(config, entityType, classLoader, method)); } } return models; diff --git a/critter/core/src/main/java/dev/morphia/critter/parser/asm/AddFieldAccessorMethods.java b/core/src/main/java/dev/morphia/critter/parser/asm/AddFieldAccessorMethods.java similarity index 100% rename from critter/core/src/main/java/dev/morphia/critter/parser/asm/AddFieldAccessorMethods.java rename to core/src/main/java/dev/morphia/critter/parser/asm/AddFieldAccessorMethods.java diff --git a/critter/core/src/main/java/dev/morphia/critter/parser/asm/AddMethodAccessorMethods.java b/core/src/main/java/dev/morphia/critter/parser/asm/AddMethodAccessorMethods.java similarity index 100% rename from critter/core/src/main/java/dev/morphia/critter/parser/asm/AddMethodAccessorMethods.java rename to core/src/main/java/dev/morphia/critter/parser/asm/AddMethodAccessorMethods.java diff --git a/critter/core/src/main/java/dev/morphia/critter/parser/asm/BaseGenerator.java b/core/src/main/java/dev/morphia/critter/parser/asm/BaseGenerator.java similarity index 100% rename from critter/core/src/main/java/dev/morphia/critter/parser/asm/BaseGenerator.java rename to core/src/main/java/dev/morphia/critter/parser/asm/BaseGenerator.java diff --git a/critter/core/src/main/java/dev/morphia/critter/parser/gizmo/BaseGizmoGenerator.java b/core/src/main/java/dev/morphia/critter/parser/gizmo/BaseGizmoGenerator.java similarity index 100% rename from critter/core/src/main/java/dev/morphia/critter/parser/gizmo/BaseGizmoGenerator.java rename to core/src/main/java/dev/morphia/critter/parser/gizmo/BaseGizmoGenerator.java diff --git a/core/src/main/java/dev/morphia/critter/parser/gizmo/CritterGizmoGenerator.java b/core/src/main/java/dev/morphia/critter/parser/gizmo/CritterGizmoGenerator.java new file mode 100644 index 00000000000..1f6082cebae --- /dev/null +++ b/core/src/main/java/dev/morphia/critter/parser/gizmo/CritterGizmoGenerator.java @@ -0,0 +1,88 @@ +package dev.morphia.critter.parser.gizmo; + +import java.io.IOException; +import java.util.List; + +import dev.morphia.config.MorphiaConfig; +import dev.morphia.critter.CritterClassLoader; +import dev.morphia.critter.parser.Generators; +import dev.morphia.critter.parser.PropertyFinder; +import dev.morphia.critter.parser.asm.AddFieldAccessorMethods; +import dev.morphia.critter.parser.asm.AddMethodAccessorMethods; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodNode; + +public class CritterGizmoGenerator { + private CritterGizmoGenerator() { + } + + public static GizmoEntityModelGenerator generate(Class type, CritterClassLoader critterClassLoader, + Generators generators, boolean runtimeMode) { + ClassNode classNode = new ClassNode(); + String resourceName = "%s.class".formatted(type.getName().replace('.', '/')); + java.io.InputStream inputStream = type.getClassLoader().getResourceAsStream(resourceName); + if (inputStream == null) { + throw new IllegalArgumentException("Could not find class file for %s".formatted(type.getName())); + } + try { + new ClassReader(inputStream).accept(classNode, 0); + } catch (IOException e) { + throw new RuntimeException("Failed to read class %s".formatted(type.getName()), e); + } + PropertyFinder propertyFinder = new PropertyFinder(generators.getMapper(), critterClassLoader, runtimeMode); + + return entityModel(type, critterClassLoader, classNode, propertyFinder.find(type, classNode, generators.getConfig()), + generators.getConfig()); + } + + public static GizmoEntityModelGenerator generate(Class type, CritterClassLoader critterClassLoader, + Generators generators) { + return generate(type, critterClassLoader, generators, false); + } + + public static byte[] fieldAccessors(Class entityType, List fields) { + return new AddFieldAccessorMethods(entityType, fields).emit(); + } + + public static byte[] methodAccessors(Class entityType, List methods) { + return new AddMethodAccessorMethods(entityType, methods).emit(); + } + + public static PropertyAccessorGenerator propertyAccessor(Class entityType, CritterClassLoader critterClassLoader, + FieldNode field) { + return new PropertyAccessorGenerator(entityType, critterClassLoader, field).emit(); + } + + public static PropertyAccessorGenerator propertyAccessor(Class entityType, CritterClassLoader critterClassLoader, + MethodNode method) { + return new PropertyAccessorGenerator(entityType, critterClassLoader, method).emit(); + } + + public static VarHandleAccessorGenerator varHandleAccessor(Class entityType, CritterClassLoader critterClassLoader, + FieldNode field) { + return new VarHandleAccessorGenerator(entityType, critterClassLoader, field).emit(); + } + + public static VarHandleAccessorGenerator varHandleAccessor(Class entityType, CritterClassLoader critterClassLoader, + MethodNode method) { + return new VarHandleAccessorGenerator(entityType, critterClassLoader, method).emit(); + } + + public static PropertyModelGenerator propertyModelGenerator(MorphiaConfig config, Class entityType, + CritterClassLoader critterClassLoader, FieldNode field) { + return new PropertyModelGenerator(config, entityType, critterClassLoader, field).emit(); + } + + public static PropertyModelGenerator propertyModelGenerator(MorphiaConfig config, Class entityType, + CritterClassLoader critterClassLoader, MethodNode method) { + return new PropertyModelGenerator(config, entityType, critterClassLoader, method).emit(); + } + + public static GizmoEntityModelGenerator entityModel(Class type, CritterClassLoader critterClassLoader, + ClassNode classNode, List properties, MorphiaConfig config) { + return new GizmoEntityModelGenerator(type, critterClassLoader, classNode, properties, config).emit(); + } +} diff --git a/critter/core/src/main/java/dev/morphia/critter/parser/gizmo/GizmoEntityModelGenerator.java b/core/src/main/java/dev/morphia/critter/parser/gizmo/GizmoEntityModelGenerator.java similarity index 95% rename from critter/core/src/main/java/dev/morphia/critter/parser/gizmo/GizmoEntityModelGenerator.java rename to core/src/main/java/dev/morphia/critter/parser/gizmo/GizmoEntityModelGenerator.java index c0f4cb58926..6419d95102a 100644 --- a/critter/core/src/main/java/dev/morphia/critter/parser/gizmo/GizmoEntityModelGenerator.java +++ b/core/src/main/java/dev/morphia/critter/parser/gizmo/GizmoEntityModelGenerator.java @@ -8,8 +8,8 @@ import dev.morphia.annotations.Entity; import dev.morphia.annotations.internal.AnnotationNodeExtensions; +import dev.morphia.config.MorphiaConfig; import dev.morphia.critter.CritterClassLoader; -import dev.morphia.critter.parser.Generators; import dev.morphia.mapping.Mapper; import dev.morphia.mapping.codec.pojo.EntityModel; import dev.morphia.mapping.codec.pojo.PropertyModel; @@ -29,10 +29,12 @@ public class GizmoEntityModelGenerator extends BaseGizmoGenerator { private final List annotations; private final List morphiaAnnotations; private final Entity entityAnnotation; + private final MorphiaConfig config; public GizmoEntityModelGenerator(Class type, CritterClassLoader critterClassLoader, - ClassNode classNode, List properties) { + ClassNode classNode, List properties, MorphiaConfig config) { super(type, critterClassLoader); + this.config = config; this.classNode = classNode; this.properties = properties; @@ -108,7 +110,7 @@ private void discriminatorKey() { try (MethodCreator mc = getCreator().getMethodCreator("discriminatorKey", String.class)) { String key = entityAnnotation.discriminator(); String result = Mapper.IGNORED_FIELDNAME.equals(key) - ? Generators.INSTANCE.getConfig().discriminatorKey() + ? config.discriminatorKey() : key; mc.returnValue(mc.load(result)); } @@ -116,7 +118,7 @@ private void discriminatorKey() { private void discriminator() { try (MethodCreator mc = getCreator().getMethodCreator("discriminator", String.class)) { - String discriminator = Generators.INSTANCE.getConfig().discriminator() + String discriminator = config.discriminator() .apply(entity, entityAnnotation.discriminator()); mc.returnValue(mc.load(discriminator)); } @@ -126,7 +128,7 @@ private void collectionName() { try (MethodCreator mc = getCreator().getMethodCreator("collectionName", String.class)) { String key = entityAnnotation.value(); String result = Mapper.IGNORED_FIELDNAME.equals(key) - ? Generators.INSTANCE.getConfig().collectionNaming().apply(entity.getSimpleName()) + ? config.collectionNaming().apply(entity.getSimpleName()) : key; mc.returnValue(mc.load(result)); } diff --git a/critter/core/src/main/java/dev/morphia/critter/parser/gizmo/GizmoExtensions.java b/core/src/main/java/dev/morphia/critter/parser/gizmo/GizmoExtensions.java similarity index 100% rename from critter/core/src/main/java/dev/morphia/critter/parser/gizmo/GizmoExtensions.java rename to core/src/main/java/dev/morphia/critter/parser/gizmo/GizmoExtensions.java diff --git a/critter/core/src/main/java/dev/morphia/critter/parser/gizmo/PropertyAccessorGenerator.java b/core/src/main/java/dev/morphia/critter/parser/gizmo/PropertyAccessorGenerator.java similarity index 100% rename from critter/core/src/main/java/dev/morphia/critter/parser/gizmo/PropertyAccessorGenerator.java rename to core/src/main/java/dev/morphia/critter/parser/gizmo/PropertyAccessorGenerator.java diff --git a/critter/core/src/main/java/dev/morphia/critter/parser/gizmo/PropertyModelGenerator.java b/core/src/main/java/dev/morphia/critter/parser/gizmo/PropertyModelGenerator.java similarity index 100% rename from critter/core/src/main/java/dev/morphia/critter/parser/gizmo/PropertyModelGenerator.java rename to core/src/main/java/dev/morphia/critter/parser/gizmo/PropertyModelGenerator.java diff --git a/critter/core/src/main/java/dev/morphia/critter/parser/gizmo/VarHandleAccessorGenerator.java b/core/src/main/java/dev/morphia/critter/parser/gizmo/VarHandleAccessorGenerator.java similarity index 100% rename from critter/core/src/main/java/dev/morphia/critter/parser/gizmo/VarHandleAccessorGenerator.java rename to core/src/main/java/dev/morphia/critter/parser/gizmo/VarHandleAccessorGenerator.java diff --git a/critter/core/src/main/java/dev/morphia/critter/parser/java/CritterParser.java b/core/src/main/java/dev/morphia/critter/parser/java/CritterParser.java similarity index 100% rename from critter/core/src/main/java/dev/morphia/critter/parser/java/CritterParser.java rename to core/src/main/java/dev/morphia/critter/parser/java/CritterParser.java diff --git a/critter/core/src/test/java/dev/morphia/critter/ClassfileOutput.java b/core/src/test/java/dev/morphia/critter/ClassfileOutput.java similarity index 100% rename from critter/core/src/test/java/dev/morphia/critter/ClassfileOutput.java rename to core/src/test/java/dev/morphia/critter/ClassfileOutput.java diff --git a/critter/core/src/test/java/dev/morphia/critter/parser/BaseCritterTest.java b/core/src/test/java/dev/morphia/critter/parser/BaseCritterTest.java similarity index 100% rename from critter/core/src/test/java/dev/morphia/critter/parser/BaseCritterTest.java rename to core/src/test/java/dev/morphia/critter/parser/BaseCritterTest.java diff --git a/critter/core/src/test/java/dev/morphia/critter/parser/GeneratorTest.java b/core/src/test/java/dev/morphia/critter/parser/GeneratorTest.java similarity index 85% rename from critter/core/src/test/java/dev/morphia/critter/parser/GeneratorTest.java rename to core/src/test/java/dev/morphia/critter/parser/GeneratorTest.java index 846fbabbd5a..60de63c366a 100644 --- a/critter/core/src/test/java/dev/morphia/critter/parser/GeneratorTest.java +++ b/core/src/test/java/dev/morphia/critter/parser/GeneratorTest.java @@ -9,11 +9,14 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +import dev.morphia.config.ManualMorphiaConfig; +import dev.morphia.config.MorphiaConfig; import dev.morphia.critter.ClassfileOutput; import dev.morphia.critter.CritterClassLoader; import dev.morphia.critter.parser.gizmo.CritterGizmoGenerator; import dev.morphia.critter.parser.gizmo.GizmoEntityModelGenerator; import dev.morphia.critter.sources.Example; +import dev.morphia.mapping.ReflectiveMapper; import dev.morphia.mapping.codec.pojo.critter.CritterEntityModel; import io.github.classgraph.ClassGraph; @@ -38,12 +41,14 @@ public class GeneratorTest { } catch (Exception ignored) { } - GizmoEntityModelGenerator gen = CritterGizmoGenerator.INSTANCE.generate(Example.class, critterClassLoader, false); + MorphiaConfig config = new ManualMorphiaConfig(); + Generators generators = new Generators(config, new ReflectiveMapper(config)); + GizmoEntityModelGenerator gen = CritterGizmoGenerator.generate(Example.class, critterClassLoader, generators, false); try { entityModel = (CritterEntityModel) critterClassLoader .loadClass(gen.getGeneratedType()) .getConstructors()[0] - .newInstance(Generators.INSTANCE.getMapper()); + .newInstance(generators.getMapper()); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/critter/core/src/test/java/dev/morphia/critter/parser/TestAccessorsMutators.java b/core/src/test/java/dev/morphia/critter/parser/TestAccessorsMutators.java similarity index 100% rename from critter/core/src/test/java/dev/morphia/critter/parser/TestAccessorsMutators.java rename to core/src/test/java/dev/morphia/critter/parser/TestAccessorsMutators.java diff --git a/critter/core/src/test/java/dev/morphia/critter/parser/TestEntityModelGenerator.java b/core/src/test/java/dev/morphia/critter/parser/TestEntityModelGenerator.java similarity index 93% rename from critter/core/src/test/java/dev/morphia/critter/parser/TestEntityModelGenerator.java rename to core/src/test/java/dev/morphia/critter/parser/TestEntityModelGenerator.java index 2f56301ea7b..1d8e90fdc89 100644 --- a/critter/core/src/test/java/dev/morphia/critter/parser/TestEntityModelGenerator.java +++ b/core/src/test/java/dev/morphia/critter/parser/TestEntityModelGenerator.java @@ -2,6 +2,7 @@ import java.lang.reflect.Method; +import dev.morphia.config.ManualMorphiaConfig; import dev.morphia.critter.ClassfileOutput; import dev.morphia.critter.CritterClassLoader; import dev.morphia.mapping.Mapper; @@ -18,7 +19,7 @@ public class TestEntityModelGenerator { private static final Logger LOG = LoggerFactory.getLogger(TestEntityModelGenerator.class); private final CritterEntityModel control; - private final Mapper mapper = new ReflectiveMapper(Generators.INSTANCE.getConfig()); + private final Mapper mapper = new ReflectiveMapper(new ManualMorphiaConfig()); private final CritterClassLoader critterClassLoader = new CritterClassLoader(); public TestEntityModelGenerator() { diff --git a/critter/core/src/test/java/dev/morphia/critter/parser/TestPropertyModelGenerator.java b/core/src/test/java/dev/morphia/critter/parser/TestPropertyModelGenerator.java similarity index 100% rename from critter/core/src/test/java/dev/morphia/critter/parser/TestPropertyModelGenerator.java rename to core/src/test/java/dev/morphia/critter/parser/TestPropertyModelGenerator.java diff --git a/critter/core/src/test/java/dev/morphia/critter/parser/TestVarHandleAccessor.java b/core/src/test/java/dev/morphia/critter/parser/TestVarHandleAccessor.java similarity index 91% rename from critter/core/src/test/java/dev/morphia/critter/parser/TestVarHandleAccessor.java rename to core/src/test/java/dev/morphia/critter/parser/TestVarHandleAccessor.java index 5dbf0349c1c..6b2d82ec049 100644 --- a/critter/core/src/test/java/dev/morphia/critter/parser/TestVarHandleAccessor.java +++ b/core/src/test/java/dev/morphia/critter/parser/TestVarHandleAccessor.java @@ -4,10 +4,13 @@ import java.util.List; import java.util.stream.Collectors; +import dev.morphia.config.ManualMorphiaConfig; +import dev.morphia.config.MorphiaConfig; import dev.morphia.critter.Critter; import dev.morphia.critter.CritterClassLoader; import dev.morphia.critter.parser.gizmo.CritterGizmoGenerator; import dev.morphia.critter.sources.Example; +import dev.morphia.mapping.ReflectiveMapper; import org.bson.codecs.pojo.PropertyAccessor; import org.testng.Assert; @@ -20,7 +23,8 @@ public class TestVarHandleAccessor { @BeforeClass public void setup() { classLoader = new CritterClassLoader(); - CritterGizmoGenerator.INSTANCE.generate(Example.class, classLoader, true); + MorphiaConfig config = new ManualMorphiaConfig(); + CritterGizmoGenerator.generate(Example.class, classLoader, new Generators(config, new ReflectiveMapper(config)), true); } @Test diff --git a/critter/core/src/test/java/dev/morphia/critter/parser/TypesTest.java b/core/src/test/java/dev/morphia/critter/parser/TypesTest.java similarity index 96% rename from critter/core/src/test/java/dev/morphia/critter/parser/TypesTest.java rename to core/src/test/java/dev/morphia/critter/parser/TypesTest.java index 58d991c1951..b1b9a412409 100644 --- a/critter/core/src/test/java/dev/morphia/critter/parser/TypesTest.java +++ b/core/src/test/java/dev/morphia/critter/parser/TypesTest.java @@ -81,7 +81,7 @@ public Object[][] typeProvider() { @Test(dataProvider = "types") public void asClassConversion(Class expected) { Type type = Type.getType(expected); - Class actual = Generators.INSTANCE.asClass(type, Thread.currentThread().getContextClassLoader()); + Class actual = Generators.asClass(type, Thread.currentThread().getContextClassLoader()); Assert.assertEquals(actual, expected, "Type " + type.getDescriptor() + " should convert to " + expected.getName()); } } diff --git a/critter/core/src/test/java/dev/morphia/critter/parser/gizmo/TestGizmoGeneration.java b/core/src/test/java/dev/morphia/critter/parser/gizmo/TestGizmoGeneration.java similarity index 97% rename from critter/core/src/test/java/dev/morphia/critter/parser/gizmo/TestGizmoGeneration.java rename to core/src/test/java/dev/morphia/critter/parser/gizmo/TestGizmoGeneration.java index cddb2678738..8896d9c2913 100644 --- a/critter/core/src/test/java/dev/morphia/critter/parser/gizmo/TestGizmoGeneration.java +++ b/core/src/test/java/dev/morphia/critter/parser/gizmo/TestGizmoGeneration.java @@ -19,12 +19,15 @@ import dev.morphia.annotations.internal.IndexBuilder; import dev.morphia.annotations.internal.IndexOptionsBuilder; import dev.morphia.annotations.internal.IndexesBuilder; +import dev.morphia.config.ManualMorphiaConfig; +import dev.morphia.config.MorphiaConfig; import dev.morphia.critter.ClassfileOutput; import dev.morphia.critter.CritterClassLoader; import dev.morphia.critter.parser.Generators; import dev.morphia.critter.parser.asm.AddMethodAccessorMethods; import dev.morphia.critter.sources.Example; import dev.morphia.critter.sources.MethodExample; +import dev.morphia.mapping.ReflectiveMapper; import dev.morphia.mapping.codec.pojo.EntityModel; import dev.morphia.mapping.codec.pojo.PropertyModel; import dev.morphia.mapping.codec.pojo.TypeData; @@ -135,7 +138,9 @@ public void testAnnotationBuilding() throws Exception { @Test public void testGizmo() throws Exception { - CritterGizmoGenerator.INSTANCE.generate(Example.class, critterClassLoader, false); + MorphiaConfig config = new ManualMorphiaConfig(); + Generators generators = new Generators(config, new ReflectiveMapper(config)); + CritterGizmoGenerator.generate(Example.class, critterClassLoader, generators, false); critterClassLoader.loadClass("dev.morphia.critter.sources.__morphia.example.AgeModel"); Class nameModel = critterClassLoader.loadClass("dev.morphia.critter.sources.__morphia.example.NameModel"); invokeAll(PropertyModel.class, nameModel); @@ -145,7 +150,7 @@ public void testGizmo() throws Exception { critterClassLoader.loadClass("dev.morphia.critter.sources.__morphia.example.SalaryAccessor").getConstructor().newInstance(); Class loadClass = critterClassLoader.loadClass("dev.morphia.critter.sources.__morphia.example.ExampleEntityModel"); - EntityModel model = (EntityModel) loadClass.getConstructors()[0].newInstance(Generators.INSTANCE.getMapper()); + EntityModel model = (EntityModel) loadClass.getConstructors()[0].newInstance(generators.getMapper()); validate(model); } diff --git a/critter/core/src/test/java/dev/morphia/critter/sources/Example.java b/core/src/test/java/dev/morphia/critter/sources/Example.java similarity index 100% rename from critter/core/src/test/java/dev/morphia/critter/sources/Example.java rename to core/src/test/java/dev/morphia/critter/sources/Example.java diff --git a/critter/core/src/test/java/dev/morphia/critter/sources/ExampleAgeAccessorTemplate.java b/core/src/test/java/dev/morphia/critter/sources/ExampleAgeAccessorTemplate.java similarity index 100% rename from critter/core/src/test/java/dev/morphia/critter/sources/ExampleAgeAccessorTemplate.java rename to core/src/test/java/dev/morphia/critter/sources/ExampleAgeAccessorTemplate.java diff --git a/critter/core/src/test/java/dev/morphia/critter/sources/ExampleAgePropertyModelTemplate.java b/core/src/test/java/dev/morphia/critter/sources/ExampleAgePropertyModelTemplate.java similarity index 100% rename from critter/core/src/test/java/dev/morphia/critter/sources/ExampleAgePropertyModelTemplate.java rename to core/src/test/java/dev/morphia/critter/sources/ExampleAgePropertyModelTemplate.java diff --git a/critter/core/src/test/java/dev/morphia/critter/sources/ExampleEntityModelTemplate.java b/core/src/test/java/dev/morphia/critter/sources/ExampleEntityModelTemplate.java similarity index 100% rename from critter/core/src/test/java/dev/morphia/critter/sources/ExampleEntityModelTemplate.java rename to core/src/test/java/dev/morphia/critter/sources/ExampleEntityModelTemplate.java diff --git a/critter/core/src/test/java/dev/morphia/critter/sources/ExampleNameAccessorTemplate.java b/core/src/test/java/dev/morphia/critter/sources/ExampleNameAccessorTemplate.java similarity index 100% rename from critter/core/src/test/java/dev/morphia/critter/sources/ExampleNameAccessorTemplate.java rename to core/src/test/java/dev/morphia/critter/sources/ExampleNameAccessorTemplate.java diff --git a/critter/core/src/test/java/dev/morphia/critter/sources/ExampleNamePropertyModelTemplate.java b/core/src/test/java/dev/morphia/critter/sources/ExampleNamePropertyModelTemplate.java similarity index 100% rename from critter/core/src/test/java/dev/morphia/critter/sources/ExampleNamePropertyModelTemplate.java rename to core/src/test/java/dev/morphia/critter/sources/ExampleNamePropertyModelTemplate.java diff --git a/critter/core/src/test/java/dev/morphia/critter/sources/ExampleSalaryAccessorTemplate.java b/core/src/test/java/dev/morphia/critter/sources/ExampleSalaryAccessorTemplate.java similarity index 100% rename from critter/core/src/test/java/dev/morphia/critter/sources/ExampleSalaryAccessorTemplate.java rename to core/src/test/java/dev/morphia/critter/sources/ExampleSalaryAccessorTemplate.java diff --git a/critter/core/src/test/java/dev/morphia/critter/sources/ExampleSalaryPropertyModelTemplate.java b/core/src/test/java/dev/morphia/critter/sources/ExampleSalaryPropertyModelTemplate.java similarity index 100% rename from critter/core/src/test/java/dev/morphia/critter/sources/ExampleSalaryPropertyModelTemplate.java rename to core/src/test/java/dev/morphia/critter/sources/ExampleSalaryPropertyModelTemplate.java diff --git a/critter/core/src/test/java/dev/morphia/critter/sources/MethodExample.java b/core/src/test/java/dev/morphia/critter/sources/MethodExample.java similarity index 100% rename from critter/core/src/test/java/dev/morphia/critter/sources/MethodExample.java rename to core/src/test/java/dev/morphia/critter/sources/MethodExample.java diff --git a/critter/core/src/main/java/dev/morphia/critter/parser/gizmo/CritterGizmoGenerator.java b/critter/core/src/main/java/dev/morphia/critter/parser/gizmo/CritterGizmoGenerator.java deleted file mode 100644 index 7c2174d9bac..00000000000 --- a/critter/core/src/main/java/dev/morphia/critter/parser/gizmo/CritterGizmoGenerator.java +++ /dev/null @@ -1,80 +0,0 @@ -package dev.morphia.critter.parser.gizmo; - -import java.io.IOException; -import java.util.List; - -import dev.morphia.critter.CritterClassLoader; -import dev.morphia.critter.parser.Generators; -import dev.morphia.critter.parser.PropertyFinder; -import dev.morphia.critter.parser.asm.AddFieldAccessorMethods; -import dev.morphia.critter.parser.asm.AddMethodAccessorMethods; - -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.FieldNode; -import org.objectweb.asm.tree.MethodNode; - -public class CritterGizmoGenerator { - public static final CritterGizmoGenerator INSTANCE = new CritterGizmoGenerator(); - - private CritterGizmoGenerator() { - } - - public GizmoEntityModelGenerator generate(Class type, CritterClassLoader critterClassLoader, boolean runtimeMode) { - ClassNode classNode = new ClassNode(); - String resourceName = "%s.class".formatted(type.getName().replace('.', '/')); - java.io.InputStream inputStream = type.getClassLoader().getResourceAsStream(resourceName); - if (inputStream == null) { - throw new IllegalArgumentException("Could not find class file for %s".formatted(type.getName())); - } - try { - new ClassReader(inputStream).accept(classNode, 0); - } catch (IOException e) { - throw new RuntimeException("Failed to read class %s".formatted(type.getName()), e); - } - PropertyFinder propertyFinder = new PropertyFinder(Generators.INSTANCE.getMapper(), critterClassLoader, runtimeMode); - - return entityModel(type, critterClassLoader, classNode, propertyFinder.find(type, classNode)); - } - - public GizmoEntityModelGenerator generate(Class type, CritterClassLoader critterClassLoader) { - return generate(type, critterClassLoader, false); - } - - public byte[] fieldAccessors(Class entityType, List fields) { - return new AddFieldAccessorMethods(entityType, fields).emit(); - } - - public byte[] methodAccessors(Class entityType, List methods) { - return new AddMethodAccessorMethods(entityType, methods).emit(); - } - - public PropertyAccessorGenerator propertyAccessor(Class entityType, CritterClassLoader critterClassLoader, FieldNode field) { - return new PropertyAccessorGenerator(entityType, critterClassLoader, field).emit(); - } - - public PropertyAccessorGenerator propertyAccessor(Class entityType, CritterClassLoader critterClassLoader, MethodNode method) { - return new PropertyAccessorGenerator(entityType, critterClassLoader, method).emit(); - } - - public VarHandleAccessorGenerator varHandleAccessor(Class entityType, CritterClassLoader critterClassLoader, FieldNode field) { - return new VarHandleAccessorGenerator(entityType, critterClassLoader, field).emit(); - } - - public VarHandleAccessorGenerator varHandleAccessor(Class entityType, CritterClassLoader critterClassLoader, MethodNode method) { - return new VarHandleAccessorGenerator(entityType, critterClassLoader, method).emit(); - } - - public PropertyModelGenerator propertyModelGenerator(Class entityType, CritterClassLoader critterClassLoader, FieldNode field) { - return new PropertyModelGenerator(Generators.INSTANCE.getConfig(), entityType, critterClassLoader, field).emit(); - } - - public PropertyModelGenerator propertyModelGenerator(Class entityType, CritterClassLoader critterClassLoader, MethodNode method) { - return new PropertyModelGenerator(Generators.INSTANCE.getConfig(), entityType, critterClassLoader, method).emit(); - } - - public GizmoEntityModelGenerator entityModel(Class type, CritterClassLoader critterClassLoader, - ClassNode classNode, List properties) { - return new GizmoEntityModelGenerator(type, critterClassLoader, classNode, properties).emit(); - } -} diff --git a/critter/critter-maven/src/main/kotlin/dev/morphia/critter/maven/CritterProcessor.kt b/critter/critter-maven/src/main/kotlin/dev/morphia/critter/maven/CritterProcessor.kt index 5de1d6f42d7..2f7d82820b1 100644 --- a/critter/critter-maven/src/main/kotlin/dev/morphia/critter/maven/CritterProcessor.kt +++ b/critter/critter-maven/src/main/kotlin/dev/morphia/critter/maven/CritterProcessor.kt @@ -5,6 +5,7 @@ import dev.morphia.config.MorphiaConfig import dev.morphia.critter.CritterClassLoader import dev.morphia.critter.parser.Generators import dev.morphia.critter.parser.gizmo.CritterGizmoGenerator +import dev.morphia.mapping.ReflectiveMapper import io.github.classgraph.ClassGraph import java.io.File import org.slf4j.Logger @@ -21,11 +22,9 @@ class CritterProcessor( private val logger: Logger = LoggerFactory.getLogger(CritterProcessor::class.java) private val critterClassLoader = CritterClassLoader() + private val generators = Generators(config, ReflectiveMapper(config)) fun process() { - // Configure Generators with the loaded MorphiaConfig - Generators.INSTANCE.configFile = findConfigFile() - val entityClasses = findEntityClasses() if (entityClasses.isEmpty()) { @@ -44,17 +43,6 @@ class CritterProcessor( writeGeneratedClasses() } - private fun findConfigFile(): String { - // Check if morphia-config.properties exists in the classes directory - val configFile = File(classesDirectory, "META-INF/morphia-config.properties") - return if (configFile.exists()) { - "META-INF/morphia-config.properties" - } else { - // Return default location - Generators will use defaults if not found - "META-INF/morphia-config.properties" - } - } - private fun findEntityClasses(): List> { val packagesToScan = packages.ifEmpty { @@ -84,7 +72,7 @@ class CritterProcessor( private fun processClass(entityClass: Class<*>) { logger.info("Generating critter code for: ${entityClass.name}") - CritterGizmoGenerator.INSTANCE.generate(entityClass, critterClassLoader, false) + CritterGizmoGenerator.generate(entityClass, critterClassLoader, generators, false) } private fun writeGeneratedClasses() { From 6665dc6eeefb44a365181ebd9978c72682db9d65 Mon Sep 17 00:00:00 2001 From: Justin Lee Date: Fri, 6 Mar 2026 11:41:56 -0500 Subject: [PATCH 2/5] Update core/pom.xml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- core/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/pom.xml b/core/pom.xml index d5728b89bda..7f29488f2ff 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -133,6 +133,7 @@ morphia-annotation-node + generate-sources From f76cdf0786f0045c86aac07d6e1d60a4c65c6c3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:43:03 +0000 Subject: [PATCH 3/5] Initial plan From 50f1d51d832bae20f86271db8b48b229626a46af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:47:44 +0000 Subject: [PATCH 4/5] Fix resource leak: wrap InputStream in try-with-resources in CritterGizmoGenerator Co-authored-by: evanchooly <195021+evanchooly@users.noreply.github.com> --- .../critter/parser/gizmo/CritterGizmoGenerator.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/dev/morphia/critter/parser/gizmo/CritterGizmoGenerator.java b/core/src/main/java/dev/morphia/critter/parser/gizmo/CritterGizmoGenerator.java index 1f6082cebae..b76c1828f77 100644 --- a/core/src/main/java/dev/morphia/critter/parser/gizmo/CritterGizmoGenerator.java +++ b/core/src/main/java/dev/morphia/critter/parser/gizmo/CritterGizmoGenerator.java @@ -23,11 +23,10 @@ public static GizmoEntityModelGenerator generate(Class type, CritterClassLoad Generators generators, boolean runtimeMode) { ClassNode classNode = new ClassNode(); String resourceName = "%s.class".formatted(type.getName().replace('.', '/')); - java.io.InputStream inputStream = type.getClassLoader().getResourceAsStream(resourceName); - if (inputStream == null) { - throw new IllegalArgumentException("Could not find class file for %s".formatted(type.getName())); - } - try { + try (java.io.InputStream inputStream = type.getClassLoader().getResourceAsStream(resourceName)) { + if (inputStream == null) { + throw new IllegalArgumentException("Could not find class file for %s".formatted(type.getName())); + } new ClassReader(inputStream).accept(classNode, 0); } catch (IOException e) { throw new RuntimeException("Failed to read class %s".formatted(type.getName()), e); From 7242b969489513b67e45aa55ec9157c1bb0540b3 Mon Sep 17 00:00:00 2001 From: evanchooly Date: Fri, 6 Mar 2026 14:55:36 -0500 Subject: [PATCH 5/5] push down default methods in MorphiaConfig make fields private --- .github/copilot-instructions.md | 2 +- .../morphia/config/ManualMorphiaConfig.java | 205 +++++++++++++++--- .../dev/morphia/config/MorphiaConfig.java | 158 ++------------ .../morphia/critter/parser/GeneratorTest.java | 3 +- .../parser/TestEntityModelGenerator.java | 4 +- .../critter/parser/TestVarHandleAccessor.java | 3 +- .../parser/gizmo/TestGizmoGeneration.java | 3 +- .../dev/morphia/test/config/TestConfig.java | 7 +- .../morphia/critter/maven/CritterProcessor.kt | 2 +- critter/pom.xml | 3 +- 10 files changed, 214 insertions(+), 176 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4af1905cd33..009c9a753e7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -182,7 +182,7 @@ The `pull-request.yml` workflow runs on every PR and is the gating check: | Error | Cause | Fix | |-------|-------|-----| | `Cannot connect to MongoDB` in tests | Docker not running / Testcontainers can't pull image | Start Docker daemon; or use `-Dmongodb=local` with a local `mongod` | -| `No MorphiaConfig found` | Missing `morphia-config.properties` on classpath | Add the file to `src/test/resources/`, or call `Morphia.createDatastore(client, new ManualMorphiaConfig())` | +| `No MorphiaConfig found` | Missing `morphia-config.properties` on classpath | Add the file to `src/test/resources/`, or call `Morphia.createDatastore(client, dev.morphia.config.MorphiaConfig.load())` | | `MappingException: No usable constructor` | Entity class has no no-arg constructor accessible to Morphia | Add a `protected` or `public` no-arg constructor | | `ClassCastException` with proxies | Lazy-loading proxy and `instanceof` check clash | Check `MorphiaInternals.proxyClassesPresent()` before using proxies, or disable lazy loading | | Tests skipped with `SkipException` | MongoDB version below minimum for a feature | Pass a higher `-Dmongodb=` version | diff --git a/core/src/main/java/dev/morphia/config/ManualMorphiaConfig.java b/core/src/main/java/dev/morphia/config/ManualMorphiaConfig.java index edda8f41469..c791eb271f8 100644 --- a/core/src/main/java/dev/morphia/config/ManualMorphiaConfig.java +++ b/core/src/main/java/dev/morphia/config/ManualMorphiaConfig.java @@ -17,7 +17,9 @@ import org.bson.codecs.configuration.CodecProvider; +import static dev.morphia.config.MorphiaConfigHelper.dumpConfigurationFile; import static dev.morphia.mapping.DateStorage.UTC; +import static dev.morphia.mapping.DiscriminatorFunction.className; import static dev.morphia.mapping.DiscriminatorFunction.simpleName; import static dev.morphia.mapping.NamingStrategy.camelCase; import static dev.morphia.mapping.NamingStrategy.identity; @@ -32,40 +34,40 @@ @MorphiaInternal @SuppressWarnings("removal") public class ManualMorphiaConfig implements MorphiaConfig { - Boolean applyCaps; - Boolean applyDocumentValidations; - Boolean applyIndexes; - - Optional codecProvider; - - NamingStrategy collectionNaming; - - String database; - - DateStorage dateStorage; - DiscriminatorFunction discriminator; - String discriminatorKey; - Boolean enablePolymorphicQueries; - Boolean ignoreFinals; - MapperType mapper; - List packages; - PropertyDiscovery propertyDiscovery; - List> propertyAnnotationProviders; - NamingStrategy propertyNaming; - QueryFactory queryFactory; - Boolean storeEmpties; - Boolean storeNulls; + private Boolean applyCaps; + private Boolean applyDocumentValidations; + private Boolean applyIndexes; + + private Optional codecProvider; + + private NamingStrategy collectionNaming; + + private String database; + + private DateStorage dateStorage; + private DiscriminatorFunction discriminator; + private String discriminatorKey; + private Boolean enablePolymorphicQueries; + private Boolean ignoreFinals; + private MapperType mapper; + private List packages; + private PropertyDiscovery propertyDiscovery; + private List> propertyAnnotationProviders; + private NamingStrategy propertyNaming; + private QueryFactory queryFactory; + private Boolean storeEmpties; + private Boolean storeNulls; /** * @hidden */ - public ManualMorphiaConfig() { + protected ManualMorphiaConfig() { } /** * @hidden */ - public ManualMorphiaConfig(MorphiaConfig base) { + protected ManualMorphiaConfig(MorphiaConfig base) { applyCaps = base.applyCaps(); applyDocumentValidations = base.applyDocumentValidations(); applyIndexes = base.applyIndexes(); @@ -122,90 +124,243 @@ public Boolean applyCaps() { return orDefault(applyCaps, FALSE); } + @Override + public MorphiaConfig applyCaps(Boolean value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.applyCaps = value; + return newConfig; + } + + @Override public Boolean applyDocumentValidations() { return orDefault(applyDocumentValidations, FALSE); } + @Override + public MorphiaConfig applyDocumentValidations(Boolean value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.applyDocumentValidations = value; + return newConfig; + } + @Override public Boolean applyIndexes() { return orDefault(applyIndexes, FALSE); } + @Override + public MorphiaConfig applyIndexes(Boolean value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.applyIndexes = value; + return newConfig; + } + @Override public Optional codecProvider() { return orDefault(codecProvider, Optional.empty()); } + @Override + public MorphiaConfig codecProvider(CodecProvider value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.codecProvider = Optional.of(value); + return newConfig; + } + @Override public NamingStrategy collectionNaming() { return orDefault(collectionNaming, camelCase()); } + @Override + public MorphiaConfig collectionNaming(NamingStrategy value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.collectionNaming = value; + return newConfig; + } + + @Override + public MorphiaConfig database(String value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.database = value; + return newConfig; + } + @Override public DateStorage dateStorage() { return orDefault(dateStorage, UTC); } + @Override + public MorphiaConfig dateStorage(DateStorage value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.dateStorage = value; + return newConfig; + } + @Override public DiscriminatorFunction discriminator() { return orDefault(discriminator, simpleName()); } + @Override + public MorphiaConfig discriminator(DiscriminatorFunction value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.discriminator = value; + return newConfig; + } + @Override public String discriminatorKey() { return orDefault(discriminatorKey, "_t"); } + @Override + public MorphiaConfig discriminatorKey(String value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.discriminatorKey = value; + return newConfig; + } + @Override public Boolean enablePolymorphicQueries() { return orDefault(enablePolymorphicQueries, FALSE); } + @Override + public MorphiaConfig enablePolymorphicQueries(Boolean value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.enablePolymorphicQueries = value; + return newConfig; + } + @Override public Boolean ignoreFinals() { return orDefault(ignoreFinals, FALSE); } + @Override + public MorphiaConfig ignoreFinals(Boolean value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.ignoreFinals = value; + return newConfig; + } + + @Override + public MorphiaConfig legacy() { + ManualMorphiaConfig newConfig = new ManualMorphiaConfig(this); + newConfig.dateStorage = DateStorage.SYSTEM_DEFAULT; + newConfig.discriminatorKey = "className"; + newConfig.discriminator = className(); + newConfig.collectionNaming = identity(); + newConfig.propertyNaming = identity(); + return newConfig; + } + @Override public MapperType mapper() { return orDefault(mapper, MapperType.LEGACY); } + @Override + public MorphiaConfig mapper(MapperType value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.mapper = value; + return newConfig; + } + @Override public List packages() { return orDefault(packages, List.of()); } + @Override + public MorphiaConfig packages(List value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.packages = value; + return newConfig; + } + @Override public List> propertyAnnotationProviders() { return orDefault(propertyAnnotationProviders, List.of(new MorphiaPropertyAnnotationProvider())); } + @Override + public MorphiaConfig propertyAnnotationProviders(List> list) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.propertyAnnotationProviders = list; + if (list.isEmpty() || list.stream().noneMatch(p -> p instanceof MorphiaPropertyAnnotationProvider)) { + newConfig.propertyAnnotationProviders.add(new MorphiaPropertyAnnotationProvider()); + } + return newConfig; + } + @Override public PropertyDiscovery propertyDiscovery() { return orDefault(propertyDiscovery, FIELDS); } + @Override + public MorphiaConfig propertyDiscovery(PropertyDiscovery value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.propertyDiscovery = value; + return newConfig; + } + @Override public NamingStrategy propertyNaming() { return orDefault(propertyNaming, identity()); } + @Override + public MorphiaConfig propertyNaming(NamingStrategy value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.propertyNaming = value; + return newConfig; + } + @Override public QueryFactory queryFactory() { return orDefault(queryFactory, new DefaultQueryFactory()); } + @Override + public MorphiaConfig queryFactory(QueryFactory value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.queryFactory = value; + return newConfig; + } + @Override public Boolean storeEmpties() { return orDefault(storeEmpties, FALSE); } + @Override + public MorphiaConfig storeEmpties(Boolean value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.storeEmpties = value; + return newConfig; + } + @Override public Boolean storeNulls() { return orDefault(storeNulls, FALSE); } + @Override + public MorphiaConfig storeNulls(Boolean value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.storeNulls = value; + return newConfig; + } + + @Override + public String toConfigFormat(boolean showComplete) { + return dumpConfigurationFile(this, showComplete); + } + protected T orDefault(@Nullable T localValue, T defaultValue) { return localValue != null ? localValue : defaultValue; } diff --git a/core/src/main/java/dev/morphia/config/MorphiaConfig.java b/core/src/main/java/dev/morphia/config/MorphiaConfig.java index c079dc40723..ce497040d11 100644 --- a/core/src/main/java/dev/morphia/config/MorphiaConfig.java +++ b/core/src/main/java/dev/morphia/config/MorphiaConfig.java @@ -30,9 +30,6 @@ import io.smallrye.config.WithConverter; import io.smallrye.config.WithDefault; -import static dev.morphia.config.MorphiaConfigHelper.dumpConfigurationFile; -import static dev.morphia.mapping.DiscriminatorFunction.className; -import static dev.morphia.mapping.NamingStrategy.identity; import static io.smallrye.config.PropertiesConfigSourceLoader.*; import static java.lang.Thread.currentThread; @@ -50,7 +47,7 @@ public interface MorphiaConfig { /** * Tries to load a configuration from the default location. - * + * * @return the loaded config */ static MorphiaConfig load() { @@ -87,12 +84,7 @@ static MorphiaConfig load(String location) { * @return a new instance with the updated configuration * @since 3.0 */ - default MorphiaConfig collectionNaming(NamingStrategy value) { - var newConfig = new ManualMorphiaConfig(this); - - newConfig.collectionNaming = value; - return newConfig; - } + MorphiaConfig collectionNaming(NamingStrategy value); /** * The database name that Morphia should use. This entry is required to be present and is the only necessary configuration element @@ -118,12 +110,7 @@ default MorphiaConfig collectionNaming(NamingStrategy value) { * @return a new instance with the updated configuration * @since 3.0 */ - default MorphiaConfig applyCaps(Boolean value) { - var newConfig = new ManualMorphiaConfig(this); - - newConfig.applyCaps = value; - return newConfig; - } + MorphiaConfig applyCaps(Boolean value); /** * If true, document validations will be enabled for entities/collections with validation mappings. @@ -142,12 +129,7 @@ default MorphiaConfig applyCaps(Boolean value) { * @return a new instance with the updated configuration * @since 3.0 */ - default MorphiaConfig applyDocumentValidations(Boolean value) { - var newConfig = new ManualMorphiaConfig(this); - - newConfig.applyDocumentValidations = value; - return newConfig; - } + MorphiaConfig applyDocumentValidations(Boolean value); /** * If true, mapped indexes will be applied to the database at start up. @@ -164,12 +146,7 @@ default MorphiaConfig applyDocumentValidations(Boolean value) { * @return a new instance with the updated configuration * @since 3.0 */ - default MorphiaConfig applyIndexes(Boolean value) { - var newConfig = new ManualMorphiaConfig(this); - - newConfig.applyIndexes = value; - return newConfig; - } + MorphiaConfig applyIndexes(Boolean value); /** * Specifies a {@code CodecProvider} to supply user defined codecs that Morphia should use. @@ -188,12 +165,7 @@ default MorphiaConfig applyIndexes(Boolean value) { * @return a new instance with the updated configuration * @since 3.0 */ - default MorphiaConfig codecProvider(CodecProvider value) { - var newConfig = new ManualMorphiaConfig(this); - - newConfig.codecProvider = Optional.of(value); - return newConfig; - } + MorphiaConfig codecProvider(CodecProvider value); /** * Sets the naming strategy to be used when generating collection names for entities if name is not explicitly given in the {@code @@ -217,12 +189,7 @@ default MorphiaConfig codecProvider(CodecProvider value) { * @return a new instance with the updated configuration * @since 3.0 */ - default MorphiaConfig database(String value) { - var newConfig = new ManualMorphiaConfig(this); - - newConfig.database = value; - return newConfig; - } + MorphiaConfig database(String value); /** * The date storage configuration Morphia should use for JSR 310 types. @@ -239,12 +206,7 @@ default MorphiaConfig database(String value) { * @return a new instance with the updated configuration * @since 3.0 */ - default MorphiaConfig dateStorage(DateStorage value) { - var newConfig = new ManualMorphiaConfig(this); - - newConfig.dateStorage = value; - return newConfig; - } + MorphiaConfig dateStorage(DateStorage value); /** * The function to use when calculating the discriminator value for an entity @@ -264,12 +226,7 @@ default MorphiaConfig dateStorage(DateStorage value) { * @return a new instance with the updated configuration * @since 3.0 */ - default MorphiaConfig discriminator(DiscriminatorFunction value) { - var newConfig = new ManualMorphiaConfig(this); - - newConfig.discriminator = value; - return newConfig; - } + MorphiaConfig discriminator(DiscriminatorFunction value); /** * The document field name to use when storing discriminator values @@ -286,12 +243,7 @@ default MorphiaConfig discriminator(DiscriminatorFunction value) { * @return a new instance with the updated configuration * @since 3.0 */ - default MorphiaConfig discriminatorKey(String value) { - var newConfig = new ManualMorphiaConfig(this); - - newConfig.discriminatorKey = value; - return newConfig; - } + MorphiaConfig discriminatorKey(String value); /** * Enable polymorphic queries. By default, Morphia will only query for the given type. However, in cases where subtypes are stored @@ -309,12 +261,7 @@ default MorphiaConfig discriminatorKey(String value) { * @return a new instance with the updated configuration * @since 3.0 */ - default MorphiaConfig enablePolymorphicQueries(Boolean value) { - var newConfig = new ManualMorphiaConfig(this); - - newConfig.enablePolymorphicQueries = value; - return newConfig; - } + MorphiaConfig enablePolymorphicQueries(Boolean value); /** * Instructs Morphia to ignore final fields. @@ -331,32 +278,17 @@ default MorphiaConfig enablePolymorphicQueries(Boolean value) { * @return a new instance with the updated configuration * @since 3.0 */ - default MorphiaConfig ignoreFinals(Boolean value) { - var newConfig = new ManualMorphiaConfig(this); - - newConfig.ignoreFinals = value; - return newConfig; - } + MorphiaConfig ignoreFinals(Boolean value); /** * Creates a new configuration based on the current one but updated to reflect the legacy configuration. This configuration is not * changed. - * + * * @return the update configuration * * @since 3.0 */ - default MorphiaConfig legacy() { - ManualMorphiaConfig newConfig = new ManualMorphiaConfig(this); - newConfig.dateStorage = DateStorage.SYSTEM_DEFAULT; - newConfig.discriminatorKey = "className"; - newConfig.discriminator = className(); - newConfig.collectionNaming = identity(); - newConfig.propertyNaming = identity(); - - return newConfig; - - } + MorphiaConfig legacy(); /** * The mapper implementation to use. Defaults to {@link MapperType#LEGACY} (reflection-based). @@ -375,12 +307,7 @@ default MorphiaConfig legacy() { * @return a new instance with the updated configuration * @since 3.0 */ - default MorphiaConfig mapper(MapperType value) { - var newConfig = new ManualMorphiaConfig(this); - - newConfig.mapper = value; - return newConfig; - } + MorphiaConfig mapper(MapperType value); /** * A comma delimited list of packages that Morphia should map. If subpackages of a specific package should also be mapped, simply add @@ -398,12 +325,7 @@ default MorphiaConfig mapper(MapperType value) { * @return a new instance with the updated configuration * @since 3.0 */ - default MorphiaConfig packages(List value) { - var newConfig = new ManualMorphiaConfig(this); - newConfig.packages = value; - - return newConfig; - } + MorphiaConfig packages(List value); /** * Specifies the providers of any external annotations to use as markers for properties for Morphia to consider while mapping. This @@ -440,14 +362,7 @@ default MorphiaConfig packages(List value) { */ @MorphiaInternal @MorphiaExperimental - default MorphiaConfig propertyAnnotationProviders(List> list) { - var newConfig = new ManualMorphiaConfig(this); - newConfig.propertyAnnotationProviders = list; - if (list.isEmpty() || list.stream().noneMatch(p -> p instanceof MorphiaPropertyAnnotationProvider)) { - newConfig.propertyAnnotationProviders.add(new MorphiaPropertyAnnotationProvider()); - } - return newConfig; - } + MorphiaConfig propertyAnnotationProviders(List> list); /** * Determines how properties are discovered. The traditional value is by scanning for fields which involves a bit more reflective @@ -468,12 +383,7 @@ default MorphiaConfig propertyAnnotationProviders(Listpom - core critter-maven critter-integration-tests - \ No newline at end of file +