in core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/FacetProcessor.java [59:405]
record FacetProcessor(
ProgrammingModel programmingModel,
/**
* {@link FacetFactory Facet factories}, in order as provided by the ProgrammingModel.
*/
Can<FacetFactory> factories,
/**
* Class<FacetFactory> => FacetFactory
*/
Map<Class<? extends FacetFactory>, FacetFactory> factoryByFactoryType,
/**
* <pre>ObjectFeatureType => List of FacetFactory</pre>
* <p>
* The lists remain in the same order as the order in
* {@link #factories}.
*/
ListMultimap<FeatureType, FacetFactory> factoriesByFeatureType,
/**
* All method prefixes to check in {@link #recognizes(Method)}.
* <p>
* Derived from factories that implement
* {@link MethodPrefixBasedFacetFactory}.
*/
Set<String> methodPrefixes,
/**
* All registered {@link FacetFactory factories} that implement
* {@link MethodFilteringFacetFactory}.
*/
List<MethodFilteringFacetFactory> methodFilteringFactories,
/**
* {@link FacetFactory factories} that implement {@link AccessorFacetFactory}
* and support properties.
*/
List<AccessorFacetFactory> propertyAccessorFactories,
/**
* {@link FacetFactory factories} that implement {@link AccessorFacetFactory}
* and support collections.
*/
List<AccessorFacetFactory> collectionAccessorFactories,
List<ObjectTypeFacetFactory> objectSpecIfFacetFactoryList
)
implements HasMetaModelContext {
public FacetProcessor(
final @NonNull ProgrammingModel programmingModel) {
this(programmingModel, programmingModel.streamFactories()
.map(programmingModel.getMetaModelContext().getServiceInjector()::injectServicesInto)
.collect(Can.toCan()));
}
private FacetProcessor(
final ProgrammingModel programmingModel,
final Can<FacetFactory> factories) {
this(programmingModel, factories, factories.toMap(FacetFactory::getClass), factoriesByFeatureType(factories));
}
private FacetProcessor(
final ProgrammingModel programmingModel,
final Can<FacetFactory> factories,
final Map<Class<? extends FacetFactory>, FacetFactory> factoryByFactoryType,
final ListMultimap<FeatureType, FacetFactory> factoriesByFeatureType) {
this(programmingModel,
factories,
factoryByFactoryType,
factoriesByFeatureType,
methodPrefixes(factories),
methodFilteringFactories(factories),
propertyAccessorFactories(factories),
collectionAccessorFactories(factories),
objectSpecIfFacetFactoryList(factoriesByFeatureType, factories));
}
@Override
public MetaModelContext getMetaModelContext() {
return programmingModel.getMetaModelContext();
}
/**
* Appends to the supplied {@link Set} all of the {@link Method}s that may
* represent a property or collection.
* <p>
* Delegates to all known
* {@link AccessorFacetFactory}s.
*/
public void findAssociationCandidateGetters(
final Stream<ResolvedMethod> methodStream,
final Consumer<ResolvedMethod> onCandidate) {
methodStream.forEach(method->{
for (var facetFactory : propertyAccessorFactories) {
if (facetFactory.isAssociationAccessor(method)) {
onCandidate.accept(method);
return; // first wins
}
}
for (var facetFactory : collectionAccessorFactories) {
if (facetFactory.isAssociationAccessor(method)) {
onCandidate.accept(method);
return; // first wins
}
}
});
}
/**
* Use the provided {@link MethodRemover} to call all known
* {@link AccessorFacetFactory}s to remove all
* property accessors and append them to the supplied methodList.
*/
public List<ResolvedMethod> findAndRemovePropertyAccessors(
final MethodRemover methodRemover) {
var propertyAccessors = new ArrayList<ResolvedMethod>();
for (var facetFactory : propertyAccessorFactories) {
methodRemover.removeMethods(facetFactory::isAssociationAccessor, propertyAccessors::add);
}
return propertyAccessors;
}
/**
* Use the provided {@link MethodRemover} to call all known
* {@link AccessorFacetFactory}s to remove all
* collection accessors and append them to the supplied methodList.
*/
public List<ResolvedMethod> findAndRemoveCollectionAccessors(
final MethodRemover methodRemover) {
var collectionAccessors = new ArrayList<ResolvedMethod>();
for (var facetFactory : collectionAccessorFactories) {
methodRemover.removeMethods(facetFactory::isAssociationAccessor, collectionAccessors::add);
}
return collectionAccessors;
}
/**
* Whether this {@link Method method} is recognized by any of the
* {@link FacetFactory}s.
* <p>
* Typically this is when method has a specific prefix, such as
* <tt>validate</tt> or <tt>hide</tt>. Specifically, it checks:
* <ul>
* <li>the method's prefix against the prefixes supplied by any
* {@link MethodPrefixBasedFacetFactory}</li>
* <li>the method against any {@link MethodFilteringFacetFactory}</li>
* </ul>
* <p>
* The design of {@link MethodPrefixBasedFacetFactory} (whereby this facet
* factory set does the work) is a slight performance optimization for when
* there are multiple facet factories that search for the same prefix.
*/
public boolean recognizes(final ResolvedMethod method) {
var methodName = method.name();
for (var prefix : methodPrefixes) {
if (methodName.startsWith(prefix)) return true;
}
for (var factory : methodFilteringFactories) {
if (factory.recognizes(method)) return true;
}
return false;
}
public void processObjectType(final Class<?> cls, final FacetHolder facetHolder) {
for (var facetFactory : objectSpecIfFacetFactoryList()) {
facetFactory.process(new ProcessObjectTypeContext(cls, facetHolder));
}
}
/**
* Attaches all facets applicable to the provided {@link FeatureType#OBJECT
* object}) to the supplied {@link FacetHolder}.
* <p>
* Delegates to {@link FacetFactory#process(FacetFactory.ProcessClassContext)} for each
* appropriate factory.
*
* @see FacetFactory#process(ProcessClassContext)
*
* @param cls
* - class to process
* @param facetHolder
* - holder to attach facets to.
*/
public void process(
final Class<?> cls,
final IntrospectionPolicy introspectionPolicy,
final MethodRemover methodRemover,
final FacetHolder facetHolder) {
var ctx = new ProcessClassContext(
cls,
introspectionPolicy,
removerElseNoopRemover(methodRemover),
facetHolder);
factoriesByFeatureType.getOrElseEmpty(FeatureType.OBJECT)
.forEach(facetFactory->facetFactory.process(ctx));
}
/**
* Attaches all facets applicable to the provided {@link FeatureType type of
* feature} to the supplied {@link FacetHolder}.
* <p>
* Delegates to {@link FacetFactory#process(FacetFactory.ProcessMethodContext)} for each
* appropriate factory.
*
* @param cls
* - class in which introspect; allowing the helper methods to be
* found is subclasses of that which the method was originally
* found.
* @param method
* - method to process
* @param facetedMethod
* - holder to attach facets to.
* @param featureType
* - what type of feature the method represents (property,
* action, collection etc)
* @param isMixinMain
* - Whether we are currently processing a mixin type AND this context's method
* can be identified as the main method of the processed mixin class. (since 2.0)
*/
public void process(
final Class<?> cls,
final IntrospectionPolicy introspectionPolicy,
final MethodFacade method,
final MethodRemover methodRemover,
final FacetedMethod facetedMethod,
final FeatureType featureType,
final boolean isMixinMain) {
var processMethodContext =
new ProcessMethodContext(
cls,
introspectionPolicy,
featureType,
method,
removerElseNoopRemover(methodRemover), facetedMethod, isMixinMain);
for (FacetFactory facetFactory : factoriesByFeatureType.getOrElseEmpty(featureType)) {
facetFactory.process(processMethodContext);
}
}
public void processMemberOrder(final ObjectMember facetHolder) {
}
/**
* Attaches all facets applicable to the provided parameter to the supplied
* {@link FacetHolder}.
* <p>
* Delegates to {@link FacetFactory#processParams(ProcessParameterContext)}
* for each appropriate factory.
*
* @see FacetFactory#processParams(ProcessParameterContext)
*
* @param introspectedClass
* @param method
* - action method to process
* @param methodRemover
* @param facetedMethodParameter
*/
public void processParams(
final Class<?> introspectedClass,
final IntrospectionPolicy introspectionPolicy,
final MethodFacade method,
final MethodRemover methodRemover,
final FacetedMethodParameter facetedMethodParameter) {
var processParameterContext =
new ProcessParameterContext(introspectedClass, introspectionPolicy,
method, methodRemover, facetedMethodParameter);
FeatureType.PARAMETERS_ONLY.stream()
.map(factoriesByFeatureType::getOrElseEmpty)
.flatMap(List::stream)
.collect(Collectors.toSet())
.forEach(facetFactory->facetFactory.processParams(processParameterContext));
}
// -- INITIALIZERS
private static ListMultimap<FeatureType, FacetFactory> factoriesByFeatureType(final Iterable<FacetFactory> factories) {
var factoryListByFeatureType = _Multimaps.<FeatureType, FacetFactory>newListMultimap();
for (var factory : factories) {
factory.getFeatureTypes().forEach(featureType->
factoryListByFeatureType.putElement(featureType, factory));
}
return factoryListByFeatureType;
}
private static Set<String> methodPrefixes(final Iterable<FacetFactory> factories) {
var cachedMethodPrefixes = _Sets.<String>newHashSet();
for (var facetFactory : factories) {
if (facetFactory instanceof MethodPrefixBasedFacetFactory methodPrefixBasedFacetFactory) {
methodPrefixBasedFacetFactory.getPrefixes().forEach(cachedMethodPrefixes::add);
}
}
return cachedMethodPrefixes;
}
private static List<MethodFilteringFacetFactory> methodFilteringFactories(final Iterable<FacetFactory> factories) {
var methodFilteringFactories = new ArrayList<MethodFilteringFacetFactory>();
for (var factory : factories) {
if (factory instanceof MethodFilteringFacetFactory methodFilteringFacetFactory) {
methodFilteringFactories.add(methodFilteringFacetFactory);
}
}
return methodFilteringFactories;
}
private static List<AccessorFacetFactory> propertyAccessorFactories(final Iterable<FacetFactory> factories) {
var propertyOrCollectionIdentifyingFactories = new ArrayList<AccessorFacetFactory>();
for (var factory : factories) {
if (factory instanceof AccessorFacetFactory accessorFacetFactory) {
if(!accessorFacetFactory.supportsProperties()) continue;
propertyOrCollectionIdentifyingFactories.add(accessorFacetFactory);
}
}
return propertyOrCollectionIdentifyingFactories;
}
private static List<AccessorFacetFactory> collectionAccessorFactories(final Iterable<FacetFactory> factories) {
var propertyOrCollectionIdentifyingFactories = new ArrayList<AccessorFacetFactory>();
for (var factory : factories) {
if (factory instanceof AccessorFacetFactory accessorFacetFactory) {
if(!accessorFacetFactory.supportsCollections()) continue;
propertyOrCollectionIdentifyingFactories.add(accessorFacetFactory);
}
}
return propertyOrCollectionIdentifyingFactories;
}
private static List<ObjectTypeFacetFactory> objectSpecIfFacetFactoryList(
final ListMultimap<FeatureType, FacetFactory> factoryListByFeatureType,
final Iterable<FacetFactory> factories) {
var facetFactories = new ArrayList<ObjectTypeFacetFactory>();
factoryListByFeatureType.getOrElseEmpty(FeatureType.OBJECT)
.forEach(facetFactory->{
if (facetFactory instanceof ObjectTypeFacetFactory objectTypeFacetFactory) {
facetFactories.add(objectTypeFacetFactory);
}
});
return Collections.unmodifiableList(facetFactories);
}
// -- HELPER
private static MethodRemover removerElseNoopRemover(final MethodRemover methodRemover) {
return methodRemover != null ? methodRemover : MethodRemover.NOOP;
}
}