You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2020/02/13 12:53:47 UTC

[isis] branch master updated: ISIS-2275: more refactoring (code readability)

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new fdf1a37  ISIS-2275: more refactoring (code readability)
fdf1a37 is described below

commit fdf1a37420389a0cb9e98f1e64a71eb078ec88f1
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Feb 13 12:19:19 2020 +0100

    ISIS-2275: more refactoring (code readability)
---
 .../core/metamodel/layout/LayoutFacetUtil.java     | 340 +++++++++++++++++++++
 .../services/grid/GridSystemServiceAbstract.java   | 296 +-----------------
 .../services/grid/bootstrap3/GridModel.java        | 110 ++++++-
 .../grid/bootstrap3/GridSystemServiceBS3.java      | 246 +++++----------
 4 files changed, 516 insertions(+), 476 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/layout/LayoutFacetUtil.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/layout/LayoutFacetUtil.java
new file mode 100644
index 0000000..4fbbf16
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/layout/LayoutFacetUtil.java
@@ -0,0 +1,340 @@
+/*
+ *  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.isis.core.metamodel.layout;
+
+import java.util.Comparator;
+
+import org.apache.isis.applib.annotation.ActionLayout;
+import org.apache.isis.applib.annotation.BookmarkPolicy;
+import org.apache.isis.applib.annotation.LabelPosition;
+import org.apache.isis.applib.annotation.RenderDay;
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.applib.layout.component.ActionLayoutData;
+import org.apache.isis.applib.layout.component.CollectionLayoutData;
+import org.apache.isis.applib.layout.component.DomainObjectLayoutData;
+import org.apache.isis.applib.layout.component.HasBookmarking;
+import org.apache.isis.applib.layout.component.HasCssClass;
+import org.apache.isis.applib.layout.component.HasCssClassFa;
+import org.apache.isis.applib.layout.component.HasDescribedAs;
+import org.apache.isis.applib.layout.component.HasHidden;
+import org.apache.isis.applib.layout.component.HasNamed;
+import org.apache.isis.applib.layout.component.PropertyLayoutData;
+import org.apache.isis.applib.layout.grid.Grid;
+import org.apache.isis.core.commons.internal.base._Strings;
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.actions.position.ActionPositionFacet;
+import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
+import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
+import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
+import org.apache.isis.core.metamodel.facets.collections.collection.defaultview.DefaultViewFacet;
+import org.apache.isis.core.metamodel.facets.collections.sortedby.SortedByFacet;
+import org.apache.isis.core.metamodel.facets.members.cssclass.CssClassFacet;
+import org.apache.isis.core.metamodel.facets.members.cssclassfa.CssClassFaFacet;
+import org.apache.isis.core.metamodel.facets.object.bookmarkpolicy.BookmarkPolicyFacet;
+import org.apache.isis.core.metamodel.facets.object.paged.PagedFacet;
+import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
+import org.apache.isis.core.metamodel.facets.objectvalue.labelat.LabelAtFacet;
+import org.apache.isis.core.metamodel.facets.objectvalue.multiline.MultiLineFacet;
+import org.apache.isis.core.metamodel.facets.objectvalue.renderedadjusted.RenderedAdjustedFacet;
+import org.apache.isis.core.metamodel.facets.objectvalue.typicallen.TypicalLengthFacet;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+import lombok.experimental.UtilityClass;
+
+/**
+ * 
+ * @since 2.0
+ *
+ */
+@UtilityClass
+public class LayoutFacetUtil {
+
+    public void setBookmarkingIfAny(
+            final HasBookmarking hasBookmarking,
+            final FacetHolder facetHolder) {
+
+        val bookmarkPolicyFacet = facetHolder.getFacet(BookmarkPolicyFacet.class);
+        if(isDoOp(bookmarkPolicyFacet)) {
+            final BookmarkPolicy bookmarking = bookmarkPolicyFacet.value();
+            if(bookmarking != null) {
+                hasBookmarking.setBookmarking(bookmarking);
+            }
+        }
+    }
+
+    public void setCssClassIfAny(
+            final HasCssClass hasCssClass,
+            final FacetHolder facetHolder) {
+
+        val cssClassFacet = facetHolder.getFacet(CssClassFacet.class);
+        if(isDoOp(cssClassFacet)) {
+            try {
+                // try...finally because CSS class may vary by object, and we pass in only null
+                final String cssClass = cssClassFacet.cssClass(null);
+                if(!_Strings.isNullOrEmpty(cssClass)) {
+                    hasCssClass.setCssClass(cssClass);
+                }
+            } catch(Exception ignore) {
+                // ignore
+            }
+        }
+    }
+
+    public void setCssClassFaIfAny(
+            final HasCssClassFa hasCssClassFa,
+            final FacetHolder facetHolder) {
+
+        val cssClassFaFacet = facetHolder.getFacet(CssClassFaFacet.class);
+        if (isDoOp(cssClassFaFacet)) {
+            final String cssClassFa = cssClassFaFacet.value();
+            if(!_Strings.isNullOrEmpty(cssClassFa)) {
+                hasCssClassFa.setCssClassFa(cssClassFa);
+                hasCssClassFa.setCssClassFaPosition(cssClassFaFacet.getPosition());
+            }
+        }
+    }
+
+    public void setDefaultViewIfAny(
+            final CollectionLayoutData collectionLayoutData,
+            final FacetHolder facetHolder) {
+
+        val defaultViewFacet = facetHolder.getFacet(DefaultViewFacet.class);
+        if(isDoOp(defaultViewFacet)) {
+            final String defaultView = defaultViewFacet.value();
+            if(!_Strings.isNullOrEmpty(defaultView)) {
+                collectionLayoutData.setDefaultView(defaultView);
+            }
+        }
+    }
+
+    public void setDescribedAsIfAny(
+            final HasDescribedAs hasDescribedAs,
+            final FacetHolder facetHolder) {
+
+        val describedAsFacet = facetHolder.getFacet(DescribedAsFacet.class);
+        if(isDoOp(describedAsFacet)) {
+            final String describedAs = describedAsFacet.value();
+            if(!_Strings.isNullOrEmpty(describedAs)) {
+                hasDescribedAs.setDescribedAs(describedAs);
+            }
+        }
+    }
+
+    public void setHiddenIfAny(
+            final HasHidden hasHidden,
+            final FacetHolder facetHolder) {
+
+        val hiddenFacet = facetHolder.getFacet(HiddenFacet.class);
+        if (isDoOp(hiddenFacet)) {
+            final Where where = hiddenFacet.where();
+            if(where != null) {
+                hasHidden.setHidden(where);
+            }
+        }
+    }
+
+    public void setLabelPositionIfAny(
+            final PropertyLayoutData propertyLayoutData,
+            final FacetHolder facetHolder) {
+
+        val labelAtFacet = facetHolder.getFacet(LabelAtFacet.class);
+        if(isDoOp(labelAtFacet)) {
+            final LabelPosition labelPosition = labelAtFacet.label();
+            if(labelPosition != null) {
+                propertyLayoutData.setLabelPosition(labelPosition);
+            }
+        }
+    }
+
+    public void setMultiLineIfAny(
+            final PropertyLayoutData propertyLayoutData,
+            final FacetHolder facetHolder) {
+
+        val multiLineFacet = facetHolder.getFacet(MultiLineFacet.class);
+        if(isDoOp(multiLineFacet)) {
+            final int numberOfLines = multiLineFacet.numberOfLines();
+            if(numberOfLines > 0) {
+                propertyLayoutData.setMultiLine(numberOfLines);
+            }
+        }
+    }
+
+    public void setNamedIfAny(
+            final HasNamed hasNamed,
+            final FacetHolder facetHolder) {
+
+        val namedFacet = facetHolder.getFacet(NamedFacet.class);
+        if(isDoOp(namedFacet)) {
+            final String named = namedFacet.value();
+            if(!_Strings.isNullOrEmpty(named)){
+                hasNamed.setNamed(named);
+            }
+            final boolean escaped = namedFacet.escaped();
+            if(!escaped) {
+                hasNamed.setNamedEscaped(escaped);
+            }
+        }
+    }
+
+    public void setPagedIfAny(
+            final CollectionLayoutData collectionLayoutData,
+            final FacetHolder facetHolder) {
+
+        val pagedFacet = facetHolder.getFacet(PagedFacet.class);
+        if(isDoOp(pagedFacet)) {
+            final int value = pagedFacet.value();
+            if(value > 0) {
+                collectionLayoutData.setPaged(value);
+            }
+        }
+    }
+
+    public void setPluralIfAny(
+            final DomainObjectLayoutData domainObjectLayoutData,
+            final FacetHolder facetHolder) {
+
+        val pluralFacet = facetHolder.getFacet(PluralFacet.class);
+        if(isDoOp(pluralFacet)) {
+            final String plural = pluralFacet.value();
+            if(!_Strings.isNullOrEmpty(plural)) {
+                domainObjectLayoutData.setPlural(plural);
+            }
+        }
+    }
+
+
+    public void setActionPositionIfAny(
+            final ActionLayoutData actionLayoutData,
+            final FacetHolder facetHolder) {
+
+        val actionPositionFacet = facetHolder.getFacet(ActionPositionFacet.class);
+        if(isDoOp(actionPositionFacet)) {
+            final ActionLayout.Position position = actionPositionFacet.position();
+            if(position != null) {
+                actionLayoutData.setPosition(position);
+            }
+        }
+    }
+
+    public void setRenderedAsDayBeforeIfAny(
+            final PropertyLayoutData propertyLayoutData,
+            final FacetHolder facetHolder) {
+
+        val renderedAdjustedFacet = facetHolder.getFacet(RenderedAdjustedFacet.class);
+        if(isDoOp(renderedAdjustedFacet)) {
+            final int adjusted = renderedAdjustedFacet.value();
+            propertyLayoutData.setRenderDay(adjusted != 0 ? RenderDay.AS_DAY_BEFORE : RenderDay.AS_DAY);
+        }
+    }
+
+    public void setSortedByIfAny(
+            final CollectionLayoutData collectionLayoutData,
+            final FacetHolder facetHolder) {
+
+        val sortedByFacet = facetHolder.getFacet(SortedByFacet.class);
+        if(isDoOp(sortedByFacet)) {
+            final Class<? extends Comparator<?>> className = sortedByFacet.value();
+            if(className != null) {
+                collectionLayoutData.setSortedBy(className.getCanonicalName());
+            }
+        }
+    }
+
+    public void setTypicalLengthIfAny(
+            final PropertyLayoutData propertyLayoutData,
+            final FacetHolder facetHolder) {
+
+        val typicalLengthFacet = facetHolder.getFacet(TypicalLengthFacet.class);
+        if(isDoOp(typicalLengthFacet)) {
+            final int typicalLength = typicalLengthFacet.value();
+            if(typicalLength > 0) {
+                propertyLayoutData.setTypicalLength(typicalLength);
+            }
+        }
+    }
+
+    @RequiredArgsConstructor(staticName = "of")
+    public static class MetamodelToGridOverridingVisistor extends Grid.VisitorAdapter  {
+
+        private final ObjectSpecification objectSpec; 
+
+        @Override
+        public void visit(final ActionLayoutData actionLayoutData) {
+            objectSpec.getObjectAction(actionLayoutData.getId())
+            .ifPresent(objectAction->{
+                setBookmarkingIfAny(actionLayoutData, objectAction);
+                setCssClassIfAny(actionLayoutData, objectAction);
+                setCssClassFaIfAny(actionLayoutData, objectAction);
+                setDescribedAsIfAny(actionLayoutData, objectAction);
+                setHiddenIfAny(actionLayoutData, objectAction);
+                setNamedIfAny(actionLayoutData, objectAction);
+                setActionPositionIfAny(actionLayoutData, objectAction);    
+            });
+        }
+
+        @Override
+        public void visit(final CollectionLayoutData collectionLayoutData) {
+            objectSpec.getAssociation(collectionLayoutData.getId())
+            .ifPresent(collection->{
+                setCssClassIfAny(collectionLayoutData, collection);
+                setDefaultViewIfAny(collectionLayoutData, collection);
+                setDescribedAsIfAny(collectionLayoutData, collection);
+                setHiddenIfAny(collectionLayoutData, collection);
+                setNamedIfAny(collectionLayoutData, collection);
+                setPagedIfAny(collectionLayoutData, collection);
+                setSortedByIfAny(collectionLayoutData, collection);    
+            });
+        }
+
+        @Override
+        public void visit(final PropertyLayoutData propertyLayoutData) {
+            objectSpec.getAssociation(propertyLayoutData.getId())
+            .ifPresent(property->{
+                setCssClassIfAny(propertyLayoutData, property);
+                setDescribedAsIfAny(propertyLayoutData, property);
+                setHiddenIfAny(propertyLayoutData, property);
+                setNamedIfAny(propertyLayoutData, property);
+                setLabelPositionIfAny(propertyLayoutData, property);
+                setMultiLineIfAny(propertyLayoutData, property);
+                setRenderedAsDayBeforeIfAny(propertyLayoutData, property);
+                setTypicalLengthIfAny(propertyLayoutData, property);    
+            });
+        }
+
+        @Override
+        public void visit(final DomainObjectLayoutData domainObjectLayoutData) {
+            setBookmarkingIfAny(domainObjectLayoutData, objectSpec);
+            setCssClassIfAny(domainObjectLayoutData, objectSpec);
+            setCssClassFaIfAny(domainObjectLayoutData, objectSpec);
+            setDescribedAsIfAny(domainObjectLayoutData, objectSpec);
+            setNamedIfAny(domainObjectLayoutData, objectSpec);
+            setPluralIfAny(domainObjectLayoutData, objectSpec);
+        }
+    }
+
+    // -- HELPER
+
+    private static boolean isDoOp(final Facet facet) {
+        return facet != null && !facet.isFallback();
+    }
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridSystemServiceAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridSystemServiceAbstract.java
index 7cfb931..c1b165c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridSystemServiceAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridSystemServiceAbstract.java
@@ -18,7 +18,6 @@
  */
 package org.apache.isis.core.metamodel.services.grid;
 
-import java.util.Comparator;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
@@ -27,23 +26,12 @@ import java.util.stream.Stream;
 
 import javax.inject.Inject;
 
-import org.apache.isis.applib.annotation.ActionLayout;
-import org.apache.isis.applib.annotation.BookmarkPolicy;
-import org.apache.isis.applib.annotation.LabelPosition;
 import org.apache.isis.applib.annotation.Programmatic;
-import org.apache.isis.applib.annotation.RenderDay;
-import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.layout.component.ActionLayoutData;
 import org.apache.isis.applib.layout.component.CollectionLayoutData;
 import org.apache.isis.applib.layout.component.DomainObjectLayoutData;
 import org.apache.isis.applib.layout.component.DomainObjectLayoutDataOwner;
 import org.apache.isis.applib.layout.component.FieldSet;
-import org.apache.isis.applib.layout.component.HasBookmarking;
-import org.apache.isis.applib.layout.component.HasCssClass;
-import org.apache.isis.applib.layout.component.HasCssClassFa;
-import org.apache.isis.applib.layout.component.HasDescribedAs;
-import org.apache.isis.applib.layout.component.HasHidden;
-import org.apache.isis.applib.layout.component.HasNamed;
 import org.apache.isis.applib.layout.component.PropertyLayoutData;
 import org.apache.isis.applib.layout.grid.Grid;
 import org.apache.isis.applib.services.grid.GridSystemService;
@@ -51,11 +39,9 @@ import org.apache.isis.applib.services.i18n.TranslationService;
 import org.apache.isis.applib.services.jaxb.JaxbService;
 import org.apache.isis.applib.services.message.MessageService;
 import org.apache.isis.core.commons.internal.base._Casts;
-import org.apache.isis.core.commons.internal.base._Strings;
 import org.apache.isis.core.commons.internal.collections._Sets;
 import org.apache.isis.core.commons.internal.environment.IsisSystemEnvironment;
 import org.apache.isis.core.metamodel.facetapi.Facet;
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FacetUtil;
 import org.apache.isis.core.metamodel.facets.actions.layout.ActionPositionFacetForActionXml;
 import org.apache.isis.core.metamodel.facets.actions.layout.BookmarkPolicyFacetForActionXml;
@@ -66,11 +52,6 @@ import org.apache.isis.core.metamodel.facets.actions.layout.HiddenFacetForAction
 import org.apache.isis.core.metamodel.facets.actions.layout.NamedFacetForActionXml;
 import org.apache.isis.core.metamodel.facets.actions.layout.PromptStyleFacetForActionXml;
 import org.apache.isis.core.metamodel.facets.actions.layout.RedirectFacetFromActionXml;
-import org.apache.isis.core.metamodel.facets.actions.position.ActionPositionFacet;
-import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
-import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
-import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
-import org.apache.isis.core.metamodel.facets.collections.collection.defaultview.DefaultViewFacet;
 import org.apache.isis.core.metamodel.facets.collections.layout.CssClassFacetForCollectionXml;
 import org.apache.isis.core.metamodel.facets.collections.layout.DefaultViewFacetForCollectionXml;
 import org.apache.isis.core.metamodel.facets.collections.layout.DescribedAsFacetForCollectionXml;
@@ -78,23 +59,13 @@ import org.apache.isis.core.metamodel.facets.collections.layout.HiddenFacetForCo
 import org.apache.isis.core.metamodel.facets.collections.layout.NamedFacetForCollectionXml;
 import org.apache.isis.core.metamodel.facets.collections.layout.PagedFacetForCollectionXml;
 import org.apache.isis.core.metamodel.facets.collections.layout.SortedByFacetForCollectionXml;
-import org.apache.isis.core.metamodel.facets.collections.sortedby.SortedByFacet;
-import org.apache.isis.core.metamodel.facets.members.cssclass.CssClassFacet;
-import org.apache.isis.core.metamodel.facets.members.cssclassfa.CssClassFaFacet;
 import org.apache.isis.core.metamodel.facets.members.order.annotprop.MemberOrderFacetXml;
-import org.apache.isis.core.metamodel.facets.object.bookmarkpolicy.BookmarkPolicyFacet;
 import org.apache.isis.core.metamodel.facets.object.domainobjectlayout.BookmarkPolicyFacetForDomainObjectXml;
 import org.apache.isis.core.metamodel.facets.object.domainobjectlayout.CssClassFaFacetForDomainObjectXml;
 import org.apache.isis.core.metamodel.facets.object.domainobjectlayout.CssClassFacetForDomainObjectXml;
 import org.apache.isis.core.metamodel.facets.object.domainobjectlayout.DescribedAsFacetForDomainObjectXml;
 import org.apache.isis.core.metamodel.facets.object.domainobjectlayout.NamedFacetForDomainObjectXml;
 import org.apache.isis.core.metamodel.facets.object.domainobjectlayout.PluralFacetForDomainObjectXml;
-import org.apache.isis.core.metamodel.facets.object.paged.PagedFacet;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
-import org.apache.isis.core.metamodel.facets.objectvalue.labelat.LabelAtFacet;
-import org.apache.isis.core.metamodel.facets.objectvalue.multiline.MultiLineFacet;
-import org.apache.isis.core.metamodel.facets.objectvalue.renderedadjusted.RenderedAdjustedFacet;
-import org.apache.isis.core.metamodel.facets.objectvalue.typicallen.TypicalLengthFacet;
 import org.apache.isis.core.metamodel.facets.properties.propertylayout.CssClassFacetForPropertyXml;
 import org.apache.isis.core.metamodel.facets.properties.propertylayout.DescribedAsFacetForPropertyXml;
 import org.apache.isis.core.metamodel.facets.properties.propertylayout.HiddenFacetForPropertyXml;
@@ -105,6 +76,7 @@ import org.apache.isis.core.metamodel.facets.properties.propertylayout.PromptSty
 import org.apache.isis.core.metamodel.facets.properties.propertylayout.RenderedAdjustedFacetForPropertyXml;
 import org.apache.isis.core.metamodel.facets.properties.propertylayout.TypicalLengthFacetForPropertyXml;
 import org.apache.isis.core.metamodel.facets.properties.propertylayout.UnchangingFacetForPropertyXml;
+import org.apache.isis.core.metamodel.layout.LayoutFacetUtil.MetamodelToGridOverridingVisistor;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.Contributed;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
@@ -387,272 +359,8 @@ implements GridSystemService<G> {
     @Override
     public void complete(final G grid, final Class<?> domainClass) {
         normalize(grid, domainClass);
-
         val objectSpec = specificationLoader.loadSpecification(domainClass);
-
-        grid.visit(new Grid.VisitorAdapter() {
-
-            @Override
-            public void visit(final ActionLayoutData actionLayoutData) {
-                objectSpec.getObjectAction(actionLayoutData.getId())
-                .ifPresent(objectAction->{
-                    setBookmarkingIfAny(actionLayoutData, objectAction);
-                    setCssClassIfAny(actionLayoutData, objectAction);
-                    setCssClassFaIfAny(actionLayoutData, objectAction);
-                    setDescribedAsIfAny(actionLayoutData, objectAction);
-                    setHiddenIfAny(actionLayoutData, objectAction);
-                    setNamedIfAny(actionLayoutData, objectAction);
-                    setActionPositionIfAny(actionLayoutData, objectAction);    
-                });
-            }
-
-            @Override
-            public void visit(final CollectionLayoutData collectionLayoutData) {
-                objectSpec.getAssociation(collectionLayoutData.getId())
-                .ifPresent(collection->{
-                    setCssClassIfAny(collectionLayoutData, collection);
-                    setDefaultViewIfAny(collectionLayoutData, collection);
-                    setDescribedAsIfAny(collectionLayoutData, collection);
-                    setHiddenIfAny(collectionLayoutData, collection);
-                    setNamedIfAny(collectionLayoutData, collection);
-                    setPagedIfAny(collectionLayoutData, collection);
-                    setSortedByIfAny(collectionLayoutData, collection);    
-                });
-            }
-
-            @Override
-            public void visit(final PropertyLayoutData propertyLayoutData) {
-                objectSpec.getAssociation(propertyLayoutData.getId())
-                .ifPresent(property->{
-                    setCssClassIfAny(propertyLayoutData, property);
-                    setDescribedAsIfAny(propertyLayoutData, property);
-                    setHiddenIfAny(propertyLayoutData, property);
-                    setNamedIfAny(propertyLayoutData, property);
-                    setLabelPositionIfAny(propertyLayoutData, property);
-                    setMultiLineIfAny(propertyLayoutData, property);
-                    setRenderedAsDayBeforeIfAny(propertyLayoutData, property);
-                    setTypicalLengthIfAny(propertyLayoutData, property);    
-                });
-            }
-
-            @Override
-            public void visit(final DomainObjectLayoutData domainObjectLayoutData) {
-                setBookmarkingIfAny(domainObjectLayoutData, objectSpec);
-                setCssClassIfAny(domainObjectLayoutData, objectSpec);
-                setCssClassFaIfAny(domainObjectLayoutData, objectSpec);
-                setDescribedAsIfAny(domainObjectLayoutData, objectSpec);
-                setNamedIfAny(domainObjectLayoutData, objectSpec);
-                setPluralIfAny(domainObjectLayoutData, objectSpec);
-            }
-        });
-    }
-
-    private static boolean isDoOp(final Facet facet) {
-        return facet != null && !facet.isFallback();
-    }
-
-    protected void setBookmarkingIfAny(
-            final HasBookmarking hasBookmarking,
-            final FacetHolder facetHolder) {
-        
-        val bookmarkPolicyFacet = facetHolder.getFacet(BookmarkPolicyFacet.class);
-        if(isDoOp(bookmarkPolicyFacet)) {
-            final BookmarkPolicy bookmarking = bookmarkPolicyFacet.value();
-            if(bookmarking != null) {
-                hasBookmarking.setBookmarking(bookmarking);
-            }
-        }
-    }
-
-    protected void setCssClassIfAny(
-            final HasCssClass hasCssClass,
-            final FacetHolder facetHolder) {
-        
-        val cssClassFacet = facetHolder.getFacet(CssClassFacet.class);
-        if(isDoOp(cssClassFacet)) {
-            try {
-                // try...finally because CSS class may vary by object, and we pass in only null
-                final String cssClass = cssClassFacet.cssClass(null);
-                if(!_Strings.isNullOrEmpty(cssClass)) {
-                    hasCssClass.setCssClass(cssClass);
-                }
-            } catch(Exception ignore) {
-                // ignore
-            }
-        }
-    }
-
-    protected void setCssClassFaIfAny(
-            final HasCssClassFa hasCssClassFa,
-            final FacetHolder facetHolder) {
-        
-        val cssClassFaFacet = facetHolder.getFacet(CssClassFaFacet.class);
-        if (isDoOp(cssClassFaFacet)) {
-            final String cssClassFa = cssClassFaFacet.value();
-            if(!_Strings.isNullOrEmpty(cssClassFa)) {
-                hasCssClassFa.setCssClassFa(cssClassFa);
-                hasCssClassFa.setCssClassFaPosition(cssClassFaFacet.getPosition());
-            }
-        }
-    }
-
-    protected void setDefaultViewIfAny(
-            final CollectionLayoutData collectionLayoutData,
-            final FacetHolder facetHolder) {
-        
-        val defaultViewFacet = facetHolder.getFacet(DefaultViewFacet.class);
-        if(isDoOp(defaultViewFacet)) {
-            final String defaultView = defaultViewFacet.value();
-            if(!_Strings.isNullOrEmpty(defaultView)) {
-                collectionLayoutData.setDefaultView(defaultView);
-            }
-        }
-    }
-
-    protected void setDescribedAsIfAny(
-            final HasDescribedAs hasDescribedAs,
-            final FacetHolder facetHolder) {
-        
-        val describedAsFacet = facetHolder.getFacet(DescribedAsFacet.class);
-        if(isDoOp(describedAsFacet)) {
-            final String describedAs = describedAsFacet.value();
-            if(!_Strings.isNullOrEmpty(describedAs)) {
-                hasDescribedAs.setDescribedAs(describedAs);
-            }
-        }
-    }
-
-    protected void setHiddenIfAny(
-            final HasHidden hasHidden,
-            final FacetHolder facetHolder) {
-        
-        val hiddenFacet = facetHolder.getFacet(HiddenFacet.class);
-        if (isDoOp(hiddenFacet)) {
-            final Where where = hiddenFacet.where();
-            if(where != null) {
-                hasHidden.setHidden(where);
-            }
-        }
-    }
-
-    protected void setLabelPositionIfAny(
-            final PropertyLayoutData propertyLayoutData,
-            final FacetHolder facetHolder) {
-        
-        val labelAtFacet = facetHolder.getFacet(LabelAtFacet.class);
-        if(isDoOp(labelAtFacet)) {
-            final LabelPosition labelPosition = labelAtFacet.label();
-            if(labelPosition != null) {
-                propertyLayoutData.setLabelPosition(labelPosition);
-            }
-        }
-    }
-
-    protected void setMultiLineIfAny(
-            final PropertyLayoutData propertyLayoutData,
-            final FacetHolder facetHolder) {
-        
-        val multiLineFacet = facetHolder.getFacet(MultiLineFacet.class);
-        if(isDoOp(multiLineFacet)) {
-            final int numberOfLines = multiLineFacet.numberOfLines();
-            if(numberOfLines > 0) {
-                propertyLayoutData.setMultiLine(numberOfLines);
-            }
-        }
-    }
-
-    protected void setNamedIfAny(
-            final HasNamed hasNamed,
-            final FacetHolder facetHolder) {
-        
-        val namedFacet = facetHolder.getFacet(NamedFacet.class);
-        if(isDoOp(namedFacet)) {
-            final String named = namedFacet.value();
-            if(!_Strings.isNullOrEmpty(named)){
-                hasNamed.setNamed(named);
-            }
-            final boolean escaped = namedFacet.escaped();
-            if(!escaped) {
-                hasNamed.setNamedEscaped(escaped);
-            }
-        }
-    }
-
-    protected void setPagedIfAny(
-            final CollectionLayoutData collectionLayoutData,
-            final FacetHolder facetHolder) {
-        
-        val pagedFacet = facetHolder.getFacet(PagedFacet.class);
-        if(isDoOp(pagedFacet)) {
-            final int value = pagedFacet.value();
-            if(value > 0) {
-                collectionLayoutData.setPaged(value);
-            }
-        }
-    }
-
-    protected void setPluralIfAny(
-            final DomainObjectLayoutData domainObjectLayoutData,
-            final FacetHolder facetHolder) {
-        
-        val pluralFacet = facetHolder.getFacet(PluralFacet.class);
-        if(isDoOp(pluralFacet)) {
-            final String plural = pluralFacet.value();
-            if(!_Strings.isNullOrEmpty(plural)) {
-                domainObjectLayoutData.setPlural(plural);
-            }
-        }
-    }
-
-
-    protected void setActionPositionIfAny(
-            final ActionLayoutData actionLayoutData,
-            final FacetHolder facetHolder) {
-        
-        val actionPositionFacet = facetHolder.getFacet(ActionPositionFacet.class);
-        if(isDoOp(actionPositionFacet)) {
-            final ActionLayout.Position position = actionPositionFacet.position();
-            if(position != null) {
-                actionLayoutData.setPosition(position);
-            }
-        }
-    }
-
-    protected void setRenderedAsDayBeforeIfAny(
-            final PropertyLayoutData propertyLayoutData,
-            final FacetHolder facetHolder) {
-        
-        val renderedAdjustedFacet = facetHolder.getFacet(RenderedAdjustedFacet.class);
-        if(isDoOp(renderedAdjustedFacet)) {
-            final int adjusted = renderedAdjustedFacet.value();
-            propertyLayoutData.setRenderDay(adjusted != 0 ? RenderDay.AS_DAY_BEFORE : RenderDay.AS_DAY);
-        }
-    }
-
-    protected void setSortedByIfAny(
-            final CollectionLayoutData collectionLayoutData,
-            final FacetHolder facetHolder) {
-        
-        val sortedByFacet = facetHolder.getFacet(SortedByFacet.class);
-        if(isDoOp(sortedByFacet)) {
-            final Class<? extends Comparator<?>> className = sortedByFacet.value();
-            if(className != null) {
-                collectionLayoutData.setSortedBy(className.getCanonicalName());
-            }
-        }
-    }
-
-    protected void setTypicalLengthIfAny(
-            final PropertyLayoutData propertyLayoutData,
-            final FacetHolder facetHolder) {
-        
-        val typicalLengthFacet = facetHolder.getFacet(TypicalLengthFacet.class);
-        if(isDoOp(typicalLengthFacet)) {
-            final int typicalLength = typicalLengthFacet.value();
-            if(typicalLength > 0) {
-                propertyLayoutData.setTypicalLength(typicalLength);
-            }
-        }
+        grid.visit(MetamodelToGridOverridingVisistor.of(objectSpec));
     }
 
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/bootstrap3/GridModel.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/bootstrap3/GridModel.java
index e68efd9..fa2571b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/bootstrap3/GridModel.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/bootstrap3/GridModel.java
@@ -21,11 +21,13 @@ package org.apache.isis.core.metamodel.services.grid.bootstrap3;
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
+import java.util.Optional;
 
 import org.apache.isis.applib.layout.component.FieldSet;
 import org.apache.isis.applib.layout.grid.bootstrap3.BS3Col;
 import org.apache.isis.applib.layout.grid.bootstrap3.BS3Grid;
 import org.apache.isis.applib.layout.grid.bootstrap3.BS3Row;
+import org.apache.isis.applib.layout.grid.bootstrap3.BS3TabGroup;
 import org.apache.isis.core.commons.internal.collections._Maps;
 import org.apache.isis.core.commons.internal.collections._Sets;
 
@@ -45,18 +47,18 @@ final class GridModel {
         private final LinkedHashMap<String, BS3Col> cols = _Maps.newLinkedHashMap();
         private final LinkedHashMap<String, FieldSet> fieldSets = _Maps.newLinkedHashMap();
         
-        @Getter private boolean duplicateIdDetected = false;
+        @Getter private BS3Col colForUnreferencedActionsRef;
+        @Getter private BS3Col colForUnreferencedCollectionsRef;
+        @Getter private FieldSet fieldSetForUnreferencedActionsRef;
+        @Getter private FieldSet fieldSetForUnreferencedPropertiesRef;
+        @Getter private BS3TabGroup tabGroupForUnreferencedCollectionsRef;
+        
+        private boolean duplicateIdDetected = false;
         
         public boolean contains(String id) {
             return allIds.contains(id);
         }
         
-//        public Collection<BS3Row> rows() {
-//            return rows.values();
-//        }
-//        public Collection<BS3Col> cols() {
-//            return cols.values();
-//        }
         public Collection<FieldSet> fieldSets() {
             return fieldSets.values();
         }
@@ -67,7 +69,15 @@ final class GridModel {
             return fieldSets.get(id);
         }
         
-        public static GridModel createFrom(BS3Grid bs3Grid) {
+        /**
+         * find all row and col ids<br>
+         * - ensure that all Ids are different<br>
+         * - ensure that there is exactly one col with the 
+         * unreferencedActions, unreferencedProperties and unreferencedCollections attribute set.
+         * @param bs3Grid
+         * @return empty if not valid
+         */
+        public static Optional<GridModel> createFrom(BS3Grid bs3Grid) {
             
             val gridModel = new GridModel();
 
@@ -115,8 +125,87 @@ final class GridModel {
                     gridModel.putFieldSet(id, fieldSet);
                 }
             });
+            
+            if(gridModel.duplicateIdDetected) {
+                return Optional.empty();
+            }
+            
+            bs3Grid.visit(new BS3Grid.VisitorAdapter(){
+
+                @Override
+                public void visit(final BS3Col bs3Col) {
+                    if(isSet(bs3Col.isUnreferencedActions())) {
+                        if(gridModel.colForUnreferencedActionsRef != null) {
+                            bs3Col.setMetadataError("More than one col with 'unreferencedActions' attribute set");
+                        } else if(gridModel.fieldSetForUnreferencedActionsRef != null) {
+                            bs3Col.setMetadataError("Already found a fieldset with 'unreferencedActions' attribute set");
+                        } else {
+                            gridModel.colForUnreferencedActionsRef=bs3Col;
+                        }
+                    }
+                    if(isSet(bs3Col.isUnreferencedCollections())) {
+                        if(gridModel.colForUnreferencedCollectionsRef != null) {
+                            bs3Col.setMetadataError("More than one col with 'unreferencedCollections' attribute set");
+                        } else if(gridModel.tabGroupForUnreferencedCollectionsRef != null) {
+                            bs3Col.setMetadataError("Already found a tabgroup with 'unreferencedCollections' attribute set");
+                        } else {
+                            gridModel.colForUnreferencedCollectionsRef = bs3Col;
+                        }
+                    }
+                }
+
+                @Override
+                public void visit(final FieldSet fieldSet) {
+                    if(isSet(fieldSet.isUnreferencedActions())) {
+                        if(gridModel.fieldSetForUnreferencedActionsRef != null) {
+                            fieldSet.setMetadataError("More than one fieldset with 'unreferencedActions' attribute set");
+                        } else if(gridModel.colForUnreferencedActionsRef != null) {
+                            fieldSet.setMetadataError("Already found a column with 'unreferencedActions' attribute set");
+                        } else {
+                            gridModel.fieldSetForUnreferencedActionsRef = fieldSet;
+                        }
+                    }
+                    if(isSet(fieldSet.isUnreferencedProperties())) {
+                        if(gridModel.fieldSetForUnreferencedPropertiesRef != null) {
+                            fieldSet.setMetadataError("More than one column with 'unreferencedProperties' attribute set");
+                        } else {
+                            gridModel.fieldSetForUnreferencedPropertiesRef = fieldSet;
+                        }
+                    }
+                }
+
+                @Override
+                public void visit(final BS3TabGroup bs3TabGroup) {
+                    if(isSet(bs3TabGroup.isUnreferencedCollections())) {
+                        if(gridModel.tabGroupForUnreferencedCollectionsRef != null) {
+                            bs3TabGroup.setMetadataError("More than one tabgroup with 'unreferencedCollections' attribute set");
+                        } else if(gridModel.colForUnreferencedCollectionsRef != null) {
+                            bs3TabGroup.setMetadataError("Already found a column with 'unreferencedCollections' attribute set");
+                        } else {
+                            gridModel.tabGroupForUnreferencedCollectionsRef = bs3TabGroup;
+                        }
+                    }
+                }
+            });
 
-            return gridModel;
+            if(gridModel.colForUnreferencedActionsRef == null && gridModel.fieldSetForUnreferencedActionsRef == null) {
+                bs3Grid.getMetadataErrors().add("No column and also no fieldset found with the 'unreferencedActions' attribute set");
+            }
+            if(gridModel.fieldSetForUnreferencedPropertiesRef == null) {
+                bs3Grid.getMetadataErrors().add("No fieldset found with the 'unreferencedProperties' attribute set");
+            }
+            if(gridModel.colForUnreferencedCollectionsRef == null && gridModel.tabGroupForUnreferencedCollectionsRef == null) {
+                bs3Grid.getMetadataErrors().add("No column and also no tabgroup found with the 'unreferencedCollections' attribute set");
+            }
+            
+            final boolean hasErrors = 
+                    gridModel.colForUnreferencedActionsRef == null 
+                    && gridModel.fieldSetForUnreferencedActionsRef == null 
+                    || gridModel.fieldSetForUnreferencedPropertiesRef == null 
+                    || gridModel.colForUnreferencedCollectionsRef == null 
+                    && gridModel.tabGroupForUnreferencedCollectionsRef == null;
+
+            return hasErrors ? Optional.empty() : Optional.of(gridModel);
             
         }
         
@@ -132,5 +221,8 @@ final class GridModel {
             fieldSets.put(id, fieldSet);
             allIds.add(id);            
         }
+        private static Boolean isSet(final Boolean flag) {
+            return flag != null && flag;
+        }
         
     }
\ No newline at end of file
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/bootstrap3/GridSystemServiceBS3.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/bootstrap3/GridSystemServiceBS3.java
index 5867737..e652033 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/bootstrap3/GridSystemServiceBS3.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/bootstrap3/GridSystemServiceBS3.java
@@ -19,11 +19,11 @@
 package org.apache.isis.core.metamodel.services.grid.bootstrap3;
 
 import java.util.Arrays;
-import java.util.LinkedHashMap;
+import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 
 import javax.inject.Inject;
@@ -166,127 +166,32 @@ public class GridSystemServiceBS3 extends GridSystemServiceAbstract<BS3Grid> {
         }
     }
 
-
-    private static final class GridVisitorResult {
-        BS3Col colForUnreferencedActionsRef;
-        BS3Col colForUnreferencedCollectionsRef;
-        FieldSet fieldSetForUnreferencedActionsRef;
-        FieldSet fieldSetForUnreferencedPropertiesRef;
-        BS3TabGroup tabGroupForUnreferencedCollectionsRef;    
-    }
-
-    /**
-     * Mandatory hook method defined in {@link GridSystemServiceAbstract superclass}, called by {@link GridSystemServiceAbstract#normalize(Grid, Class)} }.
-     */
     @Override
     protected boolean validateAndNormalize(
             final Grid grid,
             final Class<?> domainClass) {
 
+        val bs3Grid = (BS3Grid) grid;
+        val objectSpec = specificationLoader.loadSpecification(domainClass);
 
-        final ObjectSpecification objectSpec = specificationLoader.loadSpecification(domainClass);
-
-        final Map<String, OneToOneAssociation> oneToOneAssociationById =
-                ObjectMember.mapById(getOneToOneAssociations(objectSpec));
-        final Map<String, OneToManyAssociation> oneToManyAssociationById =
-                ObjectMember.mapById(getOneToManyAssociations(objectSpec));
-        final Map<String, ObjectAction> objectActionById =
-                ObjectMember.mapById(
-                        objectSpec.streamObjectActions(Contributed.INCLUDED));
-
-        final BS3Grid bs3Grid = (BS3Grid) grid;
-
-        final LinkedHashMap<String, PropertyLayoutData> propertyLayoutDataById = bs3Grid.getAllPropertiesById();
-        final LinkedHashMap<String, CollectionLayoutData> collectionLayoutDataById = bs3Grid.getAllCollectionsById();
-        final LinkedHashMap<String, ActionLayoutData> actionLayoutDataById = bs3Grid.getAllActionsById();
-
-        // find all row and col ids
-        // ensure that all Ids are different
-        val gridModel = GridModel.createFrom(bs3Grid); 
-        if(gridModel.isDuplicateIdDetected()) {
-            return false;
-        }
-
-        // ensure that there is exactly one col with the
-        // unreferencedActions, unreferencedProperties and unreferencedCollections attribute set.
-
-        final GridVisitorResult result = new GridVisitorResult();
-
-        bs3Grid.visit(new BS3Grid.VisitorAdapter(){
-
-            @Override
-            public void visit(final BS3Col bs3Col) {
-                if(isSet(bs3Col.isUnreferencedActions())) {
-                    if(result.colForUnreferencedActionsRef != null) {
-                        bs3Col.setMetadataError("More than one col with 'unreferencedActions' attribute set");
-                    } else if(result.fieldSetForUnreferencedActionsRef != null) {
-                        bs3Col.setMetadataError("Already found a fieldset with 'unreferencedActions' attribute set");
-                    } else {
-                        result.colForUnreferencedActionsRef=bs3Col;
-                    }
-                }
-                if(isSet(bs3Col.isUnreferencedCollections())) {
-                    if(result.colForUnreferencedCollectionsRef != null) {
-                        bs3Col.setMetadataError("More than one col with 'unreferencedCollections' attribute set");
-                    } else if(result.tabGroupForUnreferencedCollectionsRef != null) {
-                        bs3Col.setMetadataError("Already found a tabgroup with 'unreferencedCollections' attribute set");
-                    } else {
-                        result.colForUnreferencedCollectionsRef = bs3Col;
-                    }
-                }
-            }
-
-            @Override
-            public void visit(final FieldSet fieldSet) {
-                if(isSet(fieldSet.isUnreferencedActions())) {
-                    if(result.fieldSetForUnreferencedActionsRef != null) {
-                        fieldSet.setMetadataError("More than one fieldset with 'unreferencedActions' attribute set");
-                    } else if(result.colForUnreferencedActionsRef != null) {
-                        fieldSet.setMetadataError("Already found a column with 'unreferencedActions' attribute set");
-                    } else {
-                        result.fieldSetForUnreferencedActionsRef = fieldSet;
-                    }
-                }
-                if(isSet(fieldSet.isUnreferencedProperties())) {
-                    if(result.fieldSetForUnreferencedPropertiesRef != null) {
-                        fieldSet.setMetadataError("More than one column with 'unreferencedProperties' attribute set");
-                    } else {
-                        result.fieldSetForUnreferencedPropertiesRef = fieldSet;
-                    }
-                }
-            }
-
-            @Override
-            public void visit(final BS3TabGroup bs3TabGroup) {
-                if(isSet(bs3TabGroup.isUnreferencedCollections())) {
-                    if(result.tabGroupForUnreferencedCollectionsRef != null) {
-                        bs3TabGroup.setMetadataError("More than one tabgroup with 'unreferencedCollections' attribute set");
-                    } else if(result.colForUnreferencedCollectionsRef != null) {
-                        bs3TabGroup.setMetadataError("Already found a column with 'unreferencedCollections' attribute set");
-                    } else {
-                        result.tabGroupForUnreferencedCollectionsRef = bs3TabGroup;
-                    }
-                }
-            }
-        });
+        val oneToOneAssociationById = ObjectMember.mapById(getOneToOneAssociations(objectSpec));
+        val oneToManyAssociationById = ObjectMember.mapById(getOneToManyAssociations(objectSpec));
+        val objectActionById = ObjectMember.mapById( objectSpec.streamObjectActions(Contributed.INCLUDED));
 
-        if(result.colForUnreferencedActionsRef == null && result.fieldSetForUnreferencedActionsRef == null) {
-            bs3Grid.getMetadataErrors().add("No column and also no fieldset found with the 'unreferencedActions' attribute set");
-        }
-        if(result.fieldSetForUnreferencedPropertiesRef == null) {
-            bs3Grid.getMetadataErrors().add("No fieldset found with the 'unreferencedProperties' attribute set");
-        }
-        if(result.colForUnreferencedCollectionsRef == null && result.tabGroupForUnreferencedCollectionsRef == null) {
-            bs3Grid.getMetadataErrors().add("No column and also no tabgroup found with the 'unreferencedCollections' attribute set");
-        }
+        val propertyLayoutDataById = bs3Grid.getAllPropertiesById();
+        val collectionLayoutDataById = bs3Grid.getAllCollectionsById();
+        val actionLayoutDataById = bs3Grid.getAllActionsById();
 
-        if(     result.colForUnreferencedActionsRef == null && result.fieldSetForUnreferencedActionsRef == null ||
-                result.fieldSetForUnreferencedPropertiesRef == null ||
-                result.colForUnreferencedCollectionsRef == null && result.tabGroupForUnreferencedCollectionsRef == null) {
+        val gridModelIfValid = GridModel.createFrom(bs3Grid);
+        if(!gridModelIfValid.isPresent()) { // only present if valid
             return false;
         }
+        val gridModel = gridModelIfValid.get(); 
 
-        // add missing properties will be added to the first fieldset of the specified column
+        // * surplus ... those defined in the grid model but not available with the meta-model 
+        // * missing ... those available with the meta-model but missing in the grid-model 
+        // (missing properties will be added to the first field-set of the specified column)
+        
         val surplusAndMissingPropertyIds = 
                 surplusAndMissing(propertyLayoutDataById.keySet(),  oneToOneAssociationById.keySet());
         val surplusPropertyIds = surplusAndMissingPropertyIds.getSurplus();
@@ -296,14 +201,13 @@ public class GridSystemServiceBS3 extends GridSystemServiceAbstract<BS3Grid> {
             propertyLayoutDataById.get(surplusPropertyId).setMetadataError("No such property");
         }
 
-        // catalog which associations are bound to an existing field set
-        // so that (below) we can determine which missing property ids are not unbound vs which should be included
-        // in the fieldset that they are bound to.
-        final Map<String, Set<String>> boundAssociationIdsByFieldSetId = _Maps.newHashMap();
-
-        // all those explicitly in the grid
-        for (FieldSet fieldSet : gridModel.fieldSets()) {
-            final String fieldSetId = fieldSet.getId();
+        // catalog which associations are bound to an existing field-set
+        // so that (below) we can determine which missing property IDs are not unbound vs 
+        // which should be included in the field-set that they are bound to.
+        val boundAssociationIdsByFieldSetId = _Maps.<String, Set<String>>newHashMap();
+        
+        for (val fieldSet : gridModel.fieldSets()) {
+            val fieldSetId = fieldSet.getId();
             Set<String> boundAssociationIds = boundAssociationIdsByFieldSetId.get(fieldSetId);
             if(boundAssociationIds == null) {
                 boundAssociationIds = stream(fieldSet.getProperties())
@@ -313,13 +217,13 @@ public class GridSystemServiceBS3 extends GridSystemServiceAbstract<BS3Grid> {
             }
         }
 
-        // 1-to-1-association Ids, that want to contribute to the 'metadata' FieldSet
+        // 1-to-1-association IDs, that want to contribute to the 'metadata' FieldSet
         // but are unbound, because such a FieldSet is not defined by the given layout.
         val unboundMetadataContributingIds = _Sets.<String>newHashSet();
 
         // along with any specified by existing metadata
         for (OneToOneAssociation otoa : oneToOneAssociationById.values()) {
-            final MemberOrderFacet memberOrderFacet = otoa.getFacet(MemberOrderFacet.class);
+            val memberOrderFacet = otoa.getFacet(MemberOrderFacet.class);
             if(memberOrderFacet != null) {
                 val id = asId(memberOrderFacet.name());
                 if(gridModel.containsFieldSetId(id)) {
@@ -334,7 +238,7 @@ public class GridSystemServiceBS3 extends GridSystemServiceAbstract<BS3Grid> {
 
         if(!missingPropertyIds.isEmpty()) {
 
-            val unboundPropertyIds = _Lists.newArrayList(missingPropertyIds);
+            val unboundPropertyIds = _Sets.newLinkedHashSet(missingPropertyIds);
 
             for (final String fieldSetId : boundAssociationIdsByFieldSetId.keySet()) {
                 val boundPropertyIds = boundAssociationIdsByFieldSetId.get(fieldSetId);
@@ -343,26 +247,24 @@ public class GridSystemServiceBS3 extends GridSystemServiceAbstract<BS3Grid> {
 
             for (final String fieldSetId : boundAssociationIdsByFieldSetId.keySet()) {
                 val fieldSet = gridModel.getFieldSet(fieldSetId);
-                val associationIds =
-                        boundAssociationIdsByFieldSetId.get(fieldSetId);
-
-                val associations1To1 =
+                val associationIds = boundAssociationIdsByFieldSetId.get(fieldSetId);
+                
+                val associations1To1Ids =
                         associationIds.stream()
                         .map(oneToOneAssociationById::get)
                         .filter(_NullSafe::isPresent)
                         .sorted(ObjectMember.Comparators.byMemberOrderSequence())
+                        .map(ObjectAssociation::getId)
                         .collect(Collectors.toList());
 
-                addPropertiesTo(fieldSet,
-                        _Lists.map(associations1To1, ObjectAssociation::getId),
-                        propertyLayoutDataById);
+                addPropertiesTo(fieldSet, associations1To1Ids, propertyLayoutDataById::put);
             }
 
             if(!unboundPropertyIds.isEmpty()) {
-                val fieldSet = result.fieldSetForUnreferencedPropertiesRef;
+                val fieldSet = gridModel.getFieldSetForUnreferencedPropertiesRef();
                 if(fieldSet != null) {
                     unboundPropertyIds.removeAll(unboundMetadataContributingIds);
-                    addPropertiesTo(fieldSet, unboundPropertyIds, propertyLayoutDataById);
+                    addPropertiesTo(fieldSet, unboundPropertyIds, propertyLayoutDataById::put);
                 }
             }
         }
@@ -380,20 +282,19 @@ public class GridSystemServiceBS3 extends GridSystemServiceAbstract<BS3Grid> {
         if(!missingCollectionIds.isEmpty()) {
             final List<OneToManyAssociation> sortedCollections = 
                     _Lists.map(missingCollectionIds, oneToManyAssociationById::get);
-            {
-                sortedCollections.sort(ObjectMember.Comparators.byMemberOrderSequence());
-            }
+            
+            sortedCollections.sort(ObjectMember.Comparators.byMemberOrderSequence());
 
             final List<String> sortedMissingCollectionIds = 
                     _Lists.map(sortedCollections, ObjectAssociation::getId);
 
-            final BS3TabGroup bs3TabGroup = result.tabGroupForUnreferencedCollectionsRef;
+            final BS3TabGroup bs3TabGroup = gridModel.getTabGroupForUnreferencedCollectionsRef();
             if(bs3TabGroup != null) {
                 addCollectionsTo(bs3TabGroup, sortedMissingCollectionIds, objectSpec);
             } else {
-                final BS3Col bs3Col = result.colForUnreferencedCollectionsRef;
+                final BS3Col bs3Col = gridModel.getColForUnreferencedCollectionsRef();
                 if(bs3Col != null) {
-                    addCollectionsTo(bs3Col, sortedMissingCollectionIds, collectionLayoutDataById);
+                    addCollectionsTo(bs3Col, sortedMissingCollectionIds, collectionLayoutDataById::put);
                 }
             }
         }
@@ -408,11 +309,8 @@ public class GridSystemServiceBS3 extends GridSystemServiceAbstract<BS3Grid> {
 
         final List<ObjectAction> sortedPossiblyMissingActions = 
                 _Lists.map(possiblyMissingActionIds, objectActionById::get);
-        {
-            sortedPossiblyMissingActions
-            .sort(ObjectMember.Comparators.byMemberOrderSequence());
-        }
-
+        
+        sortedPossiblyMissingActions.sort(ObjectMember.Comparators.byMemberOrderSequence());
 
         final List<String> sortedPossiblyMissingActionIds =
                 _Lists.map(sortedPossiblyMissingActions, ObjectMember::getId);
@@ -499,13 +397,13 @@ public class GridSystemServiceBS3 extends GridSystemServiceAbstract<BS3Grid> {
         }
 
         if(!missingActionIds.isEmpty()) {
-            final BS3Col bs3Col = result.colForUnreferencedActionsRef;
+            final BS3Col bs3Col = gridModel.getColForUnreferencedActionsRef();
             if(bs3Col != null) {
-                addActionsTo(bs3Col, missingActionIds, actionLayoutDataById);
+                addActionsTo(bs3Col, missingActionIds, actionLayoutDataById::put);
             } else {
-                final FieldSet fieldSet = result.fieldSetForUnreferencedActionsRef;
+                final FieldSet fieldSet = gridModel.getFieldSetForUnreferencedActionsRef();
                 if(fieldSet != null) {
-                    addActionsTo(fieldSet, missingActionIds, actionLayoutDataById);
+                    addActionsTo(fieldSet, missingActionIds, actionLayoutDataById::put);
                 }
             }
         }
@@ -515,40 +413,43 @@ public class GridSystemServiceBS3 extends GridSystemServiceAbstract<BS3Grid> {
 
     private void addPropertiesTo(
             final FieldSet fieldSet,
-            final List<String> propertyIds,
-            final LinkedHashMap<String, PropertyLayoutData> propertyLayoutDataById) {
-
+            final Collection<String> propertyIds,
+            final BiConsumer<String, PropertyLayoutData> onNewLayoutData) {
+        
         final Set<String> existingIds =
                 stream(fieldSet.getProperties())
                 .map(PropertyLayoutData::getId)
                 .collect(Collectors.toSet());
-
+        
         for (final String propertyId : propertyIds) {
-            if(!existingIds.contains(propertyId)) {
-                final PropertyLayoutData propertyLayoutData = new PropertyLayoutData(propertyId);
-                fieldSet.getProperties().add(propertyLayoutData);
-                propertyLayoutData.setOwner(fieldSet);
-                propertyLayoutDataById.put(propertyId, propertyLayoutData);
+            if(existingIds.contains(propertyId)) {
+                continue;
             }
+            val propertyLayoutData = new PropertyLayoutData(propertyId);
+            fieldSet.getProperties().add(propertyLayoutData);
+            propertyLayoutData.setOwner(fieldSet);
+            onNewLayoutData.accept(propertyId, propertyLayoutData);
         }
     }
 
     private void addCollectionsTo(
             final BS3Col tabRowCol,
-            final List<String> collectionIds,
-            final LinkedHashMap<String, CollectionLayoutData> collectionLayoutDataById) {
+            final Collection<String> collectionIds,
+            final BiConsumer<String, CollectionLayoutData> onNewLayoutData) {
+        
         for (final String collectionId : collectionIds) {
-            final CollectionLayoutData collectionLayoutData = new CollectionLayoutData(collectionId);
+            val collectionLayoutData = new CollectionLayoutData(collectionId);
             collectionLayoutData.setDefaultView("table");
             tabRowCol.getCollections().add(collectionLayoutData);
-            collectionLayoutDataById.put(collectionId, collectionLayoutData);
+            onNewLayoutData.accept(collectionId, collectionLayoutData);
         }
     }
 
     private void addCollectionsTo(
             final BS3TabGroup tabGroup,
-            final List<String> collectionIds,
+            final Collection<String> collectionIds,
             final ObjectSpecification objectSpec) {
+        
         for (final String collectionId : collectionIds) {
             final BS3Tab bs3Tab = new BS3Tab();
             bs3Tab.setName(objectSpec.getAssociationElseFail(collectionId).getName());
@@ -573,29 +474,32 @@ public class GridSystemServiceBS3 extends GridSystemServiceAbstract<BS3Grid> {
 
     protected void addActionsTo(
             final BS3Col bs3Col,
-            final List<String> actionIds,
-            final LinkedHashMap<String, ActionLayoutData> actionLayoutDataById) {
+            final Collection<String> actionIds,
+            final BiConsumer<String, ActionLayoutData> onNewLayoutData) {
+        
         for (String actionId : actionIds) {
-            final ActionLayoutData actionLayoutData = new ActionLayoutData(actionId);
+            val actionLayoutData = new ActionLayoutData(actionId);
             addActionTo(bs3Col, actionLayoutData);
-            actionLayoutDataById.put(actionId, actionLayoutData);
+            onNewLayoutData.accept(actionId, actionLayoutData);
         }
     }
 
     private void addActionsTo(
             final FieldSet fieldSet,
-            final List<String> actionIds,
-            final LinkedHashMap<String, ActionLayoutData> actionLayoutDataById) {
+            final Collection<String> actionIds,
+            final BiConsumer<String, ActionLayoutData> onNewLayoutData) {
+        
         for (String actionId : actionIds) {
-            final ActionLayoutData actionLayoutData = new ActionLayoutData(actionId);
+            val actionLayoutData = new ActionLayoutData(actionId);
             addActionTo(fieldSet, actionLayoutData);
-            actionLayoutDataById.put(actionId, actionLayoutData);
+            onNewLayoutData.accept(actionId, actionLayoutData);
         }
     }
 
     private void addActionTo(
             final ActionLayoutDataOwner owner,
             final ActionLayoutData actionLayoutData) {
+        
         List<ActionLayoutData> actions = owner.getActions();
         if(actions == null) {
             owner.setActions(actions = _Lists.newArrayList());
@@ -604,10 +508,6 @@ public class GridSystemServiceBS3 extends GridSystemServiceAbstract<BS3Grid> {
         actionLayoutData.setOwner(owner);
     }
 
-    private static Boolean isSet(final Boolean flag) {
-        return flag != null && flag;
-    }
-
     static String asId(final String str) {
         if(_Strings.isNullOrEmpty(str)) {
             return str;