This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch 3971-layout.switching
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/3971-layout.switching by this
push:
new 107c509ca0b CAUSEWAY-3971: implements FacetRank (failing tests)
107c509ca0b is described below
commit 107c509ca0b1c72f6cceaa9e9b4d31fa3af6c659
Author: andi-huber <[email protected]>
AuthorDate: Tue Mar 3 09:44:25 2026 +0100
CAUSEWAY-3971: implements FacetRank (failing tests)
captures the various lanes per facet rank
hello world reproducer is looking good now
---
.../core/metamodel/facetapi/FacetHolderSimple.java | 7 +-
.../core/metamodel/facetapi/FacetRank.java | 114 +++++++++++
.../core/metamodel/facetapi/FacetRanking.java | 220 ++++++++++++---------
.../core/metamodel/facetapi/FacetUtil.java | 59 +++---
.../core/metamodel/facetapi/QualifiedFacet.java | 35 ++++
...ObjectLayoutAnnotationUsingCssClassUiEvent.java | 4 +-
...mainObjectLayoutAnnotationUsingIconUiEvent.java | 4 +-
...ainObjectLayoutAnnotationUsingTitleUiEvent.java | 5 +-
.../object/layout/LayoutPrefixFacetForUiEvent.java | 2 +-
...udeAnnotationEnforcesMetamodelContribution.java | 5 +-
.../metamodel/services/grid/GridLoadingTest.java | 2 +-
.../menubars/bootstrap/MenuBarsServiceBSTest.java | 5 +-
12 files changed, 315 insertions(+), 147 deletions(-)
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/FacetHolderSimple.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/FacetHolderSimple.java
index 09cf4f42ea0..ba4f5bb4492 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/FacetHolderSimple.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/FacetHolderSimple.java
@@ -27,7 +27,6 @@
import org.apache.causeway.applib.Identifier;
import org.apache.causeway.core.metamodel.context.MetaModelContext;
-import org.apache.causeway.core.metamodel.util.Facets;
/**
* Provides a (simple) list of {@link Facet}s.
@@ -87,11 +86,10 @@ public void addFacet(final @NonNull Facet facet) {
@Override
public <T extends Facet> T getFacet(final Class<T> facetType) {
- var qualifier = Facets.qualifier(this);
synchronized(rankingByType) {
return getFacetRanking(facetType)
- .flatMap(facetRanking->facetRanking.getWinner(facetType,
qualifier))
+ .flatMap(facetRanking->facetRanking.getWinner(facetType))
.orElse(null);
//return uncheckedCast(snapshot.get().get(facetType));
@@ -100,12 +98,11 @@ public <T extends Facet> T getFacet(final Class<T>
facetType) {
@Override
public Stream<Facet> streamFacets() {
- var qualifier = Facets.qualifier(this);
synchronized(rankingByType) {
// consumers should play nice and don't take too long (as we have
a lock)
//return snapshot.get().values().stream();
return streamFacetRankings()
-
.flatMap(facetRanking->facetRanking.getWinner(facetRanking.facetType(),
qualifier)
+
.flatMap(facetRanking->facetRanking.getWinner(facetRanking.facetType())
.stream());
}
}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/FacetRank.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/FacetRank.java
new file mode 100644
index 00000000000..5d58c877d72
--- /dev/null
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/FacetRank.java
@@ -0,0 +1,114 @@
+/*
+ * 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.facetapi;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.jspecify.annotations.Nullable;
+
+import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.commons.internal.assertions._Assert;
+import org.apache.causeway.commons.internal.collections._Lists;
+import org.apache.causeway.commons.internal.collections._Multimaps;
+import org.apache.causeway.core.metamodel.facetapi.QualifiedFacet.Key;
+
+/**
+ * Multiple {@link FacetRank}(s) are collected into a single {@link
FacetRanking}.
+ *
+ * @apiNote not thread-safe
+ */
+record FacetRank(
+ Class<? extends Facet> facetType,
+ Facet.Precedence precedence,
+ _Multimaps.ListMultimap<QualifiedFacet.Key, Facet> facetsByQualifier) {
+
+ FacetRank(
+ final Class<? extends Facet> facetType,
+ final Facet.Precedence precedence) {
+ this(facetType, precedence, _Multimaps.newListMultimap());
+ //this(facetType, precedence,
_Multimaps.newListMultimap(ConcurrentSkipListMap::new,
CopyOnWriteArrayList::new));
+ }
+
+ QualifiedFacet.Key key(final @Nullable String qualifier) {
+ return new QualifiedFacet.Key(facetType, qualifier);
+ }
+
+ FacetRank add(final @Nullable Facet facet) {
+ if(facet==null)
+ return this; // no-op
+
+ _Assert.assertEquals(this.precedence(), facet.precedence());
+ _Assert.assertEquals(this.facetType(), facet.facetType());
+
+ facetsByQualifier.putElement(QualifiedFacet.Key.forFacet(facet),
facet);
+ return this;
+ }
+
+ /**
+ * Whether this rank contains at least one matching {@link QualifiedFacet}.
+ */
+ boolean matches(final String qualifier) {
+ return facetsByQualifier.containsKey(key(qualifier));
+ }
+
+ Can<Facet> facetsMatching(final Key key) {
+ return lookupQualified(key)
+ .filter(list->!list.isEmpty())
+ .or(()->lookupUnqualified(key)
+ .filter(list->!list.isEmpty()))
+ .map(Can::ofCollection)
+ .orElseGet(Can::empty);
+ }
+
+ /**
+ * Rules in order of strength:
+ * <ul>
+ * <li>all matching {@link QualifiedFacet}(s) take precedence</li>
+ * <li>all non-matching {@link QualifiedFacet}(s) must be ignored</li>
+ * <li>later take precedence over earlier</li>
+ * </ul>
+ */
+ Optional<Facet> findBest(final QualifiedFacet.Key key) {
+ return lookupQualified(key)
+ .flatMap(_Lists::lastElement)
+ .or(()->lookupUnqualified(key)
+ .flatMap(_Lists::lastElement));
+ }
+
+ boolean hasBest(final QualifiedFacet.Key key) {
+ return isNotEmpty(lookupQualified(key))
+ || isNotEmpty(lookupUnqualified(key));
+ }
+
+ // -- HELPER
+
+ private Optional<List<Facet>> lookupQualified(final QualifiedFacet.Key
key) {
+ return Optional.ofNullable(facetsByQualifier.get(key.toQualified()));
+ }
+ private Optional<List<Facet>> lookupUnqualified(final QualifiedFacet.Key
key) {
+ return Optional.ofNullable(facetsByQualifier.get(key.toUnqualified()));
+ }
+ private static <T> boolean isNotEmpty(final Optional<List<T>> listOpt) {
+ return listOpt
+ .map(list->!list.isEmpty())
+ .orElse(false);
+ }
+
+}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/FacetRanking.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/FacetRanking.java
index 823b8999531..17a4353f13e 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/FacetRanking.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/FacetRanking.java
@@ -18,12 +18,14 @@
*/
package org.apache.causeway.core.metamodel.facetapi;
-import java.util.Comparator;
+import java.util.Map;
+import java.util.NavigableMap;
import java.util.Objects;
import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
-import java.util.function.Predicate;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
@@ -31,11 +33,9 @@
import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.commons.internal.assertions._Assert;
import org.apache.causeway.commons.internal.base._Casts;
-import org.apache.causeway.commons.internal.base._Reduction;
-import org.apache.causeway.commons.internal.collections._Lists;
-import org.apache.causeway.commons.internal.collections._Multimaps;
import org.apache.causeway.commons.internal.exceptions._Exceptions;
import org.apache.causeway.core.metamodel.facetapi.Facet.Precedence;
+import org.apache.causeway.core.metamodel.util.Facets;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@@ -62,11 +62,10 @@ public final class FacetRanking {
@Getter @Accessors(fluent = true) private final @NonNull Class<? extends
Facet> facetType;
- private final _Multimaps.@NonNull ListMultimap<Facet.Precedence, Facet>
facetsByPrecedence
- = _Multimaps.newSortedConcurrentListMultimap();
+ private final NavigableMap<Facet.Precedence, FacetRank> ranksByPrecedence
= new ConcurrentSkipListMap<>();
private final @NonNull AtomicReference<Facet> eventFacetRef = new
AtomicReference<>();
- private final @NonNull AtomicReference<Precedence> topPrecedenceRef = new
AtomicReference<>();
+ private final @NonNull Map<QualifiedFacet.Key, Precedence>
topPrecedenceRef = new ConcurrentHashMap<>();
/**
* @return whether the top rank changed,
@@ -79,6 +78,7 @@ public boolean add(final @NonNull Facet facet) {
// guard against invalidly mocked facets
var facetPrecedence = Objects.requireNonNull(facet.precedence(),
()->String.format("facet %s declares no precedence",
facet.getClass()));
+ var key = QualifiedFacet.Key.forFacet(facet);
// handle top priority (EVENT) facets separately
if(facetPrecedence.isEvent()) {
@@ -91,7 +91,8 @@ public boolean add(final @NonNull Facet facet) {
facet.getClass());
return facet;
});
- topPrecedenceRef.set(facetPrecedence);
+
+ topPrecedenceRef.put(key, facetPrecedence);
return true; // changes apply
}
@@ -100,12 +101,12 @@ public boolean add(final @NonNull Facet facet) {
.orElse(-1);
if(facetPrecedence.ordinal() > currentTopOrdinal) {
- topPrecedenceRef.set(facetPrecedence);
+ topPrecedenceRef.put(key, facetPrecedence);
}
- var currentTopRankOrdinal = facetsByPrecedence.isEmpty()
+ var currentTopRankOrdinal = ranksByPrecedence.isEmpty() //FIXME needs
context
? -1
- :
facetsByPrecedence.asNavigableMapElseFail().lastKey().ordinal();
+ : ranksByPrecedence.lastKey().ordinal();
var changesTopRank = facetPrecedence.ordinal() >=
currentTopRankOrdinal;
@@ -114,16 +115,18 @@ public boolean add(final @NonNull Facet facet) {
// However, there are use-cases, where access to all facets of a given
type are required,
// regardless of facet-precedence (eg. MemberNamedFacets).
if(changesTopRank
- || facet.isPopulateAllFacetRanks()) {
- facetsByPrecedence.putElement(facetPrecedence, facet);
+ || facet.isPopulateAllFacetRanks()) {
+ var rank = ranksByPrecedence
+ .computeIfAbsent(facetPrecedence, __->new
FacetRank(facetType(), facetPrecedence));
+ rank.add(facet);
}
return changesTopRank;
}
public void addAll(final @NonNull FacetRanking facetRanking) {
- facetRanking.facetsByPrecedence.asNavigableMapElseFail()
- .forEach((k, facets)->facets.forEach(this::add));
+ facetRanking.ranksByPrecedence
+ .forEach((k,
rank)->rank.facetsByQualifier().streamElements().forEach(this::add));
}
/**
@@ -132,12 +135,11 @@ public void addAll(final @NonNull FacetRanking
facetRanking) {
* @param facetType - for convenience, so the caller does not need to cast
the result
*/
public <F extends Facet> Optional<F> getWinner(
- final @NonNull Class<F> facetType,
- final @Nullable String qualifier) {
+ final @NonNull Class<F> facetType) {
var eventFacet = getEventFacet(facetType);
- if(eventFacet.isPresent())
- return eventFacet;
- return getWinnerNonEvent(facetType, qualifier);
+ return eventFacet.isPresent()
+ ? eventFacet
+ : getWinnerNonEvent(facetType);
}
/**
@@ -146,30 +148,11 @@ public <F extends Facet> Optional<F> getWinner(
* @param facetType - for convenience, so the caller does not need to cast
the result
*/
public <F extends Facet> Optional<F> getWinnerNonEvent(
- final @NonNull Class<F> facetType,
- final @Nullable String qualifier) {
- var topRank = getTopRank(facetType);
- // all matching QualifiedFacet(s) take precedence
- // all non-matching QualifiedFacet(s) must be ignored
- // historically (initial design) the last one wins
-
- F bestSoFar = null;
-
- for(var facet : topRank) {
- if(facet instanceof QualifiedFacet qFacet) {
- if(Objects.equals(qualifier, qFacet.qualifier()))
- return Optional.of(facet);
- else {
- continue;
- }
- }
- bestSoFar = facet;
- }
-
- //FIXME if bestSoFar == null, then we have to go to the next lower
rank;
- //FIXME the getTopRank(facetType) logic is broken, because it depends
now on context
-
- return Optional.ofNullable(bestSoFar);
+ final @NonNull Class<F> facetType) {
+ var topRank = topRankInternal();
+ return topRank!=null
+ ? (Optional<F>) topRank.findBest(key())
+ : Optional.empty();
}
/**
@@ -180,14 +163,12 @@ public <F extends Facet> Optional<F> getWinnerNonEvent(
*/
public <F extends Facet> Optional<F> getWinnerNonEventLowerOrEqualTo(
final @NonNull Class<F> facetType,
- final @NonNull Precedence precedenceUpper
- //,
- //final @Nullable String qualifier
- ) {
+ final @NonNull Precedence precedenceUpper) {
+ var key = key();
var selectedRank = getHighestPrecedenceLowerOrEqualTo(precedenceUpper);
return selectedRank
- .map(facetsByPrecedence::get)
- .map(_Lists::lastElementIfAny) // historically the last one
wins
+ .map(ranksByPrecedence::get)
+ .flatMap(rank->rank.findBest(key))
.map(_Casts::uncheckedCast);
}
@@ -205,10 +186,12 @@ public <F extends Facet> Optional<F> getEventFacet(final
@NonNull Class<F> facet
*/
public <F extends Facet> Can<F> getTopRank(final @NonNull Class<F>
facetType) {
_Assert.assertEquals(this.facetType, facetType);
- var topRankedFacets =
facetsByPrecedence.asNavigableMapElseFail().lastEntry();
- return topRankedFacets!=null
- ?
Can.<F>ofCollection(_Casts.uncheckedCast(topRankedFacets.getValue()))
- : Can.empty();
+ var key = key();
+ for(var rank : ranksByPrecedence.descendingMap().values()) {
+ if(rank.hasBest(key))
+ return (Can<F>) rank.facetsMatching(key);
+ }
+ return Can.empty();
}
/**
@@ -221,12 +204,16 @@ public <F extends Facet> Can<F> getRankLowerOrEqualTo(
final @NonNull Precedence precedenceUpper) {
_Assert.assertEquals(this.facetType, facetType);
+ var key = key();
var precedenceSelected =
getHighestPrecedenceLowerOrEqualTo(precedenceUpper);
return precedenceSelected
- .map(facetsByPrecedence::get)
-
.map(facetsOfSameRank->Can.<F>ofCollection(_Casts.uncheckedCast(facetsOfSameRank)))
- .orElseGet(Can::empty);
+ .map(ranksByPrecedence::get)
+ .flatMap(rank->rank.findBest(key))
+
+
+
.map(facetsOfSameRank->Can.<F>ofCollection(_Casts.uncheckedCast(facetsOfSameRank)))
+ .orElseGet(Can::empty);
}
/**
@@ -234,46 +221,51 @@ public <F extends Facet> Can<F> getRankLowerOrEqualTo(
* @param precedenceUpper - upper bound
*/
public Optional<Precedence> getHighestPrecedenceLowerOrEqualTo(final
@NonNull Precedence precedenceUpper) {
- return facetsByPrecedence
- .keySet()
- .stream()
- .filter(precedence->precedence.ordinal()<=precedenceUpper.ordinal())
- .max(Comparator.comparing(Precedence::ordinal));
+ var key = key();
+ for(var rank : ranksByPrecedence.descendingMap().values()) {
+ if(rank.precedence().ordinal()>precedenceUpper.ordinal()) {
+ continue; //next
+ }
+ if(rank.hasBest(key))
+ return Optional.of(rank.precedence());
+ }
+ return Optional.empty();
}
public Optional<Facet.Precedence> getTopPrecedence() {
- return Optional.ofNullable(topPrecedenceRef.get());
+ var key = key();
+ return Optional.ofNullable(topPrecedenceRef.get(key));
}
// -- DYNAMIC UPDATE SUPPORT
- /**
- * Removes any facet of {@code facetType} from facetHolder if it passes
the given {@code filter}.
- * @param facetType - to ensure the filter is properly
generic-type-constraint
- * @param filter
- */
- public <F extends Facet> void purgeIf(
- final @NonNull Class<F> facetType,
- final @NonNull Predicate<? super F> filter) {
-
- // reassess the top precedence
- final _Reduction<Facet.Precedence> top = _Reduction.of(null, (a,
b)->a==null?b:a.ordinal()>b.ordinal()?a:b);
- var markedForRemoval = _Lists.newArrayList(facetsByPrecedence.size());
-
- facetsByPrecedence.forEach((precedence, facets)->{
- facets.removeIf(_Casts.uncheckedCast(filter));
- if(!facets.isEmpty()) {
- top.accept(precedence);
- } else {
- markedForRemoval.add(precedence);
- }
- });
-
- topPrecedenceRef.set(top.getResult().orElse(null));
-
- // remove keys that associate empty lists, so finding highest used
precedence by key is simple
- markedForRemoval.forEach(facetsByPrecedence::remove);
- }
+// /**
+// * Removes any facet of {@code facetType} from facetHolder if it passes
the given {@code filter}.
+// * @param facetType - to ensure the filter is properly
generic-type-constraint
+// * @param filter
+// */
+// public <F extends Facet> void purgeIf(
+// final @NonNull Class<F> facetType,
+// final @NonNull Predicate<? super F> filter) {
+//
+// // reassess the top precedence
+// final _Reduction<Facet.Precedence> top = _Reduction.of(null, (a,
b)->a==null?b:a.ordinal()>b.ordinal()?a:b);
+// var markedForRemoval =
_Lists.newArrayList(facetsByPrecedence.size());
+//
+// facetsByPrecedence.forEach((precedence, facets)->{
+// facets.removeIf(_Casts.uncheckedCast(filter));
+// if(!facets.isEmpty()) {
+// top.accept(precedence);
+// } else {
+// markedForRemoval.add(precedence);
+// }
+// });
+//
+// topPrecedenceRef.set(top.getResult().orElse(null));
+//
+// // remove keys that associate empty lists, so finding highest used
precedence by key is simple
+// markedForRemoval.forEach(facetsByPrecedence::remove);
+// }
// -- VALIDATION SUPPORT
@@ -302,7 +294,53 @@ public <F extends Facet> void visitTopRankPairs(
.skip(1)
.forEach(next->visitor.accept(firstOfTopRanking, next));
}
+ }
+
+ // -- HELPER
+ QualifiedFacet.Key key() {
+ return new QualifiedFacet.Key(facetType, Facets.qualifier(null));
+ }
+
+ @Nullable
+ FacetRank topRankInternal() {
+ var key = key();
+ for(var rank : ranksByPrecedence.descendingMap().values()) {
+ if(rank.hasBest(key))
+ return rank;
+ }
+ return null;
+ }
+
+ /**
+ * Rules in order of strength:
+ * <ul>
+ * <li>all matching {@link QualifiedFacet}(s) take precedence</li>
+ * <li>all non-matching {@link QualifiedFacet}(s) must be ignored</li>
+ * <li>later take precedence over earlier</li>
+ * </ul>
+ */
+ @Deprecated
+ private static <F extends Facet> F findBestWithinRank(
+ final @NonNull Class<F> facetType,
+ final Can<? extends F> rank,
+ final @Nullable String qualifier) {
+
+ F bestNonQualified = null;
+ for(var it = rank.reverseIterator(); it.hasNext(); ) {
+ var facet = it.next();
+ if(facet instanceof QualifiedFacet qFacet) {
+ if(Objects.equals(qualifier, qFacet.qualifier()))
+ return facet;
+ else {
+ continue;
+ }
+ }
+ if(bestNonQualified==null) {
+ bestNonQualified = facet;
+ }
+ }
+ return bestNonQualified;
}
}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/FacetUtil.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/FacetUtil.java
index fd6af2ddcef..162b64b9a0e 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/FacetUtil.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/FacetUtil.java
@@ -85,7 +85,7 @@ public static boolean addFacets(final @NonNull
Iterable<Facet> facetList) {
public static <T extends Facet> XmlSchema.ExtensionData<T>
getFacetsByType(final FacetHolder facetHolder) {
- return new XmlSchema.ExtensionData<T>() {
+ return new XmlSchema.ExtensionData<>() {
@Override
public int size() {
@@ -119,7 +119,7 @@ public static void updateFacet(final @Nullable Facet facet)
{
.orElse(false);
if(skip) return;
- purgeIf(facet.facetType(), facet.getClass()::isInstance,
facet.facetHolder());
+ //FIXME purgeIf(facet.facetType(), facet.getClass()::isInstance,
facet.facetHolder());
addFacet(facet);
}
@@ -134,25 +134,25 @@ public static <F extends Facet> void updateFacetIfPresent(
updateFacet(facetIfAny.orElse(null));
}
- /**
- * Removes any facet of facet-type from facetHolder if it passes the given
filter.
- */
- private static <F extends Facet> void purgeIf(
- final Class<F> facetType,
- final Predicate<? super F> filter,
- final FacetHolder facetHolder) {
-
- facetHolder.getFacetRanking(facetType)
- .ifPresent(ranking->ranking.purgeIf(facetType, filter));
- }
+// /**
+// * Removes any facet of facet-type from facetHolder if it passes the
given filter.
+// */
+// private static <F extends Facet> void purgeIf(
+// final Class<F> facetType,
+// final Predicate<? super F> filter,
+// final FacetHolder facetHolder) {
+//
+// facetHolder.getFacetRanking(facetType)
+// .ifPresent(ranking->ranking.purgeIf(facetType, filter));
+// }
// -- FACET ATTRIBUTES
public static String attributesAsString(final Facet facet) {
return streamAttributes(facet)
- .filter(kv->!kv.key().equals("facet")) // skip superfluous
attribute
- .map(_Strings.KeyValuePair::toString)
- .collect(Collectors.joining("; "));
+ .filter(kv->!kv.key().equals("facet")) // skip superfluous
attribute
+ .map(_Strings.KeyValuePair::toString)
+ .collect(Collectors.joining("; "));
}
public static Stream<_Strings.KeyValuePair> streamAttributes(final Facet
facet) {
@@ -167,8 +167,8 @@ public static String toString(final Facet facet) {
var className = ClassUtils.getShortName(facet.getClass());
var attributesAsString = attributesAsString(facet);
return facet.getClass() == facet.facetType()
- ? String.format("%s[%s]", className, attributesAsString)
- : String.format("%s[type=%s; %s]", className,
ClassUtils.getShortName(facet.facetType()), attributesAsString);
+ ? String.format("%s[%s]", className, attributesAsString)
+ : String.format("%s[type=%s; %s]", className,
ClassUtils.getShortName(facet.facetType()), attributesAsString);
}
// -- FACET LOOKUP
@@ -176,14 +176,13 @@ public static String toString(final Facet facet) {
/** Looks up specified facetType within given {@link FacetHolder}s,
honoring Facet {@link Precedence},
* while first one found wins over later found if they have the same
precedence. */
public static <F extends Facet> Optional<F> lookupFacetIn(final @NonNull
Class<F> facetType, final FacetHolder ... facetHolders) {
- if(facetHolders==null) {
+ if(facetHolders==null)
return Optional.empty();
- }
return Stream.of(facetHolders)
- .filter(_NullSafe::isPresent)
- .map(facetHolder->facetHolder.getFacet(facetType))
- .filter(_NullSafe::isPresent)
- .reduce((a, b)->b.precedence().ordinal()>a.precedence().ordinal()
+ .filter(_NullSafe::isPresent)
+ .map(facetHolder->facetHolder.getFacet(facetType))
+ .filter(_NullSafe::isPresent)
+ .reduce((a, b)->b.precedence().ordinal()>a.precedence().ordinal()
? b
: a);
}
@@ -194,9 +193,8 @@ public static <F extends Facet> Optional<F>
lookupFacetInButExcluding(
final @NonNull Class<F> facetType,
final Predicate<Object> excluded,
final FacetHolder ... facetHolders) {
- if(facetHolders==null) {
+ if(facetHolders==null)
return Optional.empty();
- }
return Stream.of(facetHolders)
.filter(_NullSafe::isPresent)
.filter(x -> !excluded.test(x))
@@ -225,14 +223,13 @@ public static <E extends T, T extends Facet> Optional<E>
computeIfAbsentExact(
if(winnerFacet==null) return
Optional.of(addFacet(facetFactory.apply(facetHolder)));
if(winnerFacet.getClass().equals(facetExactClass)) return
Optional.of(winnerFacet).map(facetExactClass::cast);
// check if we are allowed to override based on precedence
-
if(winnerFacet.precedence().ordinal()<=overrideUpToIncluding.ordinal()) {
+ if(winnerFacet.precedence().ordinal()<=overrideUpToIncluding.ordinal())
return Optional.of(addFacet(facetFactory.apply(facetHolder)));
- }
// not allowed to override
return Optional.empty();
}
- public static void visitAttributes(Facet facet, BiConsumer<String,
Object> visitor) {
+ public static void visitAttributes(final Facet facet, final
BiConsumer<String, Object> visitor) {
visitor.accept("facet", ClassUtils.getShortName(facet.getClass()));
visitor.accept("precedence", facet.precedence().name());
@@ -243,8 +240,8 @@ public static void visitAttributes(Facet facet,
BiConsumer<String, Object> visit
visitor.accept("interactionAdvisors", interactionAdvisors);
}
}
-
- private String interactionAdvisors(Facet facet, final String delimiter) {
+
+ private String interactionAdvisors(final Facet facet, final String
delimiter) {
return Stream.of(Validating.class, HidingOrShowing.class,
DisablingOrEnabling.class)
.filter(marker->marker.isAssignableFrom(facet.getClass()))
.map(Class::getSimpleName)
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/QualifiedFacet.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/QualifiedFacet.java
index ae81b42478b..9bf3a014da8 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/QualifiedFacet.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facetapi/QualifiedFacet.java
@@ -2,6 +2,8 @@
import org.jspecify.annotations.Nullable;
+import org.apache.causeway.commons.internal.base._Strings;
+
/**
* A {@link Facet} can be qualified (similar to Spring beans) in order to
allow for alternative
* behavior or semantics based on context.
@@ -20,6 +22,39 @@
@FunctionalInterface
public interface QualifiedFacet {
+ record Key(
+ Class<? extends Facet> facetType,
+ /**
+ * The empty String "" is used for qualified facets, that have an
empty qualifier
+ */
+ @Nullable String qualifier) {
+
+ static Key unqualified(final Class<? extends Facet> facetType) {
+ return new Key(facetType, null);
+ }
+ static Key forFacet(final Facet facet) {
+ return facet instanceof QualifiedFacet qFacet
+ ? new Key(facet.facetType(),
_Strings.nullToEmpty(qFacet.qualifier()))
+ : unqualified(facet.facetType());
+ }
+ public boolean isQualified() {
+ return qualifier!=null;
+ }
+ public boolean isUnqualified() {
+ return qualifier==null;
+ }
+ public Key toUnqualified() {
+ return isUnqualified()
+ ? this
+ : unqualified(facetType);
+ }
+ public Key toQualified() {
+ return isQualified()
+ ? this
+ : new Key(facetType, "");
+ }
+ }
+
@Nullable String qualifier();
}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobjectlayout/CssClassFacetViaDomainObjectLayoutAnnotationUsingCssClassUiEvent.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobjectlayout/CssClassFacetViaDomainObjectLayoutAnnotationUsingCssClassUiEvent.java
index 8466e3e8564..6c5fd159376 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobjectlayout/CssClassFacetViaDomainObjectLayoutAnnotationUsingCssClassUiEvent.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobjectlayout/CssClassFacetViaDomainObjectLayoutAnnotationUsingCssClassUiEvent.java
@@ -32,7 +32,6 @@
import org.apache.causeway.core.metamodel.object.ManagedObjects;
import org.apache.causeway.core.metamodel.object.MmEventUtils;
import
org.apache.causeway.core.metamodel.services.events.MetamodelEventService;
-import org.apache.causeway.core.metamodel.util.Facets;
public class CssClassFacetViaDomainObjectLayoutAnnotationUsingCssClassUiEvent
extends CssClassFacetAbstract {
@@ -82,9 +81,8 @@ public String cssClass(final ManagedObject owningAdapter) {
if(cssClass == null) {
// ie no subscribers out there...
- final String qualifier = Facets.qualifier(facetHolder());
final CssClassFacet underlyingCssClassFacet =
getSharedFacetRanking()
-
.flatMap(facetRanking->facetRanking.getWinnerNonEvent(CssClassFacet.class,
qualifier))
+
.flatMap(facetRanking->facetRanking.getWinnerNonEvent(CssClassFacet.class))
.orElse(null);
if(underlyingCssClassFacet!=null)
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobjectlayout/IconFacetViaDomainObjectLayoutAnnotationUsingIconUiEvent.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobjectlayout/IconFacetViaDomainObjectLayoutAnnotationUsingIconUiEvent.java
index d03e655031b..0d8abddea89 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobjectlayout/IconFacetViaDomainObjectLayoutAnnotationUsingIconUiEvent.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobjectlayout/IconFacetViaDomainObjectLayoutAnnotationUsingIconUiEvent.java
@@ -33,7 +33,6 @@
import org.apache.causeway.core.metamodel.object.ManagedObjects;
import org.apache.causeway.core.metamodel.object.MmEventUtils;
import
org.apache.causeway.core.metamodel.services.events.MetamodelEventService;
-import org.apache.causeway.core.metamodel.util.Facets;
public record IconFacetViaDomainObjectLayoutAnnotationUsingIconUiEvent(
Class<? extends IconUiEvent<Object>> iconUiEventClass,
@@ -96,9 +95,8 @@ private IconUiEvent<Object> newIconUiEvent(final
ManagedObject owningAdapter, fi
}
private Optional<IconFacet> underlyingIconFacet() {
- final String qualifier = Facets.qualifier(facetHolder());
return getSharedFacetRanking()
-
.flatMap(facetRanking->facetRanking.getWinnerNonEvent(IconFacet.class,
qualifier));
+
.flatMap(facetRanking->facetRanking.getWinnerNonEvent(IconFacet.class));
}
}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobjectlayout/TitleFacetViaDomainObjectLayoutAnnotationUsingTitleUiEvent.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobjectlayout/TitleFacetViaDomainObjectLayoutAnnotationUsingTitleUiEvent.java
index 4a0e0cc4dad..86864b0614e 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobjectlayout/TitleFacetViaDomainObjectLayoutAnnotationUsingTitleUiEvent.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobjectlayout/TitleFacetViaDomainObjectLayoutAnnotationUsingTitleUiEvent.java
@@ -37,7 +37,6 @@
import org.apache.causeway.core.metamodel.object.MmEventUtils;
import
org.apache.causeway.core.metamodel.services.events.MetamodelEventService;
import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
-import org.apache.causeway.core.metamodel.util.Facets;
public class TitleFacetViaDomainObjectLayoutAnnotationUsingTitleUiEvent
extends TitleFacetAbstract {
@@ -99,10 +98,8 @@ public String title(final TitleRenderRequest
titleRenderRequest) {
if(titleUiEvent.getTitle() == null
&& titleUiEvent.getTranslatableTitle() == null) {
// ie no subscribers out there...
-
- final String qualifier = Facets.qualifier(facetHolder());
final TitleFacet underlyingTitleFacet = getSharedFacetRanking()
-
.flatMap(facetRanking->facetRanking.getWinnerNonEvent(TitleFacet.class,
qualifier))
+
.flatMap(facetRanking->facetRanking.getWinnerNonEvent(TitleFacet.class))
.orElse(null);
if(underlyingTitleFacet!=null)
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/layout/LayoutPrefixFacetForUiEvent.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/layout/LayoutPrefixFacetForUiEvent.java
index 37b5a9ddd70..d6650a98176 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/layout/LayoutPrefixFacetForUiEvent.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/layout/LayoutPrefixFacetForUiEvent.java
@@ -79,7 +79,7 @@ public String layoutPrefix(final ManagedObject managedObject)
{
// ie no subscribers out there, then fallback to the underlying ...
return getSharedFacetRanking()
-
.flatMap(facetRanking->facetRanking.getWinnerNonEvent(LayoutPrefixFacet.class,
null))
+
.flatMap(facetRanking->facetRanking.getWinnerNonEvent(LayoutPrefixFacet.class))
.map(underlyingLayoutFacet->underlyingLayoutFacet.layoutPrefix(managedObject))
.orElse(null);
}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ValidatorDomainIncludeAnnotationEnforcesMetamodelContribution.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ValidatorDomainIncludeAnnotationEnforcesMetamodelContribution.java
index b19018ea672..6b88f44e868 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ValidatorDomainIncludeAnnotationEnforcesMetamodelContribution.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ValidatorDomainIncludeAnnotationEnforcesMetamodelContribution.java
@@ -55,7 +55,6 @@
import org.apache.causeway.core.metamodel.spec.feature.MixedIn;
import
org.apache.causeway.core.metamodel.specloader.validator.MetaModelValidatorAbstract;
import
org.apache.causeway.core.metamodel.specloader.validator.ValidationFailure;
-import org.apache.causeway.core.metamodel.util.Facets;
/**
* @since 2.0
@@ -99,12 +98,10 @@ public void validateObjectEnter(final ObjectSpecification
spec) {
.map(MethodFacade::asMethodForIntrospection)
.forEach(memberMethods::add);
- final String qualifier = Facets.qualifier(spec);
-
spec
.streamFacetHolders()
.flatMap(FacetHolder::streamFacetRankings)
-
.map(facetRanking->facetRanking.getWinnerNonEvent(facetRanking.facetType(),
qualifier))
+
.map(facetRanking->facetRanking.getWinnerNonEvent(facetRanking.facetType()))
.flatMap(Optional::stream)
.filter(ImperativeFacet.class::isInstance)
.map(ImperativeFacet.class::cast)
diff --git
a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/grid/GridLoadingTest.java
b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/grid/GridLoadingTest.java
index 56b534d83ff..be4cb6adca1 100644
---
a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/grid/GridLoadingTest.java
+++
b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/grid/GridLoadingTest.java
@@ -98,7 +98,7 @@ void customNamed() {
// verify winning facet is the same object as the last one added from
latest layout.xml reload,
// to make sure we are not feed the winner from an outdated cache
- assertSame(facetRanking.getWinnerNonEvent(MemberNamedFacet.class,
null).get(), xmlFacetRank.getLastElseFail());
+
assertSame(facetRanking.getWinnerNonEvent(MemberNamedFacet.class).get(),
xmlFacetRank.getLastElseFail());
}
diff --git
a/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/menubars/bootstrap/MenuBarsServiceBSTest.java
b/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/menubars/bootstrap/MenuBarsServiceBSTest.java
index 36e5a1f2261..ccad2ab50e8 100644
---
a/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/menubars/bootstrap/MenuBarsServiceBSTest.java
+++
b/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/menubars/bootstrap/MenuBarsServiceBSTest.java
@@ -39,7 +39,6 @@
import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
import org.apache.causeway.core.metamodel.facetapi.Facet.Precedence;
import org.apache.causeway.core.metamodel.facets.all.named.MemberNamedFacet;
-import org.apache.causeway.core.metamodel.util.Facets;
import
org.apache.causeway.core.mmtestsupport.MetaModelContext_forTesting.MetaModelContext_forTestingBuilder;
import org.apache.causeway.core.runtimeservices.RuntimeServicesTestAbstract;
@@ -159,11 +158,9 @@ void customNamed() {
// verify rank did not grow with latest menubars.xml reload
assertEquals(1, xmlFacetRank.size());
- final String qualifier = Facets.qualifier(objectAction);
-
// verify winning facet is the same object as the last one added from
latest menubars.xml reload,
// to make sure we are not feed the winner from an outdated cache
- assertSame(facetRanking.getWinnerNonEvent(MemberNamedFacet.class,
qualifier).get(), xmlFacetRank.getLastElseFail());
+
assertSame(facetRanking.getWinnerNonEvent(MemberNamedFacet.class).get(),
xmlFacetRank.getLastElseFail());
}
// -- HELPER