You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by ah...@apache.org on 2026/08/13 15:23:25 UTC

(causeway) branch 4044-stable.objspec created (now 4930225a589)

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

andi-huber pushed a change to branch 4044-stable.objspec
in repository https://gitbox.apache.org/repos/asf/causeway.git


      at 4930225a589 CAUSEWAY-4044: refactors 'hierarchical' in to its own class

This branch includes the following new commits:

     new 4930225a589 CAUSEWAY-4044: refactors 'hierarchical' in to its own class

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(causeway) 01/01: CAUSEWAY-4044: refactors 'hierarchical' in to its own class

Posted by ah...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

andi-huber pushed a commit to branch 4044-stable.objspec
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit 4930225a58988d33c4f259ed883d4f44f2cc9165
Author: andi-huber <ah...@apache.org>
AuthorDate: Thu Aug 13 17:10:37 2026 +0200

    CAUSEWAY-4044: refactors 'hierarchical' in to its own class
---
 .../causeway/core/metamodel/spec/Hierarchical.java |  23 +---
 .../core/metamodel/spec/ObjectSpecification.java   |  29 +++++
 .../metamodel/spec/ObjectSpecificationRecord.java  |   6 -
 .../metamodel/spec/impl/HierarchicalFactory.java   | 115 +++++++++++++++++
 .../spec/impl/ObjectSpecificationDefault.java      | 137 +++------------------
 5 files changed, 164 insertions(+), 146 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/Hierarchical.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/Hierarchical.java
index 8dd021a005d..f2c0070531a 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/Hierarchical.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/Hierarchical.java
@@ -30,28 +30,17 @@
 
 public interface Hierarchical {
 
-    /**
+    final static Hierarchical EMPTY = new Hierarchical() {
+		@Override public ObjectSpecification superclass() { return null; }
+		@Override public Can<ObjectSpecification> interfaces() { return Can.empty(); }
+	};
+
+	/**
      * Get the set of specifications for all the interfaces that the class
      * represented by this specification implements.
      */
     Can<ObjectSpecification> interfaces();
 
-    /**
-     * Whether <code>this</code> specification represents the same specification,
-     * or a subclass of the specified <code>other</code> specification.
-     * <p>
-     * <tt>subSpec.isOfType(superSpec)</tt> is equivalent to
-     * {@link Class#isAssignableFrom(Class) Java's}
-     * <tt>superType.isAssignableFrom(subType)</tt>.
-     * @return whether <code>this</code> is <b>instanceof</b> <code>other</code>
-     */
-    boolean isOfType(ObjectSpecification other);
-
-    /**
-     * Same as {@link #isOfType(ObjectSpecification)}, except treating wrapper/primitive the same.
-     */
-    boolean isOfTypeResolvePrimitive(ObjectSpecification other);
-
     /**
      * Get the specification for this specification's class's superclass.
      */
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java
index 3c076ff27d7..0dd2bbf19bf 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java
@@ -77,6 +77,7 @@
 import org.apache.causeway.core.metamodel.spec.feature.ObjectMember;
 import org.jspecify.annotations.NonNull;
 import org.jspecify.annotations.Nullable;
+import org.springframework.util.ClassUtils;
 
 import lombok.experimental.UtilityClass;
 
@@ -649,6 +650,34 @@ default Stream<ObjectSpecification> streamTypeHierarchyAndInterfaces() {
         return Stream.concat(streamTypeHierarchy(), interfaces().stream());
     }
 
+    /**
+     * Whether <code>this</code> specification represents the same specification,
+     * or a subclass of the specified <code>other</code> specification.
+     * <p>
+     * <tt>subSpec.isOfType(superSpec)</tt> is equivalent to
+     * {@link Class#isAssignableFrom(Class) Java's}
+     * <tt>superType.isAssignableFrom(subType)</tt>.
+     * @return whether <code>this</code> is <b>instanceof</b> <code>other</code>
+     */
+    default boolean isOfType(final ObjectSpecification other) {
+    	var thisClass = this.getCorrespondingClass();
+    	var otherClass = other.getCorrespondingClass();
+
+    	return thisClass == otherClass
+    			|| otherClass.isAssignableFrom(thisClass);
+    }
+
+    /**
+     * Same as {@link #isOfType(ObjectSpecification)}, except treating wrapper/primitive the same.
+     */
+    default boolean isOfTypeResolvePrimitive(final ObjectSpecification other) {
+        var thisClass = ClassUtils.resolvePrimitiveIfNecessary(this.getCorrespondingClass());
+        var otherClass = ClassUtils.resolvePrimitiveIfNecessary(other.getCorrespondingClass());
+
+        return thisClass == otherClass
+                || otherClass.isAssignableFrom(thisClass);
+    }
+
     // -- COMMON SUPER TYPE FINDER
 
     /**
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecificationRecord.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecificationRecord.java
index 9ae5991d5df..f2f853eba6c 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecificationRecord.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecificationRecord.java
@@ -109,12 +109,6 @@ public record ObjectSpecificationRecord(
     @Override public Can<ObjectSpecification> interfaces() {
         return hierarchical.interfaces();
     }
-    @Override public boolean isOfType(final ObjectSpecification other) {
-        return hierarchical.isOfType(other);
-    }
-    @Override public boolean isOfTypeResolvePrimitive(final ObjectSpecification other) {
-        return hierarchical.isOfTypeResolvePrimitive(other);
-    }
     @Override public ObjectSpecification superclass() {
         return hierarchical.superclass();
     }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/HierarchicalFactory.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/HierarchicalFactory.java
new file mode 100644
index 00000000000..9b783067bdc
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/HierarchicalFactory.java
@@ -0,0 +1,115 @@
+/*
+ *  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.core.metamodel.spec.impl;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.apache.causeway.applib.annotation.DomainObject;
+import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.commons.internal.reflection._ClassCache;
+import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
+import org.apache.causeway.core.metamodel.services.classsubstitutor.ClassSubstitutorRegistry;
+import org.apache.causeway.core.metamodel.spec.Hierarchical;
+import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
+import org.apache.causeway.core.metamodel.specloader.validator.ValidationFailure;
+import org.jspecify.annotations.Nullable;
+
+record HierarchicalFactory(
+		SpecificationLoaderInternal specLoaderInternal,
+		ClassSubstitutorRegistry classSubstitutorRegistry,
+		/** used for validation messages */
+		FacetHolder facetHolder) {
+
+	Hierarchical createHierarchical(final @Nullable Class<?> cls) {
+		return cls!=null
+			? new HierarchicalRecord
+				(Optional.ofNullable(cls.getSuperclass())
+					.map(specLoaderInternal::loadSpecificationTypeOnly),
+				loadInterfaces(cls))
+			: Hierarchical.EMPTY;
+	}
+
+	// -- HELPER
+
+	private record HierarchicalRecord(
+			Optional<ObjectSpecification> superSpec,
+			Can<ObjectSpecification> interfaceSpecs)
+	implements Hierarchical {
+
+		@Override
+		public Can<ObjectSpecification> interfaces() {
+			return interfaceSpecs;
+		}
+
+		@Override
+		public ObjectSpecification superclass() {
+			return superSpec.orElse(null);
+		}
+
+	}
+
+    private Can<ObjectSpecification> loadInterfaces(final Class<?> cls) {
+    	final Class<?>[] interfaces = cls.getInterfaces();
+
+    	if(interfaces==null)
+			return Can.empty();
+
+    	var classCache = _ClassCache.getInstance();
+
+        final List<ObjectSpecification> interfaceSpecList = Stream.of(interfaces)
+    		// pre-filter common interfaces (performance)
+        	.filter(interfaceType->!interfaceType.getName().startsWith("java."))
+        	//--
+        	.map(interfaceType->{
+        		var substitution = classSubstitutorRegistry.getSubstitution(interfaceType);
+                return substitution.isReplace()
+                		? substitution.replacement()
+        				: substitution.isNeverIntrospect()
+    	    				? null
+    	    				: interfaceType;
+        	})
+        	.filter(Objects::nonNull)
+        	.filter(interfaceType->classCache.head(interfaceType).hasAnnotation(DomainObject.class))
+        	.map(specLoaderInternal::loadSpecificationTypeOnly)
+        	.filter(Objects::nonNull)
+        	.toList();
+
+        if(interfaceSpecList.isEmpty())
+        	return Can.empty();
+
+        if(interfaceSpecList.size()>1) {
+        	ValidationFailure.raiseFormatted(facetHolder,
+        			"Cannot use @DomainObject on more than one interface, as inherited by: %s",
+        			cls.getName());
+        }
+        if (cls.getSuperclass() != null) {
+        	if(classCache.head(cls.getSuperclass()).hasAnnotation(DomainObject.class)) {
+        		ValidationFailure.raiseFormatted(facetHolder,
+        				"Cannot use @DomainObject on both, abstract super class and one interface, as inherited by: %s",
+        				cls.getName());
+        	}
+        }
+
+        return Can.ofCollection(interfaceSpecList);
+    }
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationDefault.java
index 420793fc7aa..9200579df42 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationDefault.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationDefault.java
@@ -18,17 +18,14 @@
  */
 package org.apache.causeway.core.metamodel.spec.impl;
 
-import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Supplier;
-import java.util.stream.Stream;
 
 import org.apache.causeway.applib.Identifier;
-import org.apache.causeway.applib.annotation.DomainObject;
 import org.apache.causeway.applib.annotation.DomainService;
 import org.apache.causeway.applib.annotation.Introspection.IntrospectionPolicy;
 import org.apache.causeway.applib.annotation.ObjectSupport;
@@ -77,12 +74,10 @@
 import org.apache.causeway.core.metamodel.spec.ObjectSpecificationRecord;
 import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
 import org.apache.causeway.core.metamodel.spec.feature.ObjectMember;
-import org.apache.causeway.core.metamodel.specloader.validator.ValidationFailure;
 import org.apache.causeway.core.metamodel.spi.EntityTitleSubscriber;
 import org.apache.causeway.core.metamodel.util.Facets;
 import org.jspecify.annotations.NonNull;
 import org.springframework.util.Assert;
-import org.springframework.util.ClassUtils;
 
 import lombok.Getter;
 import lombok.experimental.Accessors;
@@ -99,7 +94,6 @@ final class ObjectSpecificationDefault
     // -- CONSTRUCTION
 
     private final FacetedMethodsFactory facetedMethodsFactory;
-    private final ClassSubstitutorRegistry classSubstitutorRegistry;
     private final _Lazy<Boolean> isInjectableLazy;
     private final _Lazy<Boolean> isDomainServiceLazy;
 
@@ -120,6 +114,8 @@ final class ObjectSpecificationDefault
     private ActionContainer objectActionContainer = ActionContainer.EMPTY;
     @Getter @Accessors(fluent = true)
     private MemberCatalog memberCatalog = MemberCatalog.EMPTY;
+    @Getter @Accessors(fluent = true)
+    private Hierarchical hierarchical = Hierarchical.EMPTY;
 
     private final _Lazy<Optional<ObjectSpecification>> elementSpecification =
     		_Lazy.threadSafe(()->lookupFacet(TypeOfFacet.class)
@@ -131,14 +127,6 @@ final class ObjectSpecificationDefault
     @Getter @Accessors(fluent = true)
     private final Set<ResolvedMethod> potentialOrphans = new HashSet<>();
 
-    private final List<ObjectSpecification> interfaces = new ArrayList<>();
-
-    // defensive immutable lazy copy of interfaces
-    private final _Lazy<Can<ObjectSpecification>> unmodifiableInterfaces =
-    		_Lazy.threadSafe(()->Can.ofCollection(interfaces));
-
-    private ObjectSpecification superclassSpec;
-
     private ValueFacet<?> valueFacet;
     private EntityFacet entityFacet;
     private ViewModelFacet viewmodelFacet;
@@ -172,7 +160,6 @@ public ObjectSpecificationDefault(
             Identifier.classIdentifier(logicalType()));
 
         this.postProcessor = postProcessor;
-        this.classSubstitutorRegistry = classSubstitutorRegistry;
 
         // must install EncapsulationFacet (if any) and MemberAnnotationPolicyFacet (if any)
         facetProcessor.processObjectType(typeMeta.getCorrespondingClass(), this);
@@ -185,9 +172,12 @@ public ObjectSpecificationDefault(
         this.facetedMethodsFactory =
                 new FacetedMethodsFactory(this, facetProcessor, classSubstitutorRegistry);
 
+        final var hierarchicalFactory =
+        		new HierarchicalFactory(specLoaderInternal(), classSubstitutorRegistry, facetHolder);
+
         this.introspectionStateHandler = new IntrospectionStateHandlerThreadSafe(
         		()->{
-        			introspectTypeHierarchy();
+        			introspectTypeHierarchy(hierarchicalFactory);
         	        invalidateCachedFacets();
         		},
         		()->{
@@ -212,6 +202,8 @@ public ObjectSpecificationDefault(
 	@Override public boolean isParented() { return containsFacet(ParentedCollectionFacet.class); }
 	@Override public boolean isImmutable() { return containsFacet(ImmutableFacet.class); }
 	@Override public boolean isHidden() { return containsFacet(HiddenFacet.class); }
+	@Override public ObjectSpecification superclass() { return hierarchical.superclass(); }
+    @Override public Can<ObjectSpecification> interfaces() { return hierarchical.interfaces(); }
 
     // -- CONTRACT
 
@@ -233,23 +225,17 @@ public String toString() {
                 : superclass().getFullIdentifier());
     }
 
-    private void introspectTypeHierarchy() {
-
+    private void introspectTypeHierarchy(final HierarchicalFactory hierarchicalFactory) {
         facetedMethodsFactory.introspectClass();
 
         // name
         addNamedFacetIfRequired();
 
         // go no further if a value
-        if(this.isValue()) {
-            if (log.isDebugEnabled()) {
-                log.debug("skipping type hierarchy introspection for value type {}", getFullIdentifier());
-            }
-            return;
-        }
+        if(this.isValue())
+			return;
 
-        loadSpecOfSuperclass(getCorrespondingClass().getSuperclass());
-        loadSpecOfInterfaces(getCorrespondingClass().getInterfaces());
+        this.hierarchical = hierarchicalFactory.createHierarchical(getCorrespondingClass());
     }
 
     private void introspectMembers(final MixinSpecStreamer mixinSpecStreamer) {
@@ -328,69 +314,6 @@ public Optional<ObjectSpecification> explicitElementSpec() {
         return elementSpecification.get();
     }
 
-    private void loadSpecOfSuperclass(final Class<?> superclass) {
-        if (superclass == null)
-			return;
-
-        this.superclassSpec = specLoaderInternal().loadSpecificationTypeOnly(superclass);
-        if (superclassSpec != null
-        		&& log.isDebugEnabled()) {
-            log.debug("  Superclass {}", superclass.getName());
-        }
-    }
-
-    private void loadSpecOfInterfaces(final Class<?>[] interfaces) {
-    	if(interfaces==null)
-			return;
-
-    	var classCache = _ClassCache.getInstance();
-
-        final List<ObjectSpecification> interfaceSpecList = Stream.of(interfaces)
-    		// pre-filter common interfaces (performance)
-        	.filter(interfaceType->!interfaceType.getName().startsWith("java."))
-        	//--
-        	.map(interfaceType->{
-        		var substitution = classSubstitutorRegistry.getSubstitution(interfaceType);
-                return substitution.isReplace()
-                		? substitution.replacement()
-        				: substitution.isNeverIntrospect()
-    	    				? null
-    	    				: interfaceType;
-        	})
-        	.filter(Objects::nonNull)
-        	.filter(interfaceType->classCache.head(interfaceType).hasAnnotation(DomainObject.class))
-        	.map(specLoaderInternal()::loadSpecificationTypeOnly)
-        	.filter(Objects::nonNull)
-        	.toList();
-
-        if(!interfaceSpecList.isEmpty()) {
-        	if(interfaceSpecList.size()>1) {
-              ValidationFailure.raiseFormatted(facetHolder,
-            		  "Cannot use @DomainObject on more than one interface, as inherited by: %s",
-            		  getCorrespondingClass().getName());
-        	}
-        	if (superclassSpec != null) {
-        		var superType = superclassSpec.getCorrespondingClass();
-        		if(classCache.head(superType).hasAnnotation(DomainObject.class)) {
-        			ValidationFailure.raiseFormatted(facetHolder,
-                  		  "Cannot use @DomainObject on both, abstract super class and one interface, as inherited by: %s",
-                  		  getCorrespondingClass().getName());
-        		}
-        	}
-
-//debug
-//        	System.err.println("%s".formatted(getCorrespondingClass().getName()));
-//        	interfaceSpecList.forEach(i->{
-//        		System.err.println("- %s".formatted(i.getCorrespondingClass().getName()));
-//        	});
-        	synchronized(unmodifiableInterfaces) {
-                this.interfaces.clear();
-                this.interfaces.addAll(interfaceSpecList);
-                unmodifiableInterfaces.clear();
-            }
-        }
-    }
-
     private void invalidateCachedFacets() {
         this.valueFacet = getFacet(ValueFacet.class);
         this.titleFacet = lookupNonFallbackFacet(TitleFacet.class).orElse(null);
@@ -494,26 +417,6 @@ private Optional<FontAwesomeLayers> faLayers(final ManagedObject domainObject){
             .map(FaLayersProvider::getLayers);
     }
 
-    // -- HIERARCHICAL
-
-    @Override
-    public boolean isOfType(final ObjectSpecification other) {
-        var thisClass = this.getCorrespondingClass();
-        var otherClass = other.getCorrespondingClass();
-
-        return thisClass == otherClass
-                || otherClass.isAssignableFrom(thisClass);
-    }
-
-    @Override
-    public boolean isOfTypeResolvePrimitive(final ObjectSpecification other) {
-        var thisClass = ClassUtils.resolvePrimitiveIfNecessary(this.getCorrespondingClass());
-        var otherClass = ClassUtils.resolvePrimitiveIfNecessary(other.getCorrespondingClass());
-
-        return thisClass == otherClass
-                || otherClass.isAssignableFrom(thisClass);
-    }
-
     // -- NAME, DESCRIPTION, PERSISTABILITY
 
     @Override
@@ -547,23 +450,11 @@ public final Optional<Contributing> contributing() {
 
     @Override
     public <Q extends Facet> Optional<Q> lookupFacet(final Class<Q> facetType) {
-        synchronized(unmodifiableInterfaces) {
-        	return Hierarchical.lookupFacet(facetType, facetHolder, this);
-        }
+        return Hierarchical.lookupFacet(facetType, facetHolder, this);
     }
 
     // -- INHERITED
 
-    @Override
-    public ObjectSpecification superclass() {
-        return superclassSpec;
-    }
-
-    @Override
-    public Can<ObjectSpecification> interfaces() {
-        return unmodifiableInterfaces.get();
-    }
-
     @Override
     public Optional<? extends ObjectMember> getMember(final String memberId) {
     	introspectFully();
@@ -590,7 +481,7 @@ public ObjectSpecificationRecord toUnmodifiable() {
 				typeMeta,
 				getFeatureType(),
 				facetHolder,
-				this,//Hierarchical,
+				hierarchical,
 				objectActionContainer,
 				objectAssociationContainer,
 				getServiceRegistry().select(EntityTitleSubscriber.class),