This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch maintenance-branch
in repository https://gitbox.apache.org/repos/asf/causeway.git


The following commit(s) were added to refs/heads/maintenance-branch by this 
push:
     new 72884ff8761 CAUSEWAY-3969: [v2] backports JPA Weaving Safeguard from v4
72884ff8761 is described below

commit 72884ff87617f8c08645c842114ef1fef824ef4d
Author: andi-huber <[email protected]>
AuthorDate: Thu Feb 19 11:06:04 2026 +0100

    CAUSEWAY-3969: [v2] backports JPA Weaving Safeguard from v4
---
 adoc/changelog.adoc                                |  16 ++-
 .../core/config/CausewayConfiguration.java         |  24 ++++
 .../config/beans/CausewayBeanTypeRegistry.java     |   2 +
 .../beans/CausewayBeanTypeRegistryDefault.java     |   9 ++
 .../CausewayModulePersistenceJpaIntegration.java   |   9 +-
 .../integration/services/JpaWeavingSafeguard.java  | 136 +++++++++++++++++++++
 .../services/JpaWeavingSafeguardService.java       |  53 ++++++++
 .../VerifyExtensionEntitiesAreEnhancedTest.java    | 114 ++++++++++++++---
 8 files changed, 339 insertions(+), 24 deletions(-)

diff --git a/adoc/changelog.adoc b/adoc/changelog.adoc
index 1a72c42628f..09bd86e2a89 100644
--- a/adoc/changelog.adoc
+++ b/adoc/changelog.adoc
@@ -2,7 +2,21 @@
 
 ⭐ New Features
 
-- Static weaving support for CI; enables via `mvn ... -DenhanceEclipselink`  
https://issues.apache.org/jira/browse/CAUSEWAY-3968[CAUSEWAY-3968]  
+- Static weaving support for CI; enabled via `mvn ... -DenhanceEclipselink`  
https://issues.apache.org/jira/browse/CAUSEWAY-3968[CAUSEWAY-3968]
+- JPA Weaving Safeguard 
https://issues.apache.org/jira/browse/CAUSEWAY-3969[CAUSEWAY-3969]
+; configure using 
`causeway.persistence.weaving.safeguardMode=(LOG_ONLY|REQUIRE_WEAVED|..)`
+----
+LOG_ONLY: 
+  Safeguard only logs warnings, but otherwise does not prevent an application 
from launching.
+
+REQUIRE_WEAVED_WHEN_ANY_SUB_IS_WEAVED: 
+  (Default) Requires for any entity type hierarchy that when classes are 
weaved,
+  their super classes are also weaved.
+  Prevents entity type hierarchies from failing later at runtime.
+
+REQUIRE_WEAVED: 
+  Enforces weaving on all encountered entity type hierarchies.
+----
 
 📔 Documentation
 
diff --git 
a/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
 
b/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
index 810afd2fdae..b6c863dd5a6 100644
--- 
a/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
+++ 
b/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
@@ -2230,6 +2230,30 @@ public static class Schema {
             private String createSchemaSqlTemplate = "CREATE SCHEMA IF NOT 
EXISTS %S";
 
         }
+
+        private final Weaving weaving = new Weaving();
+        @Data
+        public static class Weaving {
+            public enum SafeguardMode {
+                /**
+                 * Safeguard only logs warnings, but otherwise does not 
prevent an application from launching.
+                 */
+                LOG_ONLY,
+                /**
+                 * (Default) Requires for any entity type hierarchy that when 
classes are weaved,
+                 * their super classes are also weaved.
+                 *
+                 * <p>Prevents entity type hierarchies from failing later at 
runtime.
+                 */
+                REQUIRE_WEAVED_WHEN_ANY_SUB_IS_WEAVED,
+                /**
+                 * Enforces weaving on all encountered entity type hierarchies.
+                 */
+                REQUIRE_WEAVED
+            }
+            private SafeguardMode safeguardMode = 
SafeguardMode.REQUIRE_WEAVED_WHEN_ANY_SUB_IS_WEAVED;
+            
+        }
     }
 
     private final Prototyping prototyping = new Prototyping();
diff --git 
a/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistry.java
 
b/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistry.java
index d55365d3dc6..86d5dabae96 100644
--- 
a/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistry.java
+++ 
b/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistry.java
@@ -76,5 +76,7 @@ default PersistenceStack determineCurrentPersistenceStack() {
             .findFirst()
             .orElse(PersistenceStack.UNSPECIFIED);
     }
+    
+       Stream<Class<?>> streamEntityTypes(PersistenceStack selectedStack);
 
 }
\ No newline at end of file
diff --git 
a/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistryDefault.java
 
b/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistryDefault.java
index c7bdc277639..1fbe8ed746a 100644
--- 
a/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistryDefault.java
+++ 
b/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistryDefault.java
@@ -20,6 +20,7 @@
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Stream;
 
@@ -126,6 +127,14 @@ public CausewayBeanTypeRegistryDefault(final @NonNull 
Can<CausewayBeanMetaData>
 
     }
 
+    // -- STREAMS
 
+    @Override
+    public Stream<Class<?>> streamEntityTypes(final PersistenceStack 
selectedStack) {
+        if(selectedStack==null || !selectedStack.isJpa()) return 
Stream.empty();
+        return entityTypes.values().stream()
+                
.filter(typeMeta->Objects.equals(typeMeta.getPersistenceStack().orElse(null), 
selectedStack))
+                .<Class<?>>map(CausewayBeanMetaData::getCorrespondingClass);
+    }
 
 }
diff --git 
a/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/CausewayModulePersistenceJpaIntegration.java
 
b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/CausewayModulePersistenceJpaIntegration.java
index fff16c18d5b..79bf71175ff 100644
--- 
a/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/CausewayModulePersistenceJpaIntegration.java
+++ 
b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/CausewayModulePersistenceJpaIntegration.java
@@ -18,14 +18,11 @@
  */
 package org.apache.causeway.persistence.jpa.integration;
 
-import org.springframework.boot.autoconfigure.domain.EntityScan;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Import;
-
 import org.apache.causeway.core.runtime.CausewayModuleCoreRuntime;
 import 
org.apache.causeway.persistence.commons.CausewayModulePersistenceCommons;
 import 
org.apache.causeway.persistence.jpa.integration.entity.JpaEntityIntegration;
 import 
org.apache.causeway.persistence.jpa.integration.services.JpaSupportServiceUsingSpring;
+import 
org.apache.causeway.persistence.jpa.integration.services.JpaWeavingSafeguardService;
 import 
org.apache.causeway.persistence.jpa.integration.typeconverters.applib.CausewayBookmarkConverter;
 import 
org.apache.causeway.persistence.jpa.integration.typeconverters.applib.CausewayLocalResourcePathConverter;
 import 
org.apache.causeway.persistence.jpa.integration.typeconverters.applib.CausewayMarkupConverter;
@@ -40,6 +37,9 @@
 import 
org.apache.causeway.persistence.jpa.integration.typeconverters.schema.v2.CausewayInteractionDtoConverter;
 import 
org.apache.causeway.persistence.jpa.integration.typeconverters.schema.v2.CausewayOidDtoConverter;
 import 
org.apache.causeway.persistence.jpa.metamodel.CausewayModulePersistenceJpaMetamodel;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
 
 @Configuration
 @Import({
@@ -53,6 +53,7 @@
 
         // @Service's
         JpaSupportServiceUsingSpring.class,
+        JpaWeavingSafeguardService.class,
 
 })
 @EntityScan(basePackageClasses = {
diff --git 
a/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/services/JpaWeavingSafeguard.java
 
b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/services/JpaWeavingSafeguard.java
new file mode 100644
index 00000000000..328a77ef89c
--- /dev/null
+++ 
b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/services/JpaWeavingSafeguard.java
@@ -0,0 +1,136 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.persistence.jpa.integration.services;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.causeway.commons.internal.exceptions._Exceptions;
+import org.apache.causeway.commons.internal.reflection._ClassCache;
+import org.apache.causeway.commons.internal.reflection._Reflect;
+import 
org.apache.causeway.commons.internal.reflection._Reflect.InterfacePolicy;
+import org.apache.causeway.core.config.CausewayConfiguration;
+import 
org.apache.causeway.core.config.CausewayConfiguration.Persistence.Weaving.SafeguardMode;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.Accessors;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@RequiredArgsConstructor
+public final class JpaWeavingSafeguard {
+
+    final CausewayConfiguration.Persistence.Weaving.SafeguardMode mode;
+
+//debug
+//    {
+//        mode = SafeguardMode.REQUIRE_WEAVED;
+//    }
+
+    public void checkAll(final Iterable<Class<?>> entityTypes) {
+        var cache = new Cache();
+
+        for(final Class<?> entityType : entityTypes) {
+            var chainOfInheritance = 
cache.streamTypeHierarchyEntries(entityType).collect(Collectors.toList());
+
+            boolean isRemainingRequiredToBeEnhanced = mode == 
SafeguardMode.REQUIRE_WEAVED;
+            var it = chainOfInheritance.iterator();
+            while(it.hasNext()) {
+                var next = it.next();
+                if(isRemainingRequiredToBeEnhanced
+                    && !next.isEnhanced()) {
+                        fail(next, chainOfInheritance);
+                }
+                if(next.isEnhanced()) {
+                    isRemainingRequiredToBeEnhanced = true;
+                }
+            }
+        }
+
+    }
+
+    private void fail(final EnhancementDescriptor offender, final 
List<EnhancementDescriptor> chainOfInheritance) {
+        var chainAsMultiline = " * " + chainOfInheritance.stream()
+                .map(EnhancementDescriptor::toString)
+                .collect(Collectors.joining("\n * "));
+
+        switch (mode) {
+            case LOG_ONLY:
+                log.warn("found non-weaved\n {}\n in chain of inheritance\n{}",
+                    offender.cls().getName(),
+                    chainAsMultiline);
+                break;
+            case REQUIRE_WEAVED_WHEN_ANY_SUB_IS_WEAVED:
+                throw _Exceptions.unrecoverable("found non-weaved\n %s\n in 
chain of inheritance\n%s",
+                    offender.cls().getName(),
+                    chainAsMultiline);
+            case REQUIRE_WEAVED:
+                throw _Exceptions.unrecoverable("while weaving is enforced, 
found non-weaved\n %s\n in chain of inheritance\n%s",
+                    offender.cls().getName(),
+                    chainAsMultiline);
+        }
+    }
+
+    @RequiredArgsConstructor
+    @Getter @Accessors(fluent = true)
+    private final class EnhancementDescriptor{
+       
+       final Class<?> cls;
+        final boolean isEnhanced;
+       
+        @Override public final String toString() {
+            return String.format("%s%s",
+                isEnhanced
+                    ? "[#]"
+                    : "[-]",
+                cls.getName());
+        }
+    }
+
+    @RequiredArgsConstructor
+    @Getter @Accessors(fluent = true)
+    private final class Cache {
+
+       final Map<Class<?>, EnhancementDescriptor> descriptorsByClass;
+       final _ClassCache classCache;
+       
+        Cache() {
+            this(new HashMap<>(), _ClassCache.getInstance());
+        }
+
+        Stream<EnhancementDescriptor> streamTypeHierarchyEntries(final 
Class<?> cls) {
+            return streamTypeHierarchy(cls)
+                .map(type->
+                    descriptorsByClass.computeIfAbsent(type, __->new 
EnhancementDescriptor(type, classCache.isByteCodeEnhanced(type))));
+        }
+
+        // -- HELPER
+
+        private Stream<Class<?>> streamTypeHierarchy(final Class<?> cls) {
+            return _Reflect.streamTypeHierarchy(cls, InterfacePolicy.EXCLUDE)
+                .filter(type->!Object.class.equals(type));
+        }
+
+    }
+
+}
diff --git 
a/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/services/JpaWeavingSafeguardService.java
 
b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/services/JpaWeavingSafeguardService.java
new file mode 100644
index 00000000000..422ec0540c3
--- /dev/null
+++ 
b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/services/JpaWeavingSafeguardService.java
@@ -0,0 +1,53 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.persistence.jpa.integration.services;
+
+import java.util.HashSet;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+import org.apache.causeway.applib.events.metamodel.MetamodelListener;
+import org.apache.causeway.core.config.CausewayConfiguration;
+import org.apache.causeway.core.config.beans.CausewayBeanTypeRegistry;
+import org.apache.causeway.core.config.beans.PersistenceStack;
+import org.springframework.stereotype.Service;
+
+import lombok.extern.log4j.Log4j2;
+
+@Service
+@Log4j2
+public class JpaWeavingSafeguardService implements MetamodelListener {
+
+    @Inject CausewayConfiguration config;
+    @Inject CausewayBeanTypeRegistry causewayBeanTypeRegistry;
+
+    @Override public void onMetamodelLoaded() { }
+    @Override public void onMetamodelAboutToBeLoaded() {
+        var mode = config.getPersistence().getWeaving().getSafeguardMode();
+        log.info("running JPA Weaving Safeguard ({})", mode.name());
+
+        var jpaWeavingSafeguard = new JpaWeavingSafeguard(mode);
+        jpaWeavingSafeguard.checkAll(causewayBeanTypeRegistry
+                .streamEntityTypes(PersistenceStack.JPA)
+                .collect(Collectors.toCollection(HashSet::new)));
+    }
+
+}
+
diff --git 
a/regressiontests/persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/enhance/VerifyExtensionEntitiesAreEnhancedTest.java
 
b/regressiontests/persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/enhance/VerifyExtensionEntitiesAreEnhancedTest.java
index 52e13b310bf..30bfd2d996a 100644
--- 
a/regressiontests/persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/enhance/VerifyExtensionEntitiesAreEnhancedTest.java
+++ 
b/regressiontests/persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/enhance/VerifyExtensionEntitiesAreEnhancedTest.java
@@ -18,8 +18,13 @@
  */
 package org.apache.causeway.testdomain.persistence.jpa.enhance;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
 import org.apache.causeway.commons.internal.reflection._ClassCache;
 import org.apache.causeway.extensions.audittrail.jpa.dom.AuditTrailEntry;
 import org.apache.causeway.extensions.commandlog.jpa.dom.CommandLogEntry;
@@ -34,6 +39,9 @@
 import org.junit.jupiter.api.condition.EnabledIf;
 import org.springframework.util.StringUtils;
 
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+
 @EnabledIf(value = "isWeavingEnabled")
 class VerifyExtensionEntitiesAreEnhancedTest {
 
@@ -42,33 +50,33 @@ static boolean isWeavingEnabled() {
                 || StringUtils.hasLength(System.getenv("enhanceEclipselink"));
     }
 
-    private _ClassCache classCache = _ClassCache.getInstance();
+    private Verifier verifier = new 
Verifier(Verifier.getVersion(VerifyExtensionEntitiesAreEnhancedTest.class));
 
     @Test
     void audittrail() {
-        assertTrue(classCache.isByteCodeEnhanced(AuditTrailEntry.class));
+       verifier.verify(AuditTrailEntry.class);
     }
     @Test
     void audittrail_applib() {
-        
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.audittrail.applib.dom.AuditTrailEntry.class));
+       
verifier.verify(org.apache.causeway.extensions.audittrail.applib.dom.AuditTrailEntry.class);
     }
 
     @Test
     void commandlog() {
-        assertTrue(classCache.isByteCodeEnhanced(CommandLogEntry.class));
+       verifier.verify(CommandLogEntry.class);
     }
     @Test
     void commandlog_applib() {
-        
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntry.class));
+       
verifier.verify(org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntry.class);
     }
 
     @Test
     void executionlog() {
-        assertTrue(classCache.isByteCodeEnhanced(ExecutionLogEntry.class));
+       verifier.verify(ExecutionLogEntry.class);
     }
     @Test
     void executionlog_applib() {
-        
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.executionlog.applib.dom.ExecutionLogEntry.class));
+       
verifier.verify(org.apache.causeway.extensions.executionlog.applib.dom.ExecutionLogEntry.class);
     }
 
 //no JPA variant of ExcelDemoToDoItem in v2 yet (however, could be backported 
from main)    
@@ -79,45 +87,113 @@ void executionlog_applib() {
 
     @Test
     void executionoutbox() {
-        assertTrue(classCache.isByteCodeEnhanced(ExecutionOutboxEntry.class));
+       verifier.verify(ExecutionOutboxEntry.class);
     }
     @Test
     void executionoutbox_applib() {
-        
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.executionoutbox.applib.dom.ExecutionOutboxEntry.class));
+       
verifier.verify(org.apache.causeway.extensions.executionoutbox.applib.dom.ExecutionOutboxEntry.class);
     }
 
     @Test void secman_role() {
-        assertTrue(classCache.isByteCodeEnhanced(ApplicationRole.class));
+       verifier.verify(ApplicationRole.class);
     }
     @Test void secman_permission() {
-        assertTrue(classCache.isByteCodeEnhanced(ApplicationPermission.class));
+       verifier.verify(ApplicationPermission.class);
     }
     @Test void secman_tenancy() {
-        assertTrue(classCache.isByteCodeEnhanced(ApplicationTenancy.class));
+       verifier.verify(ApplicationTenancy.class);
     }
     @Test void secman_user() {
-        assertTrue(classCache.isByteCodeEnhanced(ApplicationUser.class));
+       verifier.verify(ApplicationUser.class);
     }
     @Test void secman_role_applib() {
-        
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.secman.applib.role.dom.ApplicationRole.class));
+       
verifier.verify(org.apache.causeway.extensions.secman.applib.role.dom.ApplicationRole.class);
     }
     @Test void secman_permission_applib() {
-        
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermission.class));
+       
verifier.verify(org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermission.class);
     }
     @Test void secman_tenancy_applib() {
-        
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.secman.applib.tenancy.dom.ApplicationTenancy.class));
+       
verifier.verify(org.apache.causeway.extensions.secman.applib.tenancy.dom.ApplicationTenancy.class);
     }
     @Test void secman_user_applib() {
-        
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.secman.applib.user.dom.ApplicationUser.class));
+       
verifier.verify(org.apache.causeway.extensions.secman.applib.user.dom.ApplicationUser.class);
     }
 
     @Test
     void sessionlog() {
-        assertTrue(classCache.isByteCodeEnhanced(SessionLogEntry.class));
+       verifier.verify(SessionLogEntry.class);
     }
     @Test
     void sessionlog_applib() {
-        
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.sessionlog.applib.dom.SessionLogEntry.class));
+       
verifier.verify(org.apache.causeway.extensions.sessionlog.applib.dom.SessionLogEntry.class);
+    }
+    
+    // -- HELPER
+
+    @RequiredArgsConstructor
+    final static class Verifier {
+
+       final Version expectedVersion; 
+       final _ClassCache classCache;
+       
+       @RequiredArgsConstructor
+       final static class Version {
+               final int major; final int minor;
+               @Override
+               public boolean equals(Object obj) {
+                       return obj instanceof Version
+                                       ? ((Version)obj).major == major
+                                               && ((Version)obj).minor == minor
+                                       : false;
+               }
+               @Override
+               public int hashCode() {
+                       return super.hashCode();
+               }
+       }
+
+        Verifier(final Version expectedVersion) {
+            this(expectedVersion, _ClassCache.getInstance());
+        }
+
+        void verify(final Class<?> cls) {
+            assertEquals(expectedVersion, getVersion(cls), ()->"java byte-code 
version mismatch");
+            assertTrue(classCache.isByteCodeEnhanced(cls), ()->"not enhanced");
+        }
+
+        //EXPERIMENTAL code suggested by Copilot
+        @SneakyThrows
+        static Version getVersion(final Class<?> clazz) {
+            // Build the resource path (handles inner classes too)
+            String resource = clazz.getName().replace('.', '/') + ".class";
+
+            try (InputStream in = clazz.getClassLoader() != null
+                    ? clazz.getClassLoader().getResourceAsStream(resource)
+                    : ClassLoader.getSystemResourceAsStream(resource)) {
+
+                if (in == null) {
+                    // Fallback: try via the class itself (works with 
bootstrap/jrt classes)
+                    try (InputStream in2 = clazz.getResourceAsStream("/" + 
resource)) {
+                        if (in2 == null)
+                            throw new IOException("Unable to locate class 
resource: " + resource);
+                        return readHeader(in2);
+                    }
+                }
+                return readHeader(in);
+            }
+        }
+
+        private static Version readHeader(final InputStream in) throws 
IOException {
+            try (DataInputStream dis = new DataInputStream(in)) {
+                int magic = dis.readInt();
+                if (magic != 0xCAFEBABE)
+                    throw new IOException("Not a valid class file (bad magic): 
0x" + Integer.toHexString(magic));
+                int minor = dis.readUnsignedShort();
+                int major = dis.readUnsignedShort();
+                return new Version(major, minor);
+            }
+        }
+
     }
 
 }

Reply via email to