This is an automated email from the ASF dual-hosted git repository. ahuber pushed a commit to branch 3889-icon.overhaul in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 01ce2059d844cd7e97a2f1d8ad3fb8c2cb684fef Author: Andi Huber <[email protected]> AuthorDate: Thu Aug 28 14:52:22 2025 +0200 CAUSEWAY-3889: unified icon support method unifies iconName, iconData and faIconLayers --- .../causeway/applib/annotation/ObjectSupport.java | 30 ++++++++ .../services/appfeatui/ApplicationFeatureMenu.java | 6 +- .../appfeatui/ApplicationFeatureViewModel.java | 4 +- .../applib/services/title/TitleService.java | 11 ++- .../progmodel/ProgrammingModelConstants.java | 15 ++-- .../metamodel/facets/FacetFactoryAbstract.java | 2 + .../core/metamodel/facets/ImperativeAspect.java | 19 +++++ ...mainObjectLayoutAnnotationUsingIconUiEvent.java | 88 ++++++++++++---------- .../metamodel/facets/object/icon/IconFacet.java | 8 +- .../metamodel/facets/object/icon/ObjectIcon.java | 6 +- .../facets/object/icon/ObjectIconEmbedded.java | 3 +- .../metamodel/facets/object/icon/ObjectIconFa.java | 3 +- .../facets/object/icon/ObjectIconService.java | 31 +++----- .../facets/object/icon/ObjectIconUrlBased.java | 3 +- ...NameMethod.java => IconFacetViaIconMethod.java} | 45 ++++++----- .../method/FaFacetViaIconFaLayersMethod.java | 80 -------------------- .../ident/IconFacetFromProjectionFacet.java | 33 ++++---- .../object/support/ObjectSupportFacetFactory.java | 67 ++++++++-------- .../facets/object/title/TitleRenderRequest.java | 4 +- .../inspect/model/MetamodelInspectView.java | 21 +++--- .../core/metamodel/object/ManagedObject.java | 15 +--- .../services/title/TitleServiceDefault.java | 27 +++---- .../core/metamodel/spec/ObjectSpecification.java | 32 +++----- .../spec/impl/ObjectSpecificationDefault.java | 46 ++++------- .../metamodel/facets/FacetFactoryTestAbstract.java | 4 +- .../IconFacetMethodFaTest.java} | 35 +++++---- .../ident/icon/IconFacetMethodFactoryTest.java | 9 ++- .../object/ident/icon/IconFacetMethodTest.java | 23 +++--- .../FontAwesomeLayersFacetMethodFactoryTest.java | 40 ---------- .../ObjectSupportFacetFactoryTestAbstract.java | 5 +- .../services/title/TitleServiceDefaultTest.java | 4 +- .../mmtestsupport/MetaModelContext_forTesting.java | 3 +- .../mmtestsupport/TitleServiceForTesting.java} | 27 +++---- .../icons/ObjectIconServiceDefault.java | 82 +++++++++++--------- .../testdomain/model/good/ProperFullyAbstract.java | 2 +- .../testdomain/model/good/ProperFullyImpl.java | 5 +- .../good/ProperMemberInheritanceAbstract.java | 4 +- .../good/ProperMemberInheritanceInterface.java | 5 +- .../interaction/DomainObjectTesterFactory.java | 11 ++- .../DomainModelTest_usingGoodDomain.java | 10 ++- .../MetaModelRegressionTest.verify.approved.xml | 24 +++--- .../viewer/commons/model/mixin/HasIcon.java | 6 +- .../viewer/controller/ResourceController.java | 3 +- .../resources/DomainObjectResourceServerside.java | 3 +- .../wicket/model/models/BookmarkTreeNode.java | 2 + .../wicket/model/models/BookmarkableModel.java | 3 +- .../viewer/wicket/model/models/UiObjectWkt.java | 7 +- .../wicket/ui/components/layout/bs/col/Col.java | 7 +- .../object/icontitle/ObjectIconAndTitlePanel.java | 22 ++++-- .../icontitle/ObjectIconAndTitlePanelFactory.java | 6 +- .../icontitle/ObjectIconTitleAndCopyLinkPanel.java | 7 +- .../ObjectIconTitleAndCopyLinkPanelFactory.java | 3 +- 52 files changed, 449 insertions(+), 512 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/annotation/ObjectSupport.java b/api/applib/src/main/java/org/apache/causeway/applib/annotation/ObjectSupport.java index 63bac495379..1e9dca58b9e 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/annotation/ObjectSupport.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/annotation/ObjectSupport.java @@ -24,6 +24,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.apache.causeway.applib.fa.FontAwesomeLayers; +import org.apache.causeway.commons.net.DataUri; + /** * Indicates that a method is a supporting-method that provides UI hints (title, iconName, layout, cssClass) * to its <i>Object</i>. @@ -47,4 +50,31 @@ @Domain.Include // meta annotation, in support of meta-model validation public @interface ObjectSupport { + /** + * Rendering context for object icons. + * An object's icon is either rendered within the object detail page header (along its title) + * or within a table row. + */ + public enum IconWhere { + OBJECT_HEADER, + TABLE_ROW //TODO also TREE_NODE and SELECT_DROPDOWN + } + + public sealed interface IconResource + permits ClassPathIconResource, FontAwesomeIconResource, EmbeddedIconResource { + + } + + public record ClassPathIconResource( + String suffix) implements IconResource { + } + + public record FontAwesomeIconResource( + FontAwesomeLayers faLayers) implements IconResource { + } + + public record EmbeddedIconResource( + DataUri dataUri) implements IconResource { + } + } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationFeatureMenu.java b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationFeatureMenu.java index 3cb99ddea4c..96f3bdc94a3 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationFeatureMenu.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationFeatureMenu.java @@ -63,10 +63,10 @@ public static abstract class CollectionDomainEvent<T> public static abstract class ActionDomainEvent extends CausewayModuleApplib.ActionDomainEvent<ApplicationFeatureMenu> {} - // -- ICON NAME + // -- ICON - @ObjectSupport public String iconName() { - return "applicationFeature"; + @ObjectSupport public ObjectSupport.IconResource icon(final ObjectSupport.IconWhere iconWhere) { + return new ObjectSupport.ClassPathIconResource("applicationFeature"); } // -- ALL PACKAGES diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationFeatureViewModel.java b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationFeatureViewModel.java index 49e84685122..c36588944cd 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationFeatureViewModel.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationFeatureViewModel.java @@ -143,8 +143,8 @@ public ApplicationFeatureViewModel() { @ObjectSupport public String title() { return getFullyQualifiedName(); } - @ObjectSupport public String iconName() { - return "applicationFeature"; + @ObjectSupport public ObjectSupport.IconResource icon(final ObjectSupport.IconWhere iconWhere) { + return new ObjectSupport.ClassPathIconResource("applicationFeature"); } // -- VIEWMODEL CONTRACT diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/title/TitleService.java b/api/applib/src/main/java/org/apache/causeway/applib/services/title/TitleService.java index 37c44807090..1080d070e33 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/title/TitleService.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/title/TitleService.java @@ -18,11 +18,14 @@ */ package org.apache.causeway.applib.services.title; +import org.apache.causeway.applib.annotation.ObjectSupport.IconResource; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; + /** * Provides methods to programmatically obtain the title and icon of a domain * object. * - * @since 1.x {@index} + * @since 1.x {@index} revised for 4.0 */ public interface TitleService { @@ -30,12 +33,12 @@ public interface TitleService { * Returns the title of the object (as rendered in the UI by the * framework's viewers). */ - String titleOf(final Object domainObject); + String titleOf(Object domainObject); /** - * Returns the icon name of the object (as rendered in the UI by the + * Returns the icon of the object (as rendered in the UI by the * framework's viewers). */ - String iconNameOf(final Object domainObject); + IconResource iconOf(Object domainObject, IconWhere iconWhere); } diff --git a/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java b/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java index 54da4c83252..448eae011fa 100644 --- a/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java +++ b/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java @@ -35,8 +35,10 @@ import java.util.function.IntFunction; import java.util.stream.Stream; -import org.springframework.context.annotation.Configuration; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; + +import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Repository; import org.apache.causeway.applib.Identifier; @@ -44,7 +46,7 @@ import org.apache.causeway.applib.annotation.MemberSupport; import org.apache.causeway.applib.annotation.ObjectLifecycle; import org.apache.causeway.applib.annotation.ObjectSupport; -import org.apache.causeway.applib.fa.FontAwesomeLayers; +import org.apache.causeway.applib.annotation.ObjectSupport.IconResource; import org.apache.causeway.applib.services.i18n.TranslatableString; import org.apache.causeway.commons.collections.Can; import org.apache.causeway.commons.functional.Try; @@ -65,7 +67,6 @@ import static org.apache.causeway.commons.internal.reflection._Reflect.predicates.paramCount; import lombok.Getter; -import org.jspecify.annotations.NonNull; import lombok.RequiredArgsConstructor; public final class ProgrammingModelConstants { @@ -249,7 +250,8 @@ public enum ReturnTypeCategory { BOOLEAN(boolean.class), STRING(String.class), TRANSLATABLE(String.class, TranslatableString.class), - FONTAWESOME_LAYERS(FontAwesomeLayers.class); + ICON_RESOURCE(IconResource.class), + ; ReturnTypeCategory(final Class<?> ...returnTypes) { this.returnTypes = Can.of(returnTypes); } @@ -292,8 +294,9 @@ public enum ObjectSupportMethod { TITLE(ReturnTypeCategory.TRANSLATABLE, "title"), CSS_CLASS(ReturnTypeCategory.STRING, "cssClass"), - ICON_NAME(ReturnTypeCategory.STRING, "iconName"), - ICON_FA_LAYERS(ReturnTypeCategory.FONTAWESOME_LAYERS, "iconFaLayers"), + + ICON(ReturnTypeCategory.ICON_RESOURCE, "icon"), + LAYOUT(ReturnTypeCategory.STRING, "layout"), /** as a fallback in the absence of other title providers */ diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/FacetFactoryAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/FacetFactoryAbstract.java index 00536c04b48..de808242092 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/FacetFactoryAbstract.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/FacetFactoryAbstract.java @@ -22,6 +22,7 @@ import org.jspecify.annotations.Nullable; +import org.apache.causeway.applib.annotation.ObjectSupport; import org.apache.causeway.commons.collections.ImmutableEnumSet; import org.apache.causeway.commons.internal.reflection._ClassCache; import org.apache.causeway.core.metamodel.context.HasMetaModelContext; @@ -87,5 +88,6 @@ public <F extends Facet> Optional<F> addFacetIfPresent(final @Nullable Optional< protected static final Class<?>[] NO_ARG = new Class<?>[0]; protected static final Class<?>[] STRING_ARG = new Class<?>[] {String.class}; + protected static final Class<?>[] ICON_WHERE_ARG = new Class<?>[] {ObjectSupport.IconWhere.class}; } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/ImperativeAspect.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/ImperativeAspect.java index 20980c854f7..fa2a51bf083 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/ImperativeAspect.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/ImperativeAspect.java @@ -54,6 +54,12 @@ public Object invokeSingleMethod(final ManagedObject domainObject) { return returnValue; } + public Object invokeSingleMethod(final ManagedObject domainObject, final Object arg0) { + var method = methods.getFirstElseFail().asMethodElseFail(); // expected regular, as the factories only creates regular + final Object returnValue = MmInvokeUtils.invokeWithSingleArgPojo(method.method(), domainObject, arg0); + return returnValue; + } + public <T> T eval( final ManagedObject domainObject, final T fallback) { @@ -66,4 +72,17 @@ public <T> T eval( } } + public <T> T eval( + final ManagedObject domainObject, + final T fallback, + final Object arg0) { + if(ManagedObjects.isNullOrUnspecifiedOrEmpty(domainObject)) return fallback; + + try { + return _Casts.uncheckedCast(invokeSingleMethod(domainObject)); + } catch (final RuntimeException ex) { + return fallback; + } + } + } 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 4423e7a7e18..ef1fd2c0da4 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 @@ -21,21 +21,27 @@ import java.util.Optional; import java.util.function.BiConsumer; +import org.springframework.util.ClassUtils; + import org.apache.causeway.applib.annotation.DomainObjectLayout; +import org.apache.causeway.applib.annotation.ObjectSupport; import org.apache.causeway.applib.events.EventObjectBase; import org.apache.causeway.applib.events.ui.IconUiEvent; import org.apache.causeway.commons.internal.base._Casts; import org.apache.causeway.commons.internal.base._Strings; +import org.apache.causeway.core.metamodel.facetapi.Facet; import org.apache.causeway.core.metamodel.facetapi.FacetHolder; import org.apache.causeway.core.metamodel.facets.object.icon.IconFacet; -import org.apache.causeway.core.metamodel.facets.object.icon.IconFacetAbstract; import org.apache.causeway.core.metamodel.object.ManagedObject; 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; -public class IconFacetViaDomainObjectLayoutAnnotationUsingIconUiEvent -extends IconFacetAbstract { +public record IconFacetViaDomainObjectLayoutAnnotationUsingIconUiEvent( + Class<? extends IconUiEvent<Object>> iconUiEventClass, + MetamodelEventService metamodelEventService, + FacetHolder facetHolder) +implements IconFacet { public static Optional<IconFacetViaDomainObjectLayoutAnnotationUsingIconUiEvent> create( final Optional<DomainObjectLayout> domainObjectLayoutIfAny, @@ -43,67 +49,69 @@ public static Optional<IconFacetViaDomainObjectLayoutAnnotationUsingIconUiEvent> final FacetHolder facetHolder) { return domainObjectLayoutIfAny - .map(DomainObjectLayout::iconUiEvent) - .filter(iconUiEvent -> MmEventUtils.eventTypeIsPostable( - iconUiEvent, - IconUiEvent.Noop.class, - IconUiEvent.Default.class, - facetHolder.getConfiguration().applib().annotation() - .domainObjectLayout().iconUiEvent().postForDefault())) - .map(iconUiEvent -> { - return new IconFacetViaDomainObjectLayoutAnnotationUsingIconUiEvent( - iconUiEvent, metamodelEventService, facetHolder); - }); + .map(DomainObjectLayout::iconUiEvent) + .filter(iconUiEvent -> MmEventUtils.eventTypeIsPostable( + iconUiEvent, + IconUiEvent.Noop.class, + IconUiEvent.Default.class, + facetHolder.getConfiguration().applib().annotation() + .domainObjectLayout().iconUiEvent().postForDefault())) + .map(iconUiEvent -> { + return new IconFacetViaDomainObjectLayoutAnnotationUsingIconUiEvent( + _Casts.uncheckedCast(iconUiEvent), metamodelEventService, facetHolder); + }); } - private final Class<? extends IconUiEvent<Object>> iconUiEventClass; - private final MetamodelEventService metamodelEventService; + @Override public FacetHolder getFacetHolder() { return facetHolder; } + @Override public Class<? extends Facet> facetType() { return IconFacet.class; } + @Override public Precedence getPrecedence() { return Precedence.EVENT; } - public IconFacetViaDomainObjectLayoutAnnotationUsingIconUiEvent( - final Class<? extends IconUiEvent<?>> iconUiEventClass, - final MetamodelEventService metamodelEventService, - final FacetHolder holder) { - super(holder, Precedence.EVENT); - this.iconUiEventClass = _Casts.uncheckedCast(iconUiEventClass); - this.metamodelEventService = metamodelEventService; - } @Override - public Optional<String> iconName(final ManagedObject owningAdapter) { + public Optional<ObjectSupport.IconResource> icon(ManagedObject domainObject, ObjectSupport.IconWhere iconWhere) { + return iconName(domainObject, iconWhere) + .map(ObjectSupport.ClassPathIconResource::new); + } - if(ManagedObjects.isNullOrUnspecifiedOrEmpty(owningAdapter)) { - return Optional.empty(); - } + private Optional<String> iconName(final ManagedObject domainObject, ObjectSupport.IconWhere iconWhere) { - final IconUiEvent<Object> iconUiEvent = newIconUiEvent(owningAdapter); + if(ManagedObjects.isNullOrUnspecifiedOrEmpty(domainObject)) return Optional.empty(); + + final IconUiEvent<Object> iconUiEvent = newIconUiEvent(domainObject); metamodelEventService.fireIconUiEvent(iconUiEvent); - final String iconName = iconUiEvent.getIconName(); + var iconName = iconUiEvent.getIconName(); if(iconName == null) { // ie no subscribers out there... - final IconFacet underlyingIconFacet = getSharedFacetRanking() - .flatMap(facetRanking->facetRanking.getWinnerNonEvent(IconFacet.class)) - .orElse(null); + var icon = underlyingIconFacet() + .flatMap(underlyingIconFacet->underlyingIconFacet.icon(domainObject, iconWhere)) + .orElse(null); - if(underlyingIconFacet!=null) { - return underlyingIconFacet.iconName(owningAdapter); - } + if(icon instanceof ObjectSupport.ClassPathIconResource cpIconResource) + iconName = cpIconResource.suffix(); } return _Strings.nonEmpty(iconName); } + @Override + public void visitAttributes(final BiConsumer<String, Object> visitor) { + visitor.accept("facet", ClassUtils.getShortName(getClass())); + visitor.accept("precedence", getPrecedence().name()); + visitor.accept("iconUiEventClass", iconUiEventClass); + } + private IconUiEvent<Object> newIconUiEvent(final ManagedObject owningAdapter) { return EventObjectBase.getInstanceWithSourceSupplier(iconUiEventClass, owningAdapter::getPojo).orElseThrow(); } - @Override - public void visitAttributes(final BiConsumer<String, Object> visitor) { - super.visitAttributes(visitor); - visitor.accept("iconUiEventClass", iconUiEventClass); + private Optional<IconFacet> underlyingIconFacet() { + return getSharedFacetRanking() + .flatMap(facetRanking->facetRanking.getWinnerNonEvent(IconFacet.class)); } + } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/IconFacet.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/IconFacet.java index 6380c5d450e..a34cfae096a 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/IconFacet.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/IconFacet.java @@ -20,6 +20,8 @@ import java.util.Optional; +import org.apache.causeway.applib.annotation.ObjectSupport; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.core.metamodel.facetapi.Facet; import org.apache.causeway.core.metamodel.facets.object.title.TitleFacet; import org.apache.causeway.core.metamodel.object.ManagedObject; @@ -35,11 +37,13 @@ * pending approval, approved, rejected). * * <p> - * In the standard Apache Causeway Programming Model, typically corresponds to a method named {@code iconName}. + * In the Apache Causeway Programming Model, corresponds to either a method named {@code iconName} + * or {@code iconData}. * * @see TitleFacet */ public interface IconFacet extends Facet { - public Optional<String> iconName(final ManagedObject object); + Optional<ObjectSupport.IconResource> icon(ManagedObject object, IconWhere iconWhere); + } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIcon.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIcon.java index 4a5bbcf321b..4bcb890d579 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIcon.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIcon.java @@ -20,11 +20,13 @@ import java.io.Serializable; +import org.apache.causeway.applib.services.title.TitleService; + /** * Icon image data class-path resource reference. * - * @see ObjectIconService - * @since 2.0 revised for 4.0 + * @see TitleService + * @since 2.0 revised for 4.0 {@index} */ public sealed interface ObjectIcon extends Serializable permits ObjectIconFa, ObjectIconEmbedded, ObjectIconUrlBased { diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconEmbedded.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconEmbedded.java index 8fb64f77914..70cc2c10f41 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconEmbedded.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconEmbedded.java @@ -18,13 +18,14 @@ */ package org.apache.causeway.core.metamodel.facets.object.icon; +import org.apache.causeway.applib.services.title.TitleService; import org.apache.causeway.commons.net.DataUri; /** * Icon image based on {@link DataUri} * * @see ObjectIcon - * @see ObjectIconService + * @see TitleService * @since 4.0 */ public record ObjectIconEmbedded( diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconFa.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconFa.java index 335a4673767..64154b5682c 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconFa.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconFa.java @@ -21,12 +21,13 @@ import java.nio.charset.StandardCharsets; import org.apache.causeway.applib.fa.FontAwesomeLayers; +import org.apache.causeway.applib.services.title.TitleService; /** * Icon image based on {@link FontAwesomeLayers} * * @see ObjectIcon - * @see ObjectIconService + * @see TitleService * @since 4.0 */ public record ObjectIconFa( diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconService.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconService.java index 8c2d6bb5cf9..1c1a00f95a3 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconService.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconService.java @@ -18,35 +18,26 @@ */ package org.apache.causeway.core.metamodel.facets.object.icon; -import java.util.Optional; - -import org.apache.causeway.applib.fa.FontAwesomeLayers; +import org.apache.causeway.applib.annotation.ObjectSupport.IconResource; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.core.metamodel.object.ManagedObject; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; /** - * Creates {@link ObjectIcon}(s), which are class-path resource references. - * <p> - * Clients should not use this service directly. Either use - * {@link ManagedObject#getIcon()} or - * {@link ObjectSpecification#getIcon(org.apache.causeway.core.metamodel.object.ManagedObject)}. + * Resolves instances of {@link IconResource} to instances of {@link ObjectIcon}. + * + * <p>Clients should not use this service directly. Either use + * {@link ManagedObject#getIcon(IconWhere)} or + * {@link ObjectSpecification#getIcon(org.apache.causeway.core.metamodel.object.ManagedObject, IconWhere)}. * * @apiNote internal service, used by the metamodel * - * @see ManagedObject#getIcon() - * @see ObjectSpecification#getIcon(org.apache.causeway.core.metamodel.object.ManagedObject) - * @since 2.0 + * @see ManagedObject#getIcon(IconWhere) + * @see ObjectSpecification#getIcon(org.apache.causeway.core.metamodel.object.ManagedObject, IconWhere) + * @since 2.0 revised for 4.0 */ public interface ObjectIconService { - /** - * {@link ObjectIcon} for given {@link ObjectSpecification} - * and iconNameSuffix or font-awesome layers. - * @return non-null - */ - ObjectIcon getObjectIcon( - ObjectSpecification specification, - Optional<String> iconNameSuffix, - Optional<FontAwesomeLayers> faLayers); + ObjectIcon getObjectIcon(ManagedObject managedObject, IconWhere iconWhere); } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconUrlBased.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconUrlBased.java index 0e094b69b73..74c3eb6324c 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconUrlBased.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/ObjectIconUrlBased.java @@ -22,6 +22,7 @@ import java.net.URL; import java.util.Objects; +import org.apache.causeway.applib.services.title.TitleService; import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType; import org.apache.causeway.commons.internal._Constants; import org.apache.causeway.commons.internal.base._Bytes; @@ -32,7 +33,7 @@ * Icon image data class-path resource reference. * * @see ObjectIcon - * @see ObjectIconService + * @see TitleService * @since 4.0 */ public record ObjectIconUrlBased( diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/method/IconFacetViaIconNameMethod.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/method/IconFacetViaIconMethod.java similarity index 59% rename from core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/method/IconFacetViaIconNameMethod.java rename to core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/method/IconFacetViaIconMethod.java index 1bfc6e131be..7060dea4071 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/method/IconFacetViaIconNameMethod.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/method/IconFacetViaIconMethod.java @@ -23,50 +23,47 @@ import org.jspecify.annotations.Nullable; -import org.apache.causeway.commons.internal.base._Strings; +import org.springframework.util.ClassUtils; + +import org.apache.causeway.applib.annotation.ObjectSupport; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.commons.internal.reflection._GenericResolver.ResolvedMethod; +import org.apache.causeway.core.metamodel.facetapi.Facet; import org.apache.causeway.core.metamodel.facetapi.FacetHolder; import org.apache.causeway.core.metamodel.facets.HasImperativeAspect; import org.apache.causeway.core.metamodel.facets.ImperativeAspect; import org.apache.causeway.core.metamodel.facets.object.icon.IconFacet; -import org.apache.causeway.core.metamodel.facets.object.icon.IconFacetAbstract; import org.apache.causeway.core.metamodel.object.ManagedObject; -import lombok.Getter; -import org.jspecify.annotations.NonNull; - -public class IconFacetViaIconNameMethod -extends IconFacetAbstract -implements HasImperativeAspect { - - @Getter(onMethod_ = {@Override}) private final @NonNull ImperativeAspect imperativeAspect; +public record IconFacetViaIconMethod( + ImperativeAspect imperativeAspect, + FacetHolder facetHolder) +implements IconFacet, HasImperativeAspect { public static Optional<IconFacet> create( final @Nullable ResolvedMethod methodIfAny, final FacetHolder holder) { - return Optional.ofNullable(methodIfAny) - .map(method-> - new IconFacetViaIconNameMethod( - ImperativeAspect.singleRegularMethod(method, Intent.UI_HINT), - holder)); + .map(method-> + new IconFacetViaIconMethod( + ImperativeAspect.singleRegularMethod(method, Intent.UI_HINT), + holder)); } - private IconFacetViaIconNameMethod( - final ImperativeAspect imperativeAspect, - final FacetHolder holder) { - super(holder); - this.imperativeAspect = imperativeAspect; - } + @Override public FacetHolder getFacetHolder() { return facetHolder; } + @Override public Class<? extends Facet> facetType() { return IconFacet.class; } + @Override public Precedence getPrecedence() { return Precedence.DEFAULT; } + @Override public ImperativeAspect getImperativeAspect() { return imperativeAspect; } @Override - public Optional<String> iconName(final ManagedObject domainObject) { - return _Strings.nonEmpty(imperativeAspect.eval(domainObject, (String)null)); + public Optional<ObjectSupport.IconResource> icon(ManagedObject domainObject, IconWhere iconWhere) { + return Optional.ofNullable(imperativeAspect.eval(domainObject, (ObjectSupport.IconResource)null, iconWhere)); } @Override public void visitAttributes(final BiConsumer<String, Object> visitor) { - super.visitAttributes(visitor); + visitor.accept("facet", ClassUtils.getShortName(getClass())); + visitor.accept("precedence", getPrecedence().name()); imperativeAspect.visitAttributes(visitor); } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/iconfa/method/FaFacetViaIconFaLayersMethod.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/iconfa/method/FaFacetViaIconFaLayersMethod.java deleted file mode 100644 index 3d04ffeacbf..00000000000 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/iconfa/method/FaFacetViaIconFaLayersMethod.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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.facets.object.iconfa.method; - -import java.util.Optional; -import java.util.function.BiConsumer; - -import org.jspecify.annotations.Nullable; - -import org.apache.causeway.applib.fa.FontAwesomeLayers; -import org.apache.causeway.commons.internal.reflection._GenericResolver.ResolvedMethod; -import org.apache.causeway.core.metamodel.facetapi.FacetHolder; -import org.apache.causeway.core.metamodel.facets.HasImperativeAspect; -import org.apache.causeway.core.metamodel.facets.ImperativeAspect; -import org.apache.causeway.core.metamodel.facets.members.iconfa.FaFacet; -import org.apache.causeway.core.metamodel.facets.members.iconfa.FaImperativeFacetAbstract; -import org.apache.causeway.core.metamodel.facets.members.iconfa.FaLayersProvider; -import org.apache.causeway.core.metamodel.object.ManagedObject; - -import lombok.Getter; -import org.jspecify.annotations.NonNull; - -public class FaFacetViaIconFaLayersMethod -extends FaImperativeFacetAbstract -implements HasImperativeAspect { - - @Getter(onMethod_ = {@Override}) private final @NonNull ImperativeAspect imperativeAspect; - - public static Optional<FaFacet> create( - final @Nullable ResolvedMethod methodIfAny, - final FacetHolder holder) { - - return Optional.ofNullable(methodIfAny) - .map(method-> - new FaFacetViaIconFaLayersMethod( - ImperativeAspect.singleRegularMethod(method, Intent.UI_HINT), - holder)); - } - - private FaFacetViaIconFaLayersMethod( - final ImperativeAspect imperativeAspect, - final FacetHolder holder) { - super(holder, Precedence.IMPERATIVE); - this.imperativeAspect = imperativeAspect; - } - - @Override - public FaLayersProvider getFaLayersProvider(final ManagedObject domainObject) { - return () -> evalLayers(domainObject); - } - - @Override - public void visitAttributes(final BiConsumer<String, Object> visitor) { - super.visitAttributes(visitor); - imperativeAspect.visitAttributes(visitor); - } - - // -- HELPER - - FontAwesomeLayers evalLayers(final ManagedObject domainObject) { - return imperativeAspect.eval(domainObject, (FontAwesomeLayers)null); - } - -} diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/projection/ident/IconFacetFromProjectionFacet.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/projection/ident/IconFacetFromProjectionFacet.java index 691e31504e1..0ff1196bb5d 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/projection/ident/IconFacetFromProjectionFacet.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/projection/ident/IconFacetFromProjectionFacet.java @@ -21,35 +21,36 @@ import java.util.Optional; import java.util.function.BiConsumer; +import org.jspecify.annotations.NonNull; + +import org.springframework.util.ClassUtils; + +import org.apache.causeway.applib.annotation.ObjectSupport; import org.apache.causeway.core.metamodel.facetapi.Facet; import org.apache.causeway.core.metamodel.facetapi.FacetHolder; -import org.apache.causeway.core.metamodel.facets.object.icon.IconFacetAbstract; +import org.apache.causeway.core.metamodel.facets.object.icon.IconFacet; import org.apache.causeway.core.metamodel.facets.object.projection.ProjectionFacet; import org.apache.causeway.core.metamodel.object.ManagedObject; -import org.jspecify.annotations.NonNull; +public record IconFacetFromProjectionFacet( + ProjectionFacet projectionFacet, + FacetHolder facetHolder) +implements IconFacet { -public class IconFacetFromProjectionFacet -extends IconFacetAbstract { - - private final @NonNull ProjectionFacet projectionFacet; - - public IconFacetFromProjectionFacet( - final ProjectionFacet projectionFacet, - final FacetHolder holder) { - super(holder); - this.projectionFacet = projectionFacet; - } + @Override public FacetHolder getFacetHolder() { return facetHolder; } + @Override public Class<? extends Facet> facetType() { return IconFacet.class; } + @Override public Precedence getPrecedence() { return Precedence.DEFAULT; } @Override - public Optional<String> iconName(final ManagedObject targetAdapter) { + public Optional<ObjectSupport.IconResource> icon(final ManagedObject targetAdapter, final ObjectSupport.IconWhere iconWhere) { var projectedAdapter = projectionFacet.projected(targetAdapter); - return projectedAdapter.objSpec().getIconName(projectedAdapter); + return projectedAdapter.objSpec().getIcon(projectedAdapter, iconWhere); } @Override public void visitAttributes(final BiConsumer<String, Object> visitor) { - super.visitAttributes(visitor); + visitor.accept("facet", ClassUtils.getShortName(getClass())); + visitor.accept("precedence", getPrecedence().name()); visitor.accept("projectionFacet", projectionFacet.getClass().getName()); } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/support/ObjectSupportFacetFactory.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/support/ObjectSupportFacetFactory.java index 1e96b4b8641..95872194dfb 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/support/ObjectSupportFacetFactory.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/support/ObjectSupportFacetFactory.java @@ -39,8 +39,7 @@ import org.apache.causeway.core.metamodel.facets.object.disabled.method.DisabledObjectFacetViaMethod; import org.apache.causeway.core.metamodel.facets.object.hidden.HiddenObjectFacet; import org.apache.causeway.core.metamodel.facets.object.hidden.method.HiddenObjectFacetViaMethod; -import org.apache.causeway.core.metamodel.facets.object.icon.method.IconFacetViaIconNameMethod; -import org.apache.causeway.core.metamodel.facets.object.iconfa.method.FaFacetViaIconFaLayersMethod; +import org.apache.causeway.core.metamodel.facets.object.icon.method.IconFacetViaIconMethod; import org.apache.causeway.core.metamodel.facets.object.layout.LayoutPrefixFacetViaMethod; import org.apache.causeway.core.metamodel.facets.object.title.methods.TitleFacetFromToStringMethod; import org.apache.causeway.core.metamodel.facets.object.title.methods.TitleFacetViaTitleMethod; @@ -70,9 +69,9 @@ public class ObjectSupportFacetFactory public ObjectSupportFacetFactory(final MetaModelContext mmc) { super(mmc, FeatureType.EVERYTHING_BUT_PARAMETERS, OrphanValidation.VALIDATE, Stream.of(ObjectSupportMethod.values()) - .map(ObjectSupportMethod::getMethodNames) - .flatMap(Can::stream) - .collect(Can.toCan())); + .map(ObjectSupportMethod::getMethodNames) + .flatMap(Can::stream) + .collect(Can.toCan())); } @Override @@ -81,13 +80,12 @@ public final void process(final ProcessClassContext processClassContext) { // priming 'toString()' into Precedence.INFERRED rank inferTitleFromToString(processClassContext); - processObjectSupport(processClassContext, ObjectSupportMethod.HIDDEN, HiddenObjectFacetViaMethod::create); - processObjectSupport(processClassContext, ObjectSupportMethod.DISABLED, DisabledObjectFacetViaMethod::create); - processObjectSupport(processClassContext, ObjectSupportMethod.TITLE, TitleFacetViaTitleMethod::create); - processObjectSupport(processClassContext, ObjectSupportMethod.LAYOUT, LayoutPrefixFacetViaMethod::create); - processObjectSupport(processClassContext, ObjectSupportMethod.ICON_NAME, IconFacetViaIconNameMethod::create); - processObjectSupport(processClassContext, ObjectSupportMethod.ICON_FA_LAYERS, FaFacetViaIconFaLayersMethod::create); - processObjectSupport(processClassContext, ObjectSupportMethod.CSS_CLASS, CssClassFacetViaCssClassMethod::create); + processObjectSupport(processClassContext, ObjectSupportMethod.HIDDEN, NO_ARG, HiddenObjectFacetViaMethod::create); + processObjectSupport(processClassContext, ObjectSupportMethod.DISABLED, NO_ARG, DisabledObjectFacetViaMethod::create); + processObjectSupport(processClassContext, ObjectSupportMethod.TITLE, NO_ARG, TitleFacetViaTitleMethod::create); + processObjectSupport(processClassContext, ObjectSupportMethod.LAYOUT, NO_ARG, LayoutPrefixFacetViaMethod::create); + processObjectSupport(processClassContext, ObjectSupportMethod.ICON, ICON_WHERE_ARG, IconFacetViaIconMethod::create); + processObjectSupport(processClassContext, ObjectSupportMethod.CSS_CLASS, NO_ARG, CssClassFacetViaCssClassMethod::create); } @Override @@ -112,35 +110,36 @@ private void inferTitleFromToString(final ProcessClassContext processClassContex var toString = ObjectSupportMethod.TO_STRING; MethodFinder - .publicOnly( - processClassContext.getCls(), - toString.getMethodNames()) - .withReturnTypeAnyOf(toString.getReturnTypeCategory().getReturnTypes()) - .streamMethodsMatchingSignature(NO_ARG) - .peek(processClassContext::removeMethod) - .forEach(method->{ - addFacetIfPresent(TitleFacetFromToStringMethod - .create(method, processClassContext.getFacetHolder())); - }); + .publicOnly( + processClassContext.getCls(), + toString.getMethodNames()) + .withReturnTypeAnyOf(toString.getReturnTypeCategory().getReturnTypes()) + .streamMethodsMatchingSignature(NO_ARG) + .peek(processClassContext::removeMethod) + .forEach(method->{ + addFacetIfPresent(TitleFacetFromToStringMethod + .create(method, processClassContext.getFacetHolder())); + }); } private void processObjectSupport( final ProcessClassContext processClassContext, final ObjectSupportMethod objectSupportMethodEnum, + final Class<?>[] methodSignature, final BiFunction<ResolvedMethod, FacetHolder, Optional<? extends Facet>> objectSupportFacetConstructor) { - MethodFinder - .objectSupport( - processClassContext.getCls(), - objectSupportMethodEnum.getMethodNames(), - processClassContext.getIntrospectionPolicy()) - .withReturnTypeAnyOf(objectSupportMethodEnum.getReturnTypeCategory().getReturnTypes()) - .streamMethodsMatchingSignature(NO_ARG) - .peek(processClassContext::removeMethod) - .forEach(method->{ - addFacetIfPresent(objectSupportFacetConstructor - .apply(method, processClassContext.getFacetHolder())); - }); + .objectSupport( + processClassContext.getCls(), + objectSupportMethodEnum.getMethodNames(), + processClassContext.getIntrospectionPolicy()) + .withReturnTypeAnyOf(objectSupportMethodEnum.getReturnTypeCategory().getReturnTypes()) + .streamMethodsMatchingSignature(methodSignature) + .peek(processClassContext::removeMethod) + .forEach(method->{ + addFacetIfPresent(objectSupportFacetConstructor + .apply(method, processClassContext.getFacetHolder())) + .orElse(null); + }); } } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/title/TitleRenderRequest.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/title/TitleRenderRequest.java index 02c9e1bffe8..c255fc13949 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/title/TitleRenderRequest.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/title/TitleRenderRequest.java @@ -29,8 +29,8 @@ public record TitleRenderRequest( @NonNull ManagedObject object, /** * Provide a title for the target object, possibly abbreviated (according to supplied predicate) - * <p> - * One reason why the title might be abbreviated is if it is being evaluated in the context + * + * <p>One reason why the title might be abbreviated is if it is being evaluated in the context * of another object. * For example as a child object of a parented collection of some parent object. * In such a context, the title might be shortened so that it does not needlessly incorporate diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/inspect/model/MetamodelInspectView.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/inspect/model/MetamodelInspectView.java index 3d569350f26..a6ebcf49ac5 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/inspect/model/MetamodelInspectView.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/inspect/model/MetamodelInspectView.java @@ -44,13 +44,13 @@ public class MetamodelInspectView extends MasterDetailTreeView<MMNode, MetamodelInspectView> { // -- FACTORY - + public static MetamodelInspectView root(final ObjectSpecification spec) { return new MetamodelInspectView(new TypeNode(spec.logicalTypeName()), TreePath.root()); } // -- CONSTRUCTION - + private final Memento memento; public MetamodelInspectView(final String mementoString) { @@ -66,7 +66,7 @@ public MetamodelInspectView(final String mementoString) { super(MMNode.class, rootNode, activeTreePath); this.memento = new Memento(rootNode.logicalName(), activeTreePath); } - + // -- UI @ObjectSupport @@ -75,13 +75,14 @@ public String title() { } @ObjectSupport - public String iconName() { - return activeNode().getClass().getSimpleName() + public ObjectSupport.IconResource icon(final ObjectSupport.IconWhere iconWhere) { + return new ObjectSupport.ClassPathIconResource( + activeNode().getClass().getSimpleName() + _Strings.nonEmpty(activeNode().iconName()) - .map(suffix-> "-" + suffix) - .orElse(""); + .map(suffix-> "-" + suffix) + .orElse("")); } - + @Property(editingDisabledReason = "readonly by design") @PropertyLayout(labelPosition = LabelPosition.NONE, fieldSetId = "detail", sequence = "1") public Markup getDetails() { @@ -89,7 +90,7 @@ public Markup getDetails() { } // -- IMPLEMENTATION DETAILS - + @Override public String viewModelMemento() { return memento.stringify(); @@ -109,7 +110,7 @@ protected TreeAdapter<MMNode> treeAdapter() { } // -- HELPER - + private record Memento ( String logicalName, TreePath treePath) { diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/ManagedObject.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/ManagedObject.java index 1b23e8f2597..54f6c84d24a 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/ManagedObject.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/ManagedObject.java @@ -24,6 +24,7 @@ import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.applib.services.bookmark.Bookmark; import org.apache.causeway.applib.services.repository.EntityState; import org.apache.causeway.commons.collections.Can; @@ -32,7 +33,6 @@ import org.apache.causeway.commons.internal.exceptions._Exceptions; import org.apache.causeway.core.metamodel.context.HasMetaModelContext; import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIcon; -import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIconService; import org.apache.causeway.core.metamodel.spec.HasObjectSpecification; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.feature.ObjectActionParameter; @@ -381,20 +381,13 @@ default EntityState getEntityState() { // -- SHORTCUT - ICON - /** - * Optionally returns the name-suffix (or embedded image data) of an icon to use for this object. - * @see ObjectIconService - */ - default Optional<String> getIconName() { - return objSpec().getIconName(this); - } - /** * Domain Objects may either have an icon corresponding to an icon resource, * or they use a font awesome icon. + * @param iconWhere */ - default ObjectIcon getIcon() { - return objSpec().getIcon(this); + default ObjectIcon getIcon(IconWhere iconWhere) { + return getObjectIconService().getObjectIcon(this, iconWhere); } default Either<ManagedObject, ManagedObject> asEitherWithOrWithoutMemoizedBookmark() { diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/title/TitleServiceDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/title/TitleServiceDefault.java index 9d8471f18d4..cf853b11228 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/title/TitleServiceDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/title/TitleServiceDefault.java @@ -19,12 +19,12 @@ package org.apache.causeway.core.metamodel.services.title; import jakarta.annotation.Priority; -import jakarta.inject.Inject; import jakarta.inject.Named; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import org.apache.causeway.applib.annotation.ObjectSupport; import org.apache.causeway.applib.annotation.PriorityPrecedence; import org.apache.causeway.applib.services.title.TitleService; import org.apache.causeway.applib.services.wrapper.WrapperFactory; @@ -34,8 +34,6 @@ import org.apache.causeway.core.metamodel.object.MmEntityUtils; import org.apache.causeway.core.metamodel.objectmanager.ObjectManager; -import lombok.RequiredArgsConstructor; - /** * Default implementation of {@link TitleService}. * @@ -45,17 +43,13 @@ @Named(CausewayModuleCoreMetamodel.NAMESPACE + ".TitleServiceDefault") @Priority(PriorityPrecedence.MIDPOINT) @Qualifier("Default") -@RequiredArgsConstructor(onConstructor_ = @Inject) -public class TitleServiceDefault implements TitleService { - - private final WrapperFactory wrapperFactory; - private final ObjectManager objectManager; +public record TitleServiceDefault( + ObjectManager objectManager, + WrapperFactory wrapperFactory) +implements TitleService { @Override public String titleOf(final Object domainObject) { - - if(objectManager == null) return "" + domainObject; // simplified JUnit test support - var pojo = unwrapped(domainObject); var objectAdapter = objectManager.adapt(pojo); @@ -68,17 +62,14 @@ public String titleOf(final Object domainObject) { } @Override - public String iconNameOf(final Object domainObject) { - - if(objectManager == null) // simplified JUnit test support - return domainObject!=null ? domainObject.getClass().getSimpleName() : "null"; - + public ObjectSupport.IconResource iconOf(final Object domainObject, final ObjectSupport.IconWhere iconWhere) { var pojo = unwrapped(domainObject); var objectAdapter = objectManager.adapt(pojo); return ManagedObjects.isNullOrUnspecifiedOrEmpty(objectAdapter) - ? "unspecified" - : objectAdapter.objSpec().getIconName(objectAdapter).orElse(null); + ? null + : objectAdapter.objSpec().getIcon(objectAdapter, iconWhere) + .orElse(null); } //-- HELPER 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 90ba1bd210e..2e0f4b4e699 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 @@ -31,8 +31,8 @@ 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; import org.apache.causeway.applib.exceptions.UnrecoverableException; -import org.apache.causeway.applib.fa.FontAwesomeLayers; import org.apache.causeway.applib.id.HasLogicalType; import org.apache.causeway.applib.id.LogicalType; import org.apache.causeway.applib.services.metamodel.BeanSort; @@ -57,8 +57,6 @@ import org.apache.causeway.core.metamodel.facets.collections.CollectionFacet; import org.apache.causeway.core.metamodel.facets.members.cssclass.CssClassFacet; import org.apache.causeway.core.metamodel.facets.object.entity.EntityFacet; -import org.apache.causeway.core.metamodel.facets.object.icon.IconFacet; -import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIcon; import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIconService; import org.apache.causeway.core.metamodel.facets.object.immutable.ImmutableFacet; import org.apache.causeway.core.metamodel.facets.object.mixin.MixinFacet; @@ -240,21 +238,19 @@ default Optional<MixedInAction> lookupMixedInAction(final ObjectSpecification mi /** * Returns the title to display of target adapter, rendered within the context * of some other adapter (if any). - * <p> + * * @see TitleFacet#title(TitleRenderRequest) */ String getTitle(TitleRenderRequest titleRenderRequest); /** - * Optionally returns the name-suffix (or embedded image data) of an icon to use for the specified object. - * <p> - * Corresponds to the {@link IconFacet#iconName(ManagedObject) icon name} - * returned by the {@link IconFacet}; is not necessarily immutable. + * Optionally returns the resource definition of an icon to use for the specified object. + * + * <p>Corresponds to the icon(...) object support method. * @see ObjectIconService + * @since 4.0 */ - Optional<String> getIconName(ManagedObject object); - - ObjectIcon getIcon(ManagedObject object); + Optional<ObjectSupport.IconResource> getIcon(ManagedObject object, ObjectSupport.IconWhere iconWhere); /** * Returns this object's navigable parent, if any. @@ -273,8 +269,6 @@ default Optional<MixedInAction> lookupMixedInAction(final ObjectSpecification mi */ String getCssClass(ManagedObject domainObject); - Optional<FontAwesomeLayers> getFaLayers(ManagedObject domainObject); - /** * @return optionally the element type spec based on presence of the TypeOfFacet * @since 2.0 @@ -293,9 +287,7 @@ default Optional<MixedInAction> lookupMixedInAction(final ObjectSpecification mi */ Optional<Contributing> contributing(); - // ////////////////////////////////////////////////////////////// - // TitleContext - // ////////////////////////////////////////////////////////////// + // -- TITLE CONTEXT /** * Create an {@link InteractionContext} representing an attempt to read the @@ -305,9 +297,7 @@ ObjectTitleContext createTitleInteractionContext( ManagedObject targetObjectAdapter, InteractionInitiatedBy invocationMethod); - // ////////////////////////////////////////////////////////////// - // ValidityContext, Validity - // ////////////////////////////////////////////////////////////// + // -- VALIDITY // internal API ObjectValidityContext createValidityInteractionContext( @@ -330,9 +320,7 @@ InteractionResult isValidResult( final ManagedObject targetAdapter, final InteractionInitiatedBy interactionInitiatedBy); - // ////////////////////////////////////////////////////////////// - // Facets - // ////////////////////////////////////////////////////////////// + // -- FACETS /** * Determines if the object represents an value or object. 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 442df8b7344..e1718fa11c1 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 @@ -39,7 +39,7 @@ import org.apache.causeway.applib.annotation.Domain; import org.apache.causeway.applib.annotation.DomainService; import org.apache.causeway.applib.annotation.Introspection.IntrospectionPolicy; -import org.apache.causeway.applib.fa.FontAwesomeLayers; +import org.apache.causeway.applib.annotation.ObjectSupport; import org.apache.causeway.applib.id.LogicalType; import org.apache.causeway.applib.services.metamodel.BeanSort; import org.apache.causeway.commons.collections.Can; @@ -79,11 +79,8 @@ import org.apache.causeway.core.metamodel.facets.all.named.MemberNamedFacetForStaticMemberName; import org.apache.causeway.core.metamodel.facets.all.named.ObjectNamedFacet; import org.apache.causeway.core.metamodel.facets.members.cssclass.CssClassFacet; -import org.apache.causeway.core.metamodel.facets.members.iconfa.FaFacet; -import org.apache.causeway.core.metamodel.facets.members.iconfa.FaLayersProvider; import org.apache.causeway.core.metamodel.facets.object.entity.EntityFacet; import org.apache.causeway.core.metamodel.facets.object.icon.IconFacet; -import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIcon; import org.apache.causeway.core.metamodel.facets.object.immutable.ImmutableFacet; import org.apache.causeway.core.metamodel.facets.object.introspection.IntrospectionPolicyFacet; import org.apache.causeway.core.metamodel.facets.object.logicaltype.AliasedFacet; @@ -771,30 +768,14 @@ public String getTitle(final TitleRenderRequest titleRenderRequest) { private void notifySubscribersIfEntity( final TitleRenderRequest titleRenderRequest, final String titleString) { - if (!isEntity()) { - return; - } + if (!isEntity()) return; + var managedObject = titleRenderRequest.object(); managedObject.getBookmark().ifPresent(bookmark -> { getTitleSubscribers().stream().forEach(x -> x.entityTitleIs(bookmark, titleString)); }); } - @Override - public Optional<String> getIconName(final ManagedObject domainObject) { - if(ManagedObjects.isSpecified(domainObject)) { - _Assert.assertEquals(domainObject.objSpec(), this); - } - return Optional.ofNullable(iconFacet) - .flatMap(facet->facet.iconName(domainObject)); - } - - @Override - public ObjectIcon getIcon(final ManagedObject domainObject) { - return getObjectIconService() - .getObjectIcon(this, getIconName(domainObject), getFaLayers(domainObject)); - } - @Override public Object getNavigableParent(final Object object) { return navigableParentFacet != null @@ -809,16 +790,6 @@ public String getCssClass(final ManagedObject reference) { : null; } - @Override - public Optional<FontAwesomeLayers> getFaLayers(final ManagedObject reference){ - return lookupFacet(FaFacet.class) - .map(FaFacet::getSpecialization) - .map(either->either.fold( - faStaticFacet->(FaLayersProvider)faStaticFacet, - faImperativeFacet->faImperativeFacet.getFaLayersProvider(reference))) - .map(FaLayersProvider::getLayers); - } - @Override public Can<LogicalType> getAliases() { return aliasedFacet != null @@ -826,6 +797,17 @@ public Can<LogicalType> getAliases() { : Can.empty(); } + // -- ICON + + @Override + public Optional<ObjectSupport.IconResource> getIcon(final ManagedObject domainObject, ObjectSupport.IconWhere iconWhere) { + if(ManagedObjects.isSpecified(domainObject)) { + _Assert.assertEquals(domainObject.objSpec(), this); + } + return Optional.ofNullable(iconFacet) + .flatMap(facet->facet.icon(domainObject, iconWhere)); + } + // -- HIERARCHICAL @Override diff --git a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/FacetFactoryTestAbstract.java b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/FacetFactoryTestAbstract.java index 34696de3e6b..6e284489ccb 100644 --- a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/FacetFactoryTestAbstract.java +++ b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/FacetFactoryTestAbstract.java @@ -433,8 +433,8 @@ protected void objectScenario(final Class<?> declaringClass, final BiConsumer<Pr // -- UTILITY - protected static ResolvedMethod findMethodExactOrFail(final Class<?> type, final String methodName, final Class<?>[] methodTypes) { - return _Utils.findMethodExactOrFail(type, methodName, methodTypes); + protected static ResolvedMethod findMethodExactOrFail(final Class<?> type, final String methodName, final Class<?>[] paramTypes) { + return _Utils.findMethodExactOrFail(type, methodName, paramTypes); } protected static ResolvedMethod findMethodExactOrFail(final Class<?> type, final String methodName) { diff --git a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/iconfa/FontAwesomeLayersFacetMethodTest.java b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/icon/IconFacetMethodFaTest.java similarity index 66% rename from core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/iconfa/FontAwesomeLayersFacetMethodTest.java rename to core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/icon/IconFacetMethodFaTest.java index 20349d92711..cf730cde426 100644 --- a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/iconfa/FontAwesomeLayersFacetMethodTest.java +++ b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/icon/IconFacetMethodFaTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.causeway.core.metamodel.facets.object.ident.iconfa; +package org.apache.causeway.core.metamodel.facets.object.ident.icon; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -29,21 +29,22 @@ import org.apache.causeway.applib.annotation.DomainObject; import org.apache.causeway.applib.annotation.Introspection; import org.apache.causeway.applib.annotation.ObjectSupport; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.applib.fa.FontAwesomeLayers; import org.apache.causeway.core.metamodel.facets.FacetFactoryTestAbstract; -import org.apache.causeway.core.metamodel.facets.members.iconfa.FaFacet; -import org.apache.causeway.core.metamodel.facets.object.iconfa.method.FaFacetViaIconFaLayersMethod; +import org.apache.causeway.core.metamodel.facets.object.icon.IconFacet; +import org.apache.causeway.core.metamodel.facets.object.icon.method.IconFacetViaIconMethod; import org.apache.causeway.core.metamodel.facets.object.support.ObjectSupportFacetFactory; -class FontAwesomeLayersFacetMethodTest +class IconFacetMethodFaTest extends FacetFactoryTestAbstract { static final FontAwesomeLayers FONTAWESOME_LAYERS_SAMPLE = FontAwesomeLayers.singleIcon("fa-solid fa-bookmark"); @DomainObject(introspection = Introspection.ENCAPSULATION_ENABLED) - static class DomainObjectWithFontAwesomeLayersMethod { - @ObjectSupport public FontAwesomeLayers iconFaLayers() { - return FONTAWESOME_LAYERS_SAMPLE; + static class DomainObjectWithFontAwesomeIconViaMethod { + @ObjectSupport public ObjectSupport.IconResource icon(final ObjectSupport.IconWhere iconWhere) { + return new ObjectSupport.FontAwesomeIconResource(FONTAWESOME_LAYERS_SAMPLE); } } @@ -61,19 +62,23 @@ protected void tearDown() throws Exception { } @Test - void fontAwesomeLayersFacetViaIconFaLayersMethod() { - var domainObject = getObjectManager().adapt(new DomainObjectWithFontAwesomeLayersMethod()); + void fontAwesomeLayersViaIconMethod() { + var domainObject = getObjectManager().adapt(new DomainObjectWithFontAwesomeIconViaMethod()); - objectScenario(DomainObjectWithFontAwesomeLayersMethod.class, (processClassContext, facetHolder) -> { + objectScenario(DomainObjectWithFontAwesomeIconViaMethod.class, (processClassContext, facetHolder) -> { //when facetFactory.process(processClassContext); //then - var fontAwesomeLayersFacet = facetHolder.getFacet(FaFacet.class); - assertNotNull(fontAwesomeLayersFacet, ()->"FaFacet required"); - assertTrue(fontAwesomeLayersFacet instanceof FaFacetViaIconFaLayersMethod); - var imperativeCssClassFacet = (FaFacetViaIconFaLayersMethod)fontAwesomeLayersFacet; + var iconFacet = facetHolder.getFacet(IconFacet.class); + assertNotNull(iconFacet, ()->"IconFacet required"); + assertTrue(iconFacet instanceof IconFacetViaIconMethod); + var imperativeFacet = (IconFacetViaIconMethod)iconFacet; - var actual = imperativeCssClassFacet.getFaLayersProvider(domainObject).getLayers(); + var actual = imperativeFacet.icon(domainObject, IconWhere.OBJECT_HEADER) + .filter(ObjectSupport.FontAwesomeIconResource.class::isInstance) + .map(ObjectSupport.FontAwesomeIconResource.class::cast) + .map(ObjectSupport.FontAwesomeIconResource::faLayers) + .orElse(null); assertEquals(FONTAWESOME_LAYERS_SAMPLE, actual); assertEquals( FontAwesomeLayers.normalizeCssClasses("fa-solid fa-bookmark", "fa"), diff --git a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/icon/IconFacetMethodFactoryTest.java b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/icon/IconFacetMethodFactoryTest.java index d56ae9f9f35..b80dc103263 100644 --- a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/icon/IconFacetMethodFactoryTest.java +++ b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/icon/IconFacetMethodFactoryTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; +import org.apache.causeway.applib.annotation.ObjectSupport; import org.apache.causeway.core.config.progmodel.ProgrammingModelConstants.ObjectSupportMethod; import org.apache.causeway.core.metamodel.facets.object.icon.IconFacet; import org.apache.causeway.core.metamodel.facets.object.support.ObjectSupportFacetFactoryTestAbstract; @@ -29,12 +30,12 @@ class IconFacetMethodFactoryTest @Test void iconNameMethodPickedUpOnClassAndMethodRemoved() { - @SuppressWarnings("unused") class Customer { - public String iconName() { return null; } - + @ObjectSupport public ObjectSupport.IconResource icon(final ObjectSupport.IconWhere iconWhere) { + return null; + } } - assertPicksUp(1, facetFactory, Customer.class, ObjectSupportMethod.ICON_NAME, IconFacet.class); + assertPicksUp(1, facetFactory, Customer.class, ObjectSupportMethod.ICON, IconFacet.class, ObjectSupport.IconWhere.class); } } diff --git a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/icon/IconFacetMethodTest.java b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/icon/IconFacetMethodTest.java index 2a797165dfd..f4b7124f21c 100644 --- a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/icon/IconFacetMethodTest.java +++ b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/icon/IconFacetMethodTest.java @@ -27,22 +27,24 @@ import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +import org.apache.causeway.applib.annotation.ObjectSupport; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.commons.internal.reflection._GenericResolver; import org.apache.causeway.core.metamodel.facetapi.FacetHolder; import org.apache.causeway.core.metamodel.facets.Mocking; -import org.apache.causeway.core.metamodel.facets.object.icon.method.IconFacetViaIconNameMethod; +import org.apache.causeway.core.metamodel.facets.object.icon.method.IconFacetViaIconMethod; import org.apache.causeway.core.metamodel.object.ManagedObject; class IconFacetMethodTest { private Mocking mocking = new Mocking(); - private IconFacetViaIconNameMethod facet; + private IconFacetViaIconMethod facet; private ManagedObject mockOwningAdapter; private DomainObjectWithProblemInIconNameMethod pojo; public static class DomainObjectWithProblemInIconNameMethod { - public String iconName() { + @ObjectSupport public ObjectSupport.IconResource icon(final ObjectSupport.IconWhere iconWhere) { throw new NullPointerException("for testing purposes"); } } @@ -51,11 +53,11 @@ public String iconName() { public void setUp() throws Exception { pojo = new DomainObjectWithProblemInIconNameMethod(); - var iconNameMethod = _GenericResolver.testing - .resolveMethod(DomainObjectWithProblemInIconNameMethod.class, "iconName"); + var iconMethod = _GenericResolver.testing + .resolveMethod(DomainObjectWithProblemInIconNameMethod.class, "icon", ObjectSupport.IconWhere.class); - facet = (IconFacetViaIconNameMethod) IconFacetViaIconNameMethod - .create(iconNameMethod, Mockito.mock(FacetHolder.class)) + facet = (IconFacetViaIconMethod) IconFacetViaIconMethod + .create(iconMethod, Mockito.mock(FacetHolder.class)) .orElse(null); mockOwningAdapter = mocking.asViewmodel(pojo); @@ -67,10 +69,9 @@ public void tearDown() throws Exception { } @Test - void iconNameThrowsException() { - //assertThrows(Exception.class, ()->facet.iconName(mockOwningAdapter)); - final String iconName = facet.iconName(mockOwningAdapter).orElse(null); - assertThat(iconName, is(nullValue())); + void iconIsNull() { + var icon = facet.icon(mockOwningAdapter, IconWhere.OBJECT_HEADER).orElse(null); + assertThat(icon, is(nullValue())); } } diff --git a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/iconfa/FontAwesomeLayersFacetMethodFactoryTest.java b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/iconfa/FontAwesomeLayersFacetMethodFactoryTest.java deleted file mode 100644 index 69a4dbf67ba..00000000000 --- a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/ident/iconfa/FontAwesomeLayersFacetMethodFactoryTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.facets.object.ident.iconfa; - -import org.junit.jupiter.api.Test; - -import org.apache.causeway.applib.fa.FontAwesomeLayers; -import org.apache.causeway.core.config.progmodel.ProgrammingModelConstants.ObjectSupportMethod; -import org.apache.causeway.core.metamodel.facets.members.iconfa.FaFacet; -import org.apache.causeway.core.metamodel.facets.object.support.ObjectSupportFacetFactoryTestAbstract; - -class FontAwesomeLayersFacetMethodFactoryTest -extends ObjectSupportFacetFactoryTestAbstract { - - @Test - void iconNameMethodPickedUpOnClassAndMethodRemoved() { - @SuppressWarnings("unused") - class Customer { - public FontAwesomeLayers iconFaLayers() { return null; } - } - assertPicksUp(1, facetFactory, Customer.class, ObjectSupportMethod.ICON_FA_LAYERS, FaFacet.class); - } - -} diff --git a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/support/ObjectSupportFacetFactoryTestAbstract.java b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/support/ObjectSupportFacetFactoryTestAbstract.java index 7a461e5e763..086a9e17bf6 100644 --- a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/support/ObjectSupportFacetFactoryTestAbstract.java +++ b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/support/ObjectSupportFacetFactoryTestAbstract.java @@ -56,14 +56,15 @@ protected void assertPicksUp( final FacetFactory facetFactory, final Class<?> type, final ProgrammingModelConstants.ObjectSupportMethod supportMethodEnum, - final Class<? extends Facet> facetType) { + final Class<? extends Facet> facetType, + final Class<?> ...paramTypes) { objectScenario(type, (processClassContext, facetHolder) -> { //when facetFactory.process(processClassContext); //then var supportMethods = supportMethodEnum.getMethodNames() - .map(methodName->findMethodExactOrFail(type, methodName)) + .map(methodName->findMethodExactOrFail(type, methodName, paramTypes)) .map(_MethodFacades::regular) .map(MethodFacade::asMethodElseFail); diff --git a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/title/TitleServiceDefaultTest.java b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/title/TitleServiceDefaultTest.java index 2f94fa99d8c..acf798ac3c3 100644 --- a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/title/TitleServiceDefaultTest.java +++ b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/title/TitleServiceDefaultTest.java @@ -34,7 +34,7 @@ void setUp() throws Exception { var mmc = MetaModelContext_forTesting.buildDefault(); - titleService = new TitleServiceDefault(null, mmc.getObjectManager()); + titleService = new TitleServiceDefault(mmc.getObjectManager(), null); } // -- FEATURED @@ -60,7 +60,6 @@ void enum_shouldHonorTitleByMethod() { var title = titleService.titleOf(domainObject); assertEquals("first", title); - } // -- PLAIN @@ -77,7 +76,6 @@ void enum_shouldFallbackTitleToEnumName() { var title = titleService.titleOf(domainObject); assertEquals("First", title); - } } diff --git a/core/mmtestsupport/src/main/java/org/apache/causeway/core/mmtestsupport/MetaModelContext_forTesting.java b/core/mmtestsupport/src/main/java/org/apache/causeway/core/mmtestsupport/MetaModelContext_forTesting.java index caa24d65fbf..b170e7e1d3f 100644 --- a/core/mmtestsupport/src/main/java/org/apache/causeway/core/mmtestsupport/MetaModelContext_forTesting.java +++ b/core/mmtestsupport/src/main/java/org/apache/causeway/core/mmtestsupport/MetaModelContext_forTesting.java @@ -96,7 +96,6 @@ import org.apache.causeway.core.metamodel.services.grid.spi.LayoutResourceLoaderDefault; import org.apache.causeway.core.metamodel.services.layout.LayoutServiceDefault; import org.apache.causeway.core.metamodel.services.message.MessageServiceNoop; -import org.apache.causeway.core.metamodel.services.title.TitleServiceDefault; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.impl.CausewayModuleCoreMetamodelConfigurationDefault; import org.apache.causeway.core.metamodel.spec.impl._JUnitSupport; @@ -203,7 +202,7 @@ public CausewayConfiguration getConfiguration() { private AuthenticationManager authenticationManager; @Builder.Default @Getter - private TitleService titleService = new TitleServiceDefault(null, null); + private TitleService titleService = new TitleServiceForTesting(); @Getter private ObjectIconService objectIconService; diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/IconFacetAbstract.java b/core/mmtestsupport/src/main/java/org/apache/causeway/core/mmtestsupport/TitleServiceForTesting.java similarity index 56% rename from core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/IconFacetAbstract.java rename to core/mmtestsupport/src/main/java/org/apache/causeway/core/mmtestsupport/TitleServiceForTesting.java index 3931dabe151..528e8b1a29c 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/icon/IconFacetAbstract.java +++ b/core/mmtestsupport/src/main/java/org/apache/causeway/core/mmtestsupport/TitleServiceForTesting.java @@ -16,26 +16,23 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.causeway.core.metamodel.facets.object.icon; +package org.apache.causeway.core.mmtestsupport; -import org.apache.causeway.core.metamodel.facetapi.Facet; -import org.apache.causeway.core.metamodel.facetapi.FacetAbstract; -import org.apache.causeway.core.metamodel.facetapi.FacetHolder; +import org.apache.causeway.applib.annotation.ObjectSupport; +import org.apache.causeway.applib.annotation.ObjectSupport.ClassPathIconResource; +import org.apache.causeway.applib.services.title.TitleService; -public abstract class IconFacetAbstract -extends FacetAbstract -implements IconFacet { +public record TitleServiceForTesting() +implements TitleService { - private static final Class<? extends Facet> type() { - return IconFacet.class; + @Override + public String titleOf(final Object domainObject) { + return "" + domainObject; // simplified JUnit test support } - public IconFacetAbstract(final FacetHolder holder) { - super(type(), holder); - } - - public IconFacetAbstract(final FacetHolder holder, final Facet.Precedence precedence) { - super(type(), holder, precedence); + @Override + public ObjectSupport.IconResource iconOf(final Object domainObject, final ObjectSupport.IconWhere iconWhere) { + return domainObject!=null ? new ClassPathIconResource("") : null; } } diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/icons/ObjectIconServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/icons/ObjectIconServiceDefault.java index 0bd5bd6c1a3..e4e91c5f1c9 100644 --- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/icons/ObjectIconServiceDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/icons/ObjectIconServiceDefault.java @@ -28,25 +28,31 @@ import jakarta.inject.Named; import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import org.apache.causeway.applib.annotation.ObjectSupport; +import org.apache.causeway.applib.annotation.ObjectSupport.EmbeddedIconResource; +import org.apache.causeway.applib.annotation.ObjectSupport.FontAwesomeIconResource; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; +import org.apache.causeway.applib.annotation.ObjectSupport.ClassPathIconResource; import org.apache.causeway.applib.annotation.PriorityPrecedence; -import org.apache.causeway.applib.fa.FontAwesomeLayers; import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType; import org.apache.causeway.commons.collections.Can; import org.apache.causeway.commons.internal.base._StableValue; import org.apache.causeway.commons.internal.base._Strings; import org.apache.causeway.commons.internal.exceptions._Exceptions; import org.apache.causeway.commons.internal.resources._Resources; -import org.apache.causeway.commons.net.DataUri; import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIcon; import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIconEmbedded; import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIconFa; import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIconService; import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIconUrlBased; +import org.apache.causeway.core.metamodel.object.ManagedObject; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.runtimeservices.CausewayModuleCoreRuntimeServices; @@ -63,7 +69,7 @@ record ObjectIconServiceDefault( ResourceLoader resourceLoader, Map<String, ObjectIcon> iconByKey, - _StableValue<ObjectIcon> fallbackIcon) + _StableValue<ObjectIcon> fallbackIconRef) implements ObjectIconService { private static final String DEFAULT_IMAGE_RESOURCE_PATH = "classpath:images"; @@ -81,53 +87,58 @@ public ObjectIconServiceDefault(ResourceLoader resourceLoader) { } @Override - public ObjectIcon getObjectIcon( - final ObjectSpecification spec, - final Optional<String> iconName, - final Optional<FontAwesomeLayers> faLayers) { + public ObjectIcon getObjectIcon(ManagedObject managedObject, IconWhere iconWhere) { + + var spec = managedObject.objSpec(); + + return spec.getIcon(managedObject, iconWhere) + .map(iconResource->{ + if(iconResource instanceof ObjectSupport.EmbeddedIconResource embedded) + return embedded(spec, embedded); + if(iconResource instanceof ObjectSupport.FontAwesomeIconResource fa) + return fa(spec, fa); + if(iconResource instanceof ObjectSupport.ClassPathIconResource suffixed) + return suffixed(spec, suffixed); + throw _Exceptions.unmatchedCase(iconResource); + }) + .orElseGet(this::fallbackIcon); + } - var domainClass = spec.getCorrespondingClass(); - var iconNameSuffixIfAny = iconName.orElse(null); + // -- HELPER - var suffix = ""; - if(StringUtils.hasLength(iconNameSuffixIfAny)) { - if(iconNameSuffixIfAny.startsWith("data:")) { - return new ObjectIconEmbedded(domainClass.getSimpleName(), DataUri.parse(iconNameSuffixIfAny)); - } - suffix = "-" + iconNameSuffixIfAny; - } else if(faLayers.isPresent()) { - return new ObjectIconFa(domainClass.getSimpleName(), faLayers.get()); - } + private ObjectIcon embedded(ObjectSpecification objSpec, EmbeddedIconResource embeddedIconResource) { + return new ObjectIconEmbedded(objSpec.getCorrespondingClass().getSimpleName(), embeddedIconResource.dataUri()); + } - var iconResourceKey = domainClass.getName() + suffix; + private ObjectIcon fa(ObjectSpecification objSpec, FontAwesomeIconResource faIconResource) { + return new ObjectIconFa(objSpec.getCorrespondingClass().getSimpleName(), faIconResource.faLayers()); + } + private ObjectIcon suffixed(ObjectSpecification objSpec, ClassPathIconResource cpIconResource) { + var domainClass = objSpec.getCorrespondingClass(); + var iconResourceKey = StringUtils.hasLength(cpIconResource.suffix()) + ? domainClass.getName() + "-" + cpIconResource.suffix() + : domainClass.getName(); // also memoize unsuccessful icon lookups (as fallback), so we don't search repeatedly - var cachedIcon = iconByKey.get(iconResourceKey); if(cachedIcon!=null) return cachedIcon; - var icon = findIcon(spec, iconName); - - //NOTE: cannot use computeIfAbsent, as it does not support recursive update - // return iconByKey.computeIfAbsent(iconResourceKey, key-> - // findIcon(spec, iconNameModifier)); + var icon = findIcon(objSpec, _Strings.nonEmpty(cpIconResource.suffix())); iconByKey.put(iconResourceKey, icon); - return icon; } - // -- HELPER - - private ObjectIcon getObjectFallbackIcon() { - return fallbackIcon.orElseSet(()->ObjectIconUrlBased.eager( + private ObjectIcon fallbackIcon() { + return fallbackIconRef.orElseSet(()->ObjectIconUrlBased.eager( "ObjectIconFallback", _Resources.lookupResourceUrl( ObjectIconServiceDefault.class, "ObjectIconFallback.png") - .orElse(null), + .orElse(null), CommonMimeType.PNG)); } + @Nullable private ObjectIcon findIcon( final ObjectSpecification spec, final Optional<String> iconName) { @@ -145,7 +156,7 @@ private ObjectIcon findIcon( var objectIcon = imageType .proposedFileExtensions() .stream() - .map(suffix->iconResourceNameNoExt + "." + suffix) + .map(ext->iconResourceNameNoExt + "." + ext) .map(iconResourceName-> classPathResource(domainClass, iconResourceName) .map(url->ObjectIconUrlBased.lazy( @@ -182,14 +193,13 @@ private ObjectIcon findIcon( return spec.superclass()!=null // continue search in super spec - ? getObjectIcon(spec.superclass(), iconName, Optional.empty()) // memoizes as a side-effect + ? findIcon(spec.superclass(), iconName) // memoizes as a side-effect : _Strings.isNotEmpty(iconNameSuffixIfAny) // also do a more generic search, skipping the modifier - ? getObjectIcon(spec, Optional.empty(), Optional.empty()) // memoizes as a side-effect - : getObjectFallbackIcon(); + ? findIcon(spec, Optional.empty()) // memoizes as a side-effect + : null; } - // -- HELPER @SneakyThrows private Optional<URL> classPathResource( diff --git a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperFullyAbstract.java b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperFullyAbstract.java index 8cbce54516d..1a40784047f 100644 --- a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperFullyAbstract.java +++ b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperFullyAbstract.java @@ -31,7 +31,7 @@ abstract class ProperFullyAbstract { @ObjectSupport public abstract String title(); - @ObjectSupport public abstract String iconName(); + @ObjectSupport public abstract ObjectSupport.IconResource icon(ObjectSupport.IconWhere iconWhere); @ObjectSupport public abstract String cssClass(); @ObjectSupport public abstract String layout(); diff --git a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperFullyImpl.java b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperFullyImpl.java index 39b588bc53b..d5de33dce25 100644 --- a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperFullyImpl.java +++ b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperFullyImpl.java @@ -22,6 +22,7 @@ import org.apache.causeway.applib.annotation.DomainObject; import org.apache.causeway.applib.annotation.Nature; +import org.apache.causeway.applib.annotation.ObjectSupport; @DomainObject(nature = Nature.VIEW_MODEL) public class ProperFullyImpl @@ -33,8 +34,8 @@ public String title() { } @Override - public String iconName() { - return "icon"; + public ObjectSupport.IconResource icon(final ObjectSupport.IconWhere iconWhere) { + return new ObjectSupport.ClassPathIconResource("icon"); } @Override diff --git a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperMemberInheritanceAbstract.java b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperMemberInheritanceAbstract.java index 1b9047c17da..f7fbb3d86dd 100644 --- a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperMemberInheritanceAbstract.java +++ b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperMemberInheritanceAbstract.java @@ -37,8 +37,8 @@ abstract class ProperMemberInheritanceAbstract { return "inherited title"; } - @ObjectSupport public String iconName() { - return "inherited icon"; + @ObjectSupport public ObjectSupport.IconResource icon(final ObjectSupport.IconWhere iconWhere) { + return new ObjectSupport.ClassPathIconResource("inherited icon"); } @ObjectSupport public String cssClass(){ diff --git a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperMemberInheritanceInterface.java b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperMemberInheritanceInterface.java index c02d0c1ec0a..d70b7aca363 100644 --- a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperMemberInheritanceInterface.java +++ b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/good/ProperMemberInheritanceInterface.java @@ -35,9 +35,8 @@ default String title() { return "inherited title"; } - @ObjectSupport - default String iconName() { - return "inherited icon"; + @ObjectSupport default ObjectSupport.IconResource icon(final ObjectSupport.IconWhere iconWhere) { + return new ObjectSupport.ClassPathIconResource("inherited icon"); } @Action diff --git a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/util/interaction/DomainObjectTesterFactory.java b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/util/interaction/DomainObjectTesterFactory.java index ae05a7f37d0..d9e947164aa 100644 --- a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/util/interaction/DomainObjectTesterFactory.java +++ b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/util/interaction/DomainObjectTesterFactory.java @@ -40,6 +40,8 @@ import org.springframework.stereotype.Service; import org.apache.causeway.applib.Identifier; +import org.apache.causeway.applib.annotation.ObjectSupport; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.applib.annotation.Where; import org.apache.causeway.applib.exceptions.unrecoverable.DomainModelException; import org.apache.causeway.applib.id.LogicalType; @@ -225,11 +227,12 @@ public void assertTitle(final @Nullable String expectedResult) { } public void assertIcon(final @Nullable String expectedResult) { - assertEquals(expectedResult, - super.objectSpecification.getTitleService().iconNameOf(vm.getPojo())); - assertEquals(expectedResult, + var expectedIcon = new ObjectSupport.ClassPathIconResource(expectedResult); + assertEquals(expectedIcon, + super.objectSpecification.getTitleService().iconOf(vm.getPojo(), IconWhere.OBJECT_HEADER)); + assertEquals(expectedIcon, super.objectSpecification.lookupFacet(IconFacet.class) - .flatMap(iconFacet->iconFacet.iconName(vm)) + .flatMap(iconFacet->iconFacet.icon(vm, IconWhere.OBJECT_HEADER)) .orElse(null)); } diff --git a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingGoodDomain.java b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingGoodDomain.java index d4ed1c8f53a..2e1d5d05516 100644 --- a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingGoodDomain.java +++ b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingGoodDomain.java @@ -44,6 +44,8 @@ import org.apache.causeway.applib.annotation.Introspection.EncapsulationPolicy; import org.apache.causeway.applib.annotation.Introspection.MemberAnnotationPolicy; +import org.apache.causeway.applib.annotation.ObjectSupport; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.applib.id.LogicalType; import org.apache.causeway.applib.services.jaxb.JaxbService; import org.apache.causeway.applib.services.metamodel.BeanSort; @@ -292,11 +294,15 @@ void titleAndIconName_shouldBeInheritable(final Class<?> type) throws Exception if(!spec.isAbstract()) { var instance = type.getDeclaredConstructor().newInstance(); assertEquals("inherited title", titleService.titleOf(instance)); - assertEquals("inherited icon", titleService.iconNameOf(instance)); + assertEquals( + new ObjectSupport.ClassPathIconResource("inherited icon"), + titleService.iconOf(instance, IconWhere.OBJECT_HEADER)); var domainObject = ManagedObject.adaptSingular(spec, instance); assertEquals("inherited title", domainObject.getTitle()); - assertEquals("inherited icon", iconFacet.iconName(domainObject).orElse(null)); + assertEquals( + new ObjectSupport.ClassPathIconResource("inherited icon"), + iconFacet.icon(domainObject, IconWhere.OBJECT_HEADER).orElse(null)); } } diff --git a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml index b6005b58e71..7f4840dc116 100644 --- a/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml +++ b/regressiontests/domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml @@ -9229,10 +9229,10 @@ <mml:attr name="interactionAdvisors" value="HidingOrShowing"/> <mml:attr name="precedence" value="HIGH"/> </mml:facet> - <mml:facet id="org.apache.causeway.core.metamodel.facets.object.icon.IconFacet" fqcn="org.apache.causeway.core.metamodel.facets.object.icon.method.IconFacetViaIconNameMethod"> - <mml:attr name="facet" value="IconFacetViaIconNameMethod"/> - <mml:attr name="intent.iconName" value="UI_HINT"/> - <mml:attr name="methods" value="public java.lang.String org.apache.causeway.testdomain.model.good.ProperFullyImpl.iconName()"/> + <mml:facet id="org.apache.causeway.core.metamodel.facets.object.icon.IconFacet" fqcn="org.apache.causeway.core.metamodel.facets.object.icon.method.IconFacetViaIconMethod"> + <mml:attr name="facet" value="IconFacetViaIconMethod"/> + <mml:attr name="intent.icon" value="UI_HINT"/> + <mml:attr name="methods" value="public org.apache.causeway.applib.annotation.ObjectSupport$IconResource org.apache.causeway.testdomain.model.good.ProperFullyImpl.icon(org.apache.causeway.applib.annotation.ObjectSupport$IconWhere)"/> <mml:attr name="precedence" value="DEFAULT"/> </mml:facet> <mml:facet id="org.apache.causeway.core.metamodel.facets.object.layout.LayoutPrefixFacet" fqcn="org.apache.causeway.core.metamodel.facets.object.layout.LayoutPrefixFacetViaMethod"> @@ -13567,10 +13567,10 @@ <mml:attr name="interactionAdvisors" value="HidingOrShowing"/> <mml:attr name="precedence" value="HIGH"/> </mml:facet> - <mml:facet id="org.apache.causeway.core.metamodel.facets.object.icon.IconFacet" fqcn="org.apache.causeway.core.metamodel.facets.object.icon.method.IconFacetViaIconNameMethod"> - <mml:attr name="facet" value="IconFacetViaIconNameMethod"/> - <mml:attr name="intent.iconName" value="UI_HINT"/> - <mml:attr name="methods" value="public java.lang.String org.apache.causeway.testdomain.model.good.ProperMemberInheritanceAbstract.iconName()"/> + <mml:facet id="org.apache.causeway.core.metamodel.facets.object.icon.IconFacet" fqcn="org.apache.causeway.core.metamodel.facets.object.icon.method.IconFacetViaIconMethod"> + <mml:attr name="facet" value="IconFacetViaIconMethod"/> + <mml:attr name="intent.icon" value="UI_HINT"/> + <mml:attr name="methods" value="public org.apache.causeway.applib.annotation.ObjectSupport$IconResource org.apache.causeway.testdomain.model.good.ProperMemberInheritanceAbstract.icon(org.apache.causeway.applib.annotation.ObjectSupport$IconWhere)"/> <mml:attr name="precedence" value="DEFAULT"/> </mml:facet> <mml:facet id="org.apache.causeway.core.metamodel.facets.object.layout.LayoutPrefixFacet" fqcn="org.apache.causeway.core.metamodel.facets.object.layout.LayoutPrefixFacetViaMethod"> @@ -14915,10 +14915,10 @@ <mml:attr name="interactionAdvisors" value="HidingOrShowing"/> <mml:attr name="precedence" value="HIGH"/> </mml:facet> - <mml:facet id="org.apache.causeway.core.metamodel.facets.object.icon.IconFacet" fqcn="org.apache.causeway.core.metamodel.facets.object.icon.method.IconFacetViaIconNameMethod"> - <mml:attr name="facet" value="IconFacetViaIconNameMethod"/> - <mml:attr name="intent.iconName" value="UI_HINT"/> - <mml:attr name="methods" value="public default java.lang.String org.apache.causeway.testdomain.model.good.ProperMemberInheritanceInterface.iconName()"/> + <mml:facet id="org.apache.causeway.core.metamodel.facets.object.icon.IconFacet" fqcn="org.apache.causeway.core.metamodel.facets.object.icon.method.IconFacetViaIconMethod"> + <mml:attr name="facet" value="IconFacetViaIconMethod"/> + <mml:attr name="intent.icon" value="UI_HINT"/> + <mml:attr name="methods" value="public default org.apache.causeway.applib.annotation.ObjectSupport$IconResource org.apache.causeway.testdomain.model.good.ProperMemberInheritanceInterface.icon(org.apache.causeway.applib.annotation.ObjectSupport$IconWhere)"/> <mml:attr name="precedence" value="DEFAULT"/> </mml:facet> <mml:facet id="org.apache.causeway.core.metamodel.facets.object.objectvalidprops.ObjectValidPropertiesFacet" fqcn="org.apache.causeway.core.metamodel.facets.object.objectvalidprops.impl.ObjectValidPropertiesFacetImpl"> diff --git a/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/mixin/HasIcon.java b/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/mixin/HasIcon.java index e02226fd958..2b19cc8f6df 100644 --- a/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/mixin/HasIcon.java +++ b/viewers/commons/model/src/main/java/org/apache/causeway/viewer/commons/model/mixin/HasIcon.java @@ -21,6 +21,7 @@ import java.util.Objects; import java.util.function.Consumer; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.commons.internal.exceptions._Exceptions; import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIcon; import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIconEmbedded; @@ -30,14 +31,15 @@ @FunctionalInterface public interface HasIcon { - ObjectIcon getIcon(); + ObjectIcon getIcon(IconWhere iconWhere); default void visitIconVariant( + IconWhere iconWhere, Consumer<ObjectIconUrlBased> a, Consumer<ObjectIconEmbedded> b, Consumer<ObjectIconFa> c) { - var objectIcon = Objects.requireNonNull(getIcon()); + var objectIcon = Objects.requireNonNull(getIcon(iconWhere)); if(objectIcon instanceof ObjectIconUrlBased urlBased){ a.accept(urlBased); } else if(objectIcon instanceof ObjectIconEmbedded embedded){ diff --git a/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/ResourceController.java b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/ResourceController.java index bf371186770..e10fff67c05 100644 --- a/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/ResourceController.java +++ b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/ResourceController.java @@ -36,6 +36,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.applib.layout.grid.Grid; import org.apache.causeway.applib.services.bookmark.Bookmark; import org.apache.causeway.applib.services.bookmark.BookmarkService; @@ -174,7 +175,7 @@ public ResponseEntity<byte[]> icon( } return lookup(logicalTypeName, id) - .map(ManagedObject::getIcon) + .map(mo->mo.getIcon(IconWhere.OBJECT_HEADER)) .filter(Objects::nonNull) .map(objectIcon -> { var bytes = objectIcon.iconData(); diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java index 615c30f048f..7312f45031f 100644 --- a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java +++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java @@ -27,6 +27,7 @@ import org.springframework.web.bind.annotation.RestController; import org.apache.causeway.applib.annotation.Where; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.applib.layout.component.ActionLayoutData; import org.apache.causeway.applib.layout.component.CollectionLayoutData; import org.apache.causeway.applib.layout.component.DomainObjectLayoutData; @@ -218,7 +219,7 @@ public ResponseEntity<Object> image( var objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId, roEx->_EndpointLogging.error(log, "GET /objects/{}/{}/object-icon", domainType, instanceId, roEx)); - var objectIcon = objectAdapter.getIcon(); + var objectIcon = objectAdapter.getIcon(IconWhere.OBJECT_HEADER); return _EndpointLogging.response(log, "GET /objects/{}/{}/object-icon", domainType, instanceId, responseFactory.ok( diff --git a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/BookmarkTreeNode.java b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/BookmarkTreeNode.java index 52fb4a6696f..4d287f581b3 100644 --- a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/BookmarkTreeNode.java +++ b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/BookmarkTreeNode.java @@ -28,6 +28,7 @@ import org.apache.wicket.request.resource.ResourceReference; import org.jspecify.annotations.NonNull; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.applib.fa.FontAwesomeLayers; import org.apache.causeway.applib.services.bookmark.Bookmark; import org.apache.causeway.commons.internal.base._Casts; @@ -75,6 +76,7 @@ private BookmarkTreeNode( _Casts.castTo(UiObjectWkt.class, bookmarkableModel) .ifPresent(x->x.visitIconVariantOrElse( + IconWhere.TABLE_ROW, rref->{this.iconResourceReference = rref;}, embedded->{this.embedded = embedded;}, faLayers->{this.faLayers = faLayers;}, diff --git a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/BookmarkableModel.java b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/BookmarkableModel.java index f4bdcd261a4..63a08db93a6 100644 --- a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/BookmarkableModel.java +++ b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/BookmarkableModel.java @@ -24,6 +24,7 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.causeway.applib.annotation.BookmarkPolicy; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.applib.services.bookmark.Bookmark; import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIcon; import org.apache.causeway.viewer.commons.model.mixin.HasIcon; @@ -65,7 +66,7 @@ default Stream<Bookmark> streamPropertyBookmarks() { * I believe actions only support the former. Hence the asymmetry here. */ @Override - default ObjectIcon getIcon() { + default ObjectIcon getIcon(IconWhere iconWhere) { return null; // overwritten for domain objects } diff --git a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/UiObjectWkt.java b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/UiObjectWkt.java index 30b7fd68235..ae48661a268 100644 --- a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/UiObjectWkt.java +++ b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/UiObjectWkt.java @@ -31,6 +31,7 @@ import org.jspecify.annotations.Nullable; import org.apache.causeway.applib.Identifier; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.applib.exceptions.unrecoverable.ObjectNotFoundException; import org.apache.causeway.applib.fa.FontAwesomeLayers; import org.apache.causeway.applib.services.bookmark.Bookmark; @@ -184,16 +185,18 @@ public String getTitle() { } @Override - public ObjectIcon getIcon() { - return getManagedObject().getIcon(); + public ObjectIcon getIcon(IconWhere iconWhere) { + return getManagedObject().getIcon(iconWhere); } public void visitIconVariantOrElse( + IconWhere iconWhere, Consumer<ResourceReference> a, Consumer<ObjectIconEmbedded> b, Consumer<FontAwesomeLayers> c, Runnable onNoMatch) { visitIconVariant( + iconWhere, urlBased->{ var rref = imageResourceCache().resourceReferenceForObjectIcon(urlBased); if(rref!=null) { diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/col/Col.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/col/Col.java index 691e20d72e6..f1f181c6ac3 100644 --- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/col/Col.java +++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/col/Col.java @@ -98,11 +98,10 @@ private void buildGui() { final String actionIdToUse; final String actionIdToHide; if(domainObject != null) { - final WebMarkupContainer entityHeaderPanel = new WebMarkupContainer(ID_OBJECT_HEADER_PANEL); + var entityHeaderPanel = new WebMarkupContainer(ID_OBJECT_HEADER_PANEL); div.add(entityHeaderPanel); - final ComponentFactory componentFactory = - getComponentFactoryRegistry().findComponentFactory(UiComponentType.OBJECT_ICON_TITLE_AND_COPYLINK, getModel()); - final Component component = componentFactory.createComponent(getModel()); + final Component component = getComponentFactoryRegistry() + .createComponent(UiComponentType.OBJECT_ICON_TITLE_AND_COPYLINK, getModel()); entityHeaderPanel.addOrReplace(component); actionOwner = entityHeaderPanel; diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanel.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanel.java index 80fc51d0860..0fd4cd24784 100644 --- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanel.java +++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanel.java @@ -24,6 +24,7 @@ import org.apache.wicket.MarkupContainer; import org.apache.wicket.markup.html.link.AbstractLink; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.applib.layout.component.CssClassFaPosition; import org.apache.causeway.commons.internal.assertions._Assert; import org.apache.causeway.commons.internal.base._Strings; @@ -61,10 +62,14 @@ class ObjectIconAndTitlePanel private static final String ID_OBJECT_TITLE = "objectTitle"; private static final String ID_OBJECT_ICON = "objectImage"; + private IconWhere iconWhere; + public ObjectIconAndTitlePanel( final String id, + final IconWhere iconWhere, final ObjectAdapterModel objectAdapterModel) { super(id, objectAdapterModel); + this.iconWhere = iconWhere; guardAgainstNonEmptyAbstractSingular(objectAdapterModel); } @@ -75,13 +80,13 @@ protected ManagedObject linkedDomainObject() { @Override protected void onBeforeRender() { - buildGui(); + buildGui(iconWhere); super.onBeforeRender(); } @Override - public ObjectIcon getIcon() { - return linkedDomainObject().getIcon(); + public ObjectIcon getIcon(IconWhere iconWhere) { + return linkedDomainObject().getIcon(iconWhere); } /** @@ -91,19 +96,19 @@ protected void onLinkWrapperCreated(final MarkupContainer linkWrapper) {} // -- HELPER - private void buildGui() { - addLinkWrapper(); + private void buildGui(IconWhere iconWhere) { + addLinkWrapper(iconWhere); setOutputMarkupId(true); } - private void addLinkWrapper() { + private void addLinkWrapper(IconWhere iconWhere) { var linkWrapper = Wkt.container(ID_OBJECT_LINK_WRAPPER); - linkWrapper.addOrReplace(createLinkWithIconAndTitle()); + linkWrapper.addOrReplace(createLinkWithIconAndTitle(iconWhere)); addOrReplace(linkWrapper); onLinkWrapperCreated(linkWrapper); } - private AbstractLink createLinkWithIconAndTitle() { + private AbstractLink createLinkWithIconAndTitle(IconWhere iconWhere) { final ManagedObject linkedDomainObject = linkedDomainObject(); final AbstractLink link = createDynamicallyVisibleLink(linkedDomainObject); @@ -116,6 +121,7 @@ private AbstractLink createLinkWithIconAndTitle() { Wkt.labelAdd(link, ID_OBJECT_TITLE, titleAbbreviated("(no object)")); } else { HasIcon.super.visitIconVariant( + iconWhere, iconUrlBased->{ Wkt.imageAddCachable(link, ID_OBJECT_ICON, getImageResourceCache().resourceReferenceForObjectIcon(iconUrlBased)); diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanelFactory.java index 854b710953d..a263b5c249d 100644 --- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanelFactory.java +++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanelFactory.java @@ -21,6 +21,7 @@ import org.apache.wicket.Component; import org.apache.wicket.model.IModel; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.commons.internal.exceptions._Exceptions; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.viewer.commons.model.components.UiComponentType; @@ -92,16 +93,17 @@ public Component createComponent(final String id, final IModel<?> model) { throw _Exceptions.unexpectedCodeReach(); } - return new ObjectIconAndTitlePanel(id, objectAdapterModel); + return new ObjectIconAndTitlePanel(id, IconWhere.TABLE_ROW, objectAdapterModel); } /** * refactoring hint: go through proper ComponentFactory channels instead */ + @Deprecated public static Component entityIconAndTitlePanel( final String componentId, final ObjectAdapterModel objectAdapterModel) { - return new ObjectIconAndTitlePanel(componentId, objectAdapterModel); + return new ObjectIconAndTitlePanel(componentId, IconWhere.TABLE_ROW, objectAdapterModel); } } diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconTitleAndCopyLinkPanel.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconTitleAndCopyLinkPanel.java index 87bb1324c4a..1c545901299 100644 --- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconTitleAndCopyLinkPanel.java +++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconTitleAndCopyLinkPanel.java @@ -20,12 +20,13 @@ import org.apache.wicket.MarkupContainer; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.viewer.wicket.model.models.UiObjectWkt; import org.apache.causeway.viewer.wicket.ui.components.widgets.zclip.ZeroClipboardPanel; /** * An extension of {@link org.apache.causeway.viewer.wicket.ui.components.object.icontitle.ObjectIconAndTitlePanel} - * that additionally has a link allowing to copy the url to the shown entity + * that additionally has a link allowing to copy the URL to the shown entity */ class ObjectIconTitleAndCopyLinkPanel extends ObjectIconAndTitlePanel { @@ -33,8 +34,8 @@ class ObjectIconTitleAndCopyLinkPanel extends ObjectIconAndTitlePanel { private static final String ID_COPY_LINK = "copyLink"; - public ObjectIconTitleAndCopyLinkPanel(final String id, final UiObjectWkt objectModel) { - super(id, objectModel); + public ObjectIconTitleAndCopyLinkPanel(final String id, final IconWhere iconWhere, final UiObjectWkt objectModel) { + super(id, iconWhere, objectModel); } @Override diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconTitleAndCopyLinkPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconTitleAndCopyLinkPanelFactory.java index fa2921f3b3a..0331be1c539 100644 --- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconTitleAndCopyLinkPanelFactory.java +++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconTitleAndCopyLinkPanelFactory.java @@ -21,6 +21,7 @@ import org.apache.wicket.Component; import org.apache.wicket.model.IModel; +import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere; import org.apache.causeway.viewer.commons.model.components.UiComponentType; import org.apache.causeway.viewer.wicket.model.models.UiObjectWkt; import org.apache.causeway.viewer.wicket.ui.components.object.ObjectComponentFactoryAbstract; @@ -38,6 +39,6 @@ public ObjectIconTitleAndCopyLinkPanelFactory() { @Override public Component createComponent(final String id, final IModel<?> model) { final UiObjectWkt objectModel = (UiObjectWkt) model; - return new ObjectIconTitleAndCopyLinkPanel(id, objectModel); + return new ObjectIconTitleAndCopyLinkPanel(id, IconWhere.OBJECT_HEADER, objectModel); } }
