From 2fc2ee1103ebf16df18fc9c2e05335c4424b31ce Mon Sep 17 00:00:00 2001
From: andi-huber
- * The zip contains:
+ * The zip contains:
* Xxx.columnOrder.txt files for the specified domain object.
*
- *
*
- * DomainClass itself
* DomainClass' collection with id collection1.
* DomainClass' collection with id collectionN.
*
- * These should be unzipped and copied in the domain class' package, and then their contents updated to specify the + *
These should be un-zipped and copied in the domain class' package, and then their contents updated to specify the * order in which the respective object's properties will be shown in the standalone or parented collections. - *
* * @see Object_downloadColumnOrderTxtFilesAsZip + * @see Object_patchColumnOrder * @see TableColumnOrderService * * @since 2.0 {@index} */ public interface ColumnOrderTxtFileService { - byte[] toZip(final Object domainObject); + byte[] toZip(Object domainObject); + + /** + * List of memberIds for given domainObject, that represent a {@link Collection}, including mixed-in ones. + * @since 4.0 + */ + List Each non-comment line can be mapped to a Java class of type {@code T}.
+ * @param
+ * If a line is not a comment, but can also not be mapped to {@code T},
+ * then it is commented out.
+ */
+ public Line parseLine(final String wholeLine) {
+ final var line = wholeLine.trim();
+ if(line.isBlank())
+ return new LineComment(line);
+ if(line.startsWith("#")) {
+ var trimmed = line;
+ while(trimmed.startsWith("#")) {
+ trimmed = trimmed.substring(1).trim();
+ }
+ T object = asT(trimmed).getValue().orElse(null);
+ return object==null
+ ? new LineComment(wholeLine)
+ : (line.startsWith("REMOVED "))
+ ? new LineRemoved<>(object, trimmed.substring(8))
+ : new LineDisabled<>(object, trimmed);
+ }
+ Try We do this by adding a comment line {@code #MERGED} followed by any lines that are new.
+ *
+ * Any lines already existing are kept as they are,
+ * unless the {@code newerVersion} no longer contains the referenced object of type {@code T},
+ * in which case, the {@link Line} will be commented out (if not already) with a marker
+ * {@code #REMOVED}.
+ *
+ * This requires the merge algorithm to evaluate whether 2 referenced objects are equal,
+ * which it does by checking object keys as given by {@link ListingHandler#keyExtractor}
+ * for equality. We could have done the same by directly checking referenced objects for equality,
+ * but - worst case - that would involve entire objects graphs to be assembled,
+ * while for our use case its convenient to work with simple object facades.
+ */
+ public Each non-comment line can be mapped to a Java class of type {@code T}.
+ *
+ * Typically not used a domain value, as we provide no {@link ValueSemantics},
+ * but rather as a (multi-line) {@link String} support class.
+ *
* @param
+ *
+ *
+ *
Each non-comment line can be mapped to a Java class of type {@code T}. * - *
Typically not used a domain value, as we provide no {@link ValueSemantics}, + *
Typically not used as domain value, as we provide no {@link ValueSemantics},
* but rather as a (multi-line) {@link String} support class.
*
- * @param
- * If a line is not a comment, but can also not be mapped to {@code T},
+ *
+ * If a line is not a comment, but can also not be mapped to {@code T},
* then it is commented out.
*/
public Line parseLine(final String wholeLine) {
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/appfeat/ApplicationFeatureRepositoryDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/appfeat/ApplicationFeatureRepositoryDefault.java
index d286c6f943a..57af31a44e1 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/appfeat/ApplicationFeatureRepositoryDefault.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/appfeat/ApplicationFeatureRepositoryDefault.java
@@ -33,9 +33,12 @@
import jakarta.inject.Inject;
import jakarta.inject.Named;
+import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
+
import org.springframework.stereotype.Service;
+import org.apache.causeway.applib.Identifier;
import org.apache.causeway.applib.annotation.SemanticsOf;
import org.apache.causeway.applib.events.metamodel.MetamodelListener;
import org.apache.causeway.applib.id.LogicalType;
@@ -45,7 +48,6 @@
import org.apache.causeway.applib.services.appfeat.ApplicationFeatureSort;
import org.apache.causeway.applib.services.appfeat.ApplicationMemberSort;
import org.apache.causeway.commons.internal.collections._Maps;
-import org.apache.causeway.commons.internal.exceptions._Exceptions;
import org.apache.causeway.core.config.CausewayConfiguration;
import org.apache.causeway.core.config.metamodel.services.ApplicationFeaturesInitConfiguration;
import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel;
@@ -62,7 +64,6 @@
import org.apache.causeway.core.metamodel.spec.feature.ObjectMember;
import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
-import org.jspecify.annotations.NonNull;
import lombok.extern.slf4j.Slf4j;
/**
@@ -123,9 +124,8 @@ enum InitializationState {
private InitializationState initializationState = InitializationState.NOT_INITIALIZED;
private synchronized void initializeIfRequired() {
- if(initializationState == InitializationState.INITIALIZED) {
+ if(initializationState == InitializationState.INITIALIZED)
return;
- }
initializationState = InitializationState.INITIALIZED;
for (var spec : specificationLoader.snapshotSpecifications()) {
@@ -147,9 +147,8 @@ private void visitFeatureIdentifierByName(
void createApplicationFeaturesFor(final ObjectSpecification spec) {
- if (exclude(spec)) {
+ if (exclude(spec))
return;
- }
final List
- * This value is {@link Comparable}, the implementation of which considers
+ *
+ * This value is {@link Comparable}, the implementation of which considers
* {@link #getSort() (feature) sort}, {@link #getNamespace() namespace},
* {@link #getTypeSimpleName() type simple name} and {@link #getLogicalMemberName() member-logical-name}.
- *
- * If the represented member is an action, then {@link #getLogicalMemberName() member-logical-name}
+ *
+ * If the represented member is an action, then {@link #getLogicalMemberName() member-logical-name}
* must not include any parameter list or parentheses.
* Consequently method overloading is not supported.
- *
- * If there is a member name clash involving an action and an association,
+ *
+ * If there is a member name clash involving an action and an association,
* then consequently any permissions defined automatically apply to both and one cannot separate
* these.
*
@@ -79,6 +79,10 @@ public class ApplicationFeatureId
// -- FACTORY METHODS
+ /**
+ * @apiNote the inverse conversion is available via
+ * {@link ApplicationFeatureRepository#asIdentifier(ApplicationFeatureId)}
+ */
public static ApplicationFeatureId fromIdentifier(final @NonNull Identifier identifier) {
var logicalTypeName = identifier.logicalTypeName();
diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureRepository.java b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureRepository.java
index 7c8525c6554..21eb8551a9c 100644
--- a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureRepository.java
+++ b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureRepository.java
@@ -23,6 +23,8 @@
import java.util.Optional;
import java.util.SortedSet;
+import org.jspecify.annotations.Nullable;
+
import org.apache.causeway.applib.Identifier;
import org.apache.causeway.applib.id.LogicalType;
@@ -55,5 +57,5 @@ public interface ApplicationFeatureRepository {
SortedSet Where columns returned are those that are in principle available. Also those are returned in no particular order.
+ *
+ * If domainType is null returns an empty {@link Stream}.
+ *
+ * Otherwise, if memberIdentifier is null returns Columns for the standalone table of element-type == domainType.
+ *
+ * @since 4.0
+ */
+ Stream Where columns returned are those that are currently visible. Also those are returned in same order as rendered.
+ *
+ * If domainType is null returns an empty {@link Stream}.
+ *
+ * Otherwise, if memberIdentifier is null returns Columns for the standalone table of element-type == domainType.
+ *
+ * @since 4.0
+ */
+ Stream
- * The files are located relative to the class itself. A number of conventions are supported:
+ * The files are located relative to the class itself. A number of conventions are supported:
*
*
- * Any associations omitted from the file will not be shown as columns of the table. The associationId must also
+ * Any associations omitted from the file will not be shown as columns of the table. The associationId must also
* be an exact match, so can be ignored by commenting out, eg with "#".
- *
- * Also note that association that have been explicitly hidden from tables using
- * {@link PropertyLayout#hidden() @PropertyLayout#hidden} or {@link CollectionLayout#hidden()} are never shown,
- * irrespective of whether they are listed in the files. You may therefore prefer to not hide properties
- * with annotations, and then rely solely on these external columnOrder.txt files. This has the further
- * benefit that files can be modified at runtime and will be automatically picked up without requiring a restart
- * of the application.
- * Also note that association that have been explicitly hidden from tables using
+ * {@link PropertyLayout#hidden() @PropertyLayout#hidden} or {@link CollectionLayout#hidden()} are never shown,
+ * irrespective of whether they are listed in the files. You may therefore prefer to not hide properties
+ * with annotations, and then rely solely on these external columnOrder.txt files. This has the further
+ * benefit that files can be modified at runtime and will be automatically picked up without requiring a restart
+ * of the application.
*
* @see TableColumnOrderServiceDefault
*
@@ -102,8 +98,7 @@ public class TableColumnOrderServiceUsingTxtFile implements TableColumnOrderServ
/**
* Reads association Ids of the parented collection from a file.
*
- *
- * The search algorithm is:
+ * The search algorithm is:
*
- * Additional files can be provided by overriding {@link #addResourceNames(Class, String, Class, List)}
- * Additional files can be provided by overriding {@link #addResourceNames(Class, String, Class, List)}
*
* @Returns {@code null}, if no matching resource was found
*/
@@ -135,9 +127,9 @@ public List
- * The default implementation provides only a single file name, ClassName#collectionId.columnOrder.txt.
- * The default implementation provides only a single file name, ClassName#collectionId.columnOrder.txt.
*
* @param domainClass - the class with the parent collection
* @param collectionId - the id of the collection
@@ -175,9 +165,8 @@ private static Optional The search algorithm is:
*
- * Additional files can be provided by overriding {@link #addResourceNames(Class, List)}.
- * Additional files can be provided by overriding {@link #addResourceNames(Class, List)}.
*
* @Returns {@code null}, if no matching resource was found
*/
@@ -212,9 +197,9 @@ public List
- * The default implementation provides only a single file name, ClassName#collectionId.columnOrder.txt.
- * The default implementation provides only a single file name, ClassName#collectionId.columnOrder.txt.
*
* @param domainClass - the class in the standalone collection
* @param addTo - to be added to
@@ -241,15 +224,15 @@ protected void addResourceNames(
}
/**
- * if contents is {@code null} returns {@code null}
+ * if content is {@code null} returns {@code null}
*/
@Nullable
- private static List
*
*
- *
*
- * ParentClassName#collectionId.columnOrder.txt ParentClassName#collectionId.columnOrder.fallback.txt ElementTypeClassName.columnOrder.txtElementTypeClassName.columnOrder.fallback.txt tryLoad(final Class> domainClass, final List
- * The search algorithm is:
+ *
*
- * DomainTypeClassName.columnOrder.txtDomainTypeClassName.columnOrder.fallback.txt
Included are all types in the hierarchy, that are recognized by the Metamodel, recognized Interfaces last.
+ *
+ * @apiNote Since 4.0, the Metamodel supports Interfaces acting as element-types for Collections.
+ * This requires explicit {@link DomainObject} annotations on those Interfaces along with the constraint, that within
+ * a type hierarchy, every type can have at most one super-type.
+ *
+ * @since 4.0
+ */
+ Stream Otherwise, if memberIdentifier is null returns Columns for the standalone table of element-type == domainType.
*
+ * FIXME distinction between PARENTED and STANDALONE could be more explicit (separate methods)
+ * FIXME column-order-txt feature supports identifier wildcards, which could be reflected by leaving the memberId empty here
* @since 4.0
*/
Stream Otherwise, if memberIdentifier is null returns Columns for the standalone table of element-type == domainType.
*
+ * FIXME distinction between PARENTED and STANDALONE could be more explicit (separate methods)
+ * FIXME column-order-txt feature supports identifier wildcards, which could be reflected by leaving the memberId empty here
* @since 4.0
*/
Stream Where columns returned are those that are in principle available. Also those are returned in no particular order.
+ * @since 4.0
+ */
+ public enum AssociationsLookup {
+ AVAILABLE,
+ ENABLED
+ }
+
+ /**
+ * Stream of {@link Identifier} representing the Columns for specified PARENTED collection.
+ *
+ * Columns returned are those that are either in principle AVAILABLE or currently ENABLED, based on given {@link AssociationsLookup}.
*
- * If domainType is null returns an empty {@link Stream}.
+ * The availability lookup returns {@link Identifier}(s) in no particular order,
+ * whereas the enablement lookup returns them in same order as rendered.
*
- * Otherwise, if memberIdentifier is null returns Columns for the standalone table of element-type == domainType.
+ * If parentDomainObject is null returns an empty {@link Stream}.
*
- * FIXME distinction between PARENTED and STANDALONE could be more explicit (separate methods)
- * FIXME column-order-txt feature supports identifier wildcards, which could be reflected by leaving the memberId empty here
* @since 4.0
*/
- Stream Where columns returned are those that are currently visible. Also those are returned in same order as rendered.
+ * Stream of {@link Identifier} representing the Columns for specified STANDALONE collection.
*
- * If domainType is null returns an empty {@link Stream}.
+ * Columns returned are those that are either in principle AVAILABLE or currently ENABLED, based on given {@link AssociationsLookup}.
*
- * Otherwise, if memberIdentifier is null returns Columns for the standalone table of element-type == domainType.
+ * The availability lookup returns {@link Identifier}(s) in no particular order,
+ * whereas the enablement lookup returns them in same order as rendered.
*
- * FIXME distinction between PARENTED and STANDALONE could be more explicit (separate methods)
- * FIXME column-order-txt feature supports identifier wildcards, which could be reflected by leaving the memberId empty here
* @since 4.0
*/
- Stream