Skip to content

Commit

Permalink
CAUSEWAY-2297: move attribute enum to class cache
Browse files Browse the repository at this point in the history
  • Loading branch information
andi-huber committed Nov 12, 2024
1 parent b50ee26 commit d09ede6
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.causeway.commons.internal.reflection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
Expand All @@ -37,6 +38,7 @@

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -80,6 +82,17 @@
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public final class _ClassCache implements AutoCloseable {

public enum Attribute {
/**
* Corresponds to the bean name of Spring managed beans.
*/
SPRING_NAMED,
/**
* Corresponds to DomainObject#mixinMethod().
*/
MIXIN_MAIN_METHOD_NAME;
}

public static _ClassCache getInstance() {
return _Context.computeIfAbsent(_ClassCache.class, ()->new _ClassCache(_Context.getDefaultClassLoader()));
Expand Down Expand Up @@ -107,14 +120,14 @@ public boolean isNamed(final Class<?> type) {
return classModel(type).head().named()!=null;
}

public void setNamed(Class<?> beanClass, String beanDefinitionName) {
public void setSpringNamed(Class<?> beanClass, String beanDefinitionName) {
_Assert.assertNotEmpty(beanDefinitionName);
classModel(beanClass).attributeMap().put("NAMED", beanDefinitionName);
classModel(beanClass).head().attributeMap().put(Attribute.SPRING_NAMED, beanDefinitionName);
}

public String getLogicalName(final Class<?> type) {
var model = classModel(type);
return Optional.ofNullable(model.attributeMap().get("NAMED"))
return Optional.ofNullable(model.head().attributeMap().get(Attribute.SPRING_NAMED))
.or(()->Optional.ofNullable(model.head().named()))
.or(()->Optional.ofNullable(type.getCanonicalName()))
.orElseGet(type::getName);
Expand Down Expand Up @@ -244,49 +257,47 @@ public Stream<ResolvedMethod> streamDeclaredMethodsHaving(
}
}

// -- ATTRIBUTES

public _ClassCache setAttribute(
final Class<?> type,
final String attributeName,
final String value) {
var classModel = classModel(type);
classModel.attributeMap.put(attributeName, value);
return this;
}

public Optional<String> lookupAttribute(
final Class<?> type,
final String attributeName) {
var classModel = classModel(type);
return Optional.ofNullable(classModel.attributeMap.get(attributeName));
}

// -- IMPLEMENATION DETAILS

public record ClassModelHead(
MergedAnnotations mergedAnnotations,
/** explicit name if any */
@Nullable String named) {
@Nullable String named,
Map<Attribute, String> attributeMap) {

static ClassModelHead create(final Class<?> type) {
var mergedAnnotations = MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY);
return new ClassModelHead(mergedAnnotations, _ClassCacheUtil.inferName(type, mergedAnnotations));
return new ClassModelHead(mergedAnnotations, _ClassCacheUtil.inferName(type, mergedAnnotations),
new ConcurrentHashMap<>());
}

/**
* @see MergedAnnotations#get(Class)
*/
public <A extends Annotation> MergedAnnotation<A> annotation(Class<A> annotationType) {
return mergedAnnotations.get(annotationType);
}

/**
* whether type is annotated with annotationType
*/
public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
return mergedAnnotations.get(annotationType).isPresent();
}

/**
* whether type is annotated with {@link BeanInternal} or {@link Configuration}
*/
public boolean hasInternalBeanSemantics() {
return mergedAnnotations.get(BeanInternal.class).isPresent()
|| mergedAnnotations.get(Configuration.class).isPresent();
return hasAnnotation(BeanInternal.class)
|| hasAnnotation(Configuration.class);
}

/**
* whether type is annotated with {@link XmlRootElement}
*/
public boolean hasJaxbRootElementSemantics() {
return mergedAnnotations.get(XmlRootElement.class).isPresent();
return hasAnnotation(XmlRootElement.class);
}

/**
Expand All @@ -308,27 +319,23 @@ private record ClassModel(
Map<MethodKey, ResolvedMethod> resolvedMethodsByKey,
Map<MethodKey, ResolvedMethod> publicMethodsByKey,
Map<MethodKey, ResolvedMethod> postConstructMethodsByKey,

Map<String, Can<ResolvedMethod>> declaredMethodsByAttribute,
Map<String, String> attributeMap
) {

Map<String, Can<ResolvedMethod>> declaredMethodsByAttribute) {

ClassModel(
ClassModelHead head,
Can<Field> declaredFields) {
this(head,
declaredFields,
new HashMap<>(), new HashMap<>(), new HashMap<>(),
new HashMap<>(), new HashMap<>(), new HashMap<>(),
new ConcurrentHashMap<>());
new HashMap<>(), new HashMap<>(), new HashMap<>());
}

public static ClassModel headOnly(ClassModelHead head) {
return new ClassModel(head,
Can.empty(),
Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(),
Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(),
Collections.emptyMap());
Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap());
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import java.io.Serializable;
import java.lang.reflect.Modifier;
import java.util.Optional;
import java.util.function.Supplier;

import jakarta.persistence.Entity;
Expand Down Expand Up @@ -50,27 +49,6 @@ public record CausewayBeanTypeClassifier(
@NonNull Can<String> activeProfiles,
@NonNull _ClassCache classCache) {

public enum Attributes {
/**
* Corresponds to presence of a {@link DomainService} annotation.
* @see _ClassCache#lookupAttribute(Class, String)
*/
HAS_DOMAIN_SERVICE_SEMANTICS,

/**
* Corresponds to {@link DomainObject#mixinMethod()}.
* @see _ClassCache#lookupAttribute(Class, String)
*/
MIXIN_MAIN_METHOD_NAME;

public void set(final _ClassCache classCache, final Class<?> type, final String attributeValue) {
classCache.setAttribute(type, this.name(), attributeValue);
}
public Optional<String> lookup(final _ClassCache classCache, final Class<?> type) {
return classCache.lookupAttribute(type, this.name());
}
}

// -- CONSTRUCTION

CausewayBeanTypeClassifier(final ApplicationContext applicationContext) {
Expand Down Expand Up @@ -133,51 +111,45 @@ public CausewayBeanMetaData classify(@NonNull final LogicalType logicalType, fin
return CausewayBeanMetaData.notManaged(discoveredBy, BeanSort.VETOED, named.get()); // reject
}

// handle value types ...

var aValue = _Annotations.synthesize(type, org.apache.causeway.applib.annotation.Value.class)
.orElse(null);
if(aValue!=null) {
return CausewayBeanMetaData.notManaged(discoveredBy, BeanSort.VALUE, named.get());
//[CAUSEWAY-3585] when implements ViewModel, then don't consider alternatives, yield VIEW_MODEL
if(org.apache.causeway.applib.ViewModel.class.isAssignableFrom(type)) {
return CausewayBeanMetaData.causewayManaged(discoveredBy, BeanSort.VIEW_MODEL, named.get());
}

// handle internal bean types ...
var typeHead = classCache().head(type);

if(classCache.head(type).hasInternalBeanSemantics()) {
// value types
if(typeHead.hasAnnotation(org.apache.causeway.applib.annotation.Value.class)) {
return CausewayBeanMetaData.notManaged(discoveredBy, BeanSort.VALUE, named.get());
}

// internal bean types
if(typeHead.hasInternalBeanSemantics()) {
return CausewayBeanMetaData.springManaged(discoveredBy, BeanSort.MANAGED_BEAN_NOT_CONTRIBUTING, logicalType);
}

// handle actual bean types ...

var aDomainService = _Annotations.synthesize(type, DomainService.class);
if(aDomainService.isPresent()) {
Attributes.HAS_DOMAIN_SERVICE_SEMANTICS.set(classCache, type, "true");
// domain service
if(typeHead.hasAnnotation(DomainService.class)) {
return CausewayBeanMetaData.springManaged(discoveredBy, BeanSort.MANAGED_BEAN_CONTRIBUTING, logicalType);
}

//[CAUSEWAY-3585] when implements ViewModel, then don't consider alternatives, yield VIEW_MODEL
if(org.apache.causeway.applib.ViewModel.class.isAssignableFrom(type)) {
return CausewayBeanMetaData.causewayManaged(discoveredBy, BeanSort.VIEW_MODEL, named.get());
}

// JDO entity support
if(classCache.head(type).isJdoPersistenceCapable()){
// entity support
if(typeHead.isJdoPersistenceCapable()){
return CausewayBeanMetaData.entity(discoveredBy, PersistenceStack.JDO, named.get());
}

var entityAnnotation = _Annotations.synthesize(type, Entity.class).orElse(null);
if(entityAnnotation!=null) {
if(typeHead.hasAnnotation(Entity.class)) {
return CausewayBeanMetaData.entity(discoveredBy, PersistenceStack.JPA, named.get());
}

}

// domain object
var aDomainObject = _Annotations.synthesize(type, DomainObject.class).orElse(null);
if(aDomainObject!=null) {
switch (aDomainObject.nature()) {
case BEAN:
return CausewayBeanMetaData.unspecified(discoveredBy, BeanSort.MANAGED_BEAN_CONTRIBUTING, named.get());
case MIXIN:
// memoize mixin main name
Attributes.MIXIN_MAIN_METHOD_NAME.set(classCache, type, aDomainObject.mixinMethod());
typeHead.attributeMap().put(_ClassCache.Attribute.MIXIN_MAIN_METHOD_NAME, aDomainObject.mixinMethod());
return CausewayBeanMetaData.causewayManaged(discoveredBy, BeanSort.MIXIN, named.get());
case ENTITY:
return CausewayBeanMetaData.entity(discoveredBy, PersistenceStack.UNSPECIFIED, named.get());
Expand All @@ -188,11 +160,11 @@ public CausewayBeanMetaData classify(@NonNull final LogicalType logicalType, fin
}
}

if(_ClassCache.getInstance().head(type).hasJaxbRootElementSemantics()) {
if(typeHead.hasJaxbRootElementSemantics()) {
return CausewayBeanMetaData.causewayManaged(discoveredBy, BeanSort.VIEW_MODEL, named.get());
}

if(_Annotations.isPresent(type, Component.class)) {
if(typeHead.hasAnnotation(Component.class)) {
return CausewayBeanMetaData.unspecified(discoveredBy, BeanSort.MANAGED_BEAN_NOT_CONTRIBUTING, logicalType);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ void collect(final String beanDefinitionName) {
case UNSPECIFIED -> {
if(isRenamed) {
//rename(beanDefinition, beanDefinitionName, typeMeta.logicalType().logicalName()); //TODO does not work yet
_ClassCache.getInstance().setNamed(beanClass, beanDefinitionName);
_ClassCache.getInstance().setSpringNamed(beanClass, beanDefinitionName);
}
}
case SPRING -> {
if(isRenamed) {
// renaming not allowed, report back to class-cache
_ClassCache.getInstance().setNamed(beanClass, beanDefinitionName);
_ClassCache.getInstance().setSpringNamed(beanClass, beanDefinitionName);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
import jakarta.inject.Inject;

import org.apache.causeway.commons.internal.functions._Predicates;
import org.apache.causeway.commons.internal.reflection._ClassCache.Attribute;
import org.apache.causeway.commons.internal.reflection._GenericResolver.ResolvedMethod;
import org.apache.causeway.core.config.beans.CausewayBeanTypeClassifier.Attributes;
import org.apache.causeway.core.config.progmodel.ProgrammingModelConstants;
import org.apache.causeway.core.metamodel.context.MetaModelContext;
import org.apache.causeway.core.metamodel.facetapi.FeatureType;
Expand Down Expand Up @@ -100,8 +100,7 @@ private Predicate<ResolvedMethod> isMixinMainMethod(final @NonNull ProcessClassC
}
// lookup attribute from class-cache as it should have been already processed by the BeanTypeClassifier
var cls = processClassContext.getCls();
var mixinMainMethodName = Attributes.MIXIN_MAIN_METHOD_NAME.lookup(getClassCache(), cls)
.orElse(null);
var mixinMainMethodName = getClassCache().head(cls).attributeMap().get(Attribute.MIXIN_MAIN_METHOD_NAME);
return method->method.name().equals(mixinMainMethodName);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.springframework.lang.Nullable;

import org.apache.causeway.applib.Identifier;
import org.apache.causeway.applib.annotation.DomainService;
import org.apache.causeway.applib.annotation.Introspection.IntrospectionPolicy;
import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.commons.collections.ImmutableEnumSet;
Expand All @@ -41,7 +42,6 @@
import org.apache.causeway.commons.internal.reflection._MethodFacades.MethodFacade;
import org.apache.causeway.commons.internal.reflection._Reflect;
import org.apache.causeway.core.config.beans.CausewayBeanMetaData;
import org.apache.causeway.core.config.beans.CausewayBeanTypeClassifier.Attributes;
import org.apache.causeway.core.metamodel.commons.ToString;
import org.apache.causeway.core.metamodel.context.MetaModelContext;
import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
Expand Down Expand Up @@ -353,9 +353,7 @@ public boolean isInjectable() {
}

private _Lazy<Boolean> isDomainServiceLazy = _Lazy.threadSafe(()->
Attributes.HAS_DOMAIN_SERVICE_SEMANTICS.lookup(_ClassCache.getInstance(), getCorrespondingClass())
.map("true"::equals)
.orElse(false));
_ClassCache.getInstance().head(getCorrespondingClass()).hasAnnotation(DomainService.class));

@Override
public boolean isDomainService() {
Expand Down

0 comments on commit d09ede6

Please sign in to comment.