/** * 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.services.grid.bootstrap3; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; import com.google.common.base.Function; import com.google.common.base.Predicates; import com.google.common.base.Strings; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.apache.isis.applib.annotation.ActionLayout; import org.apache.isis.applib.annotation.DomainService; import org.apache.isis.applib.annotation.MemberGroupLayout; import org.apache.isis.applib.annotation.NatureOfService; import org.apache.isis.applib.annotation.Programmatic; import org.apache.isis.applib.filter.Filters; import org.apache.isis.applib.layout.component.ActionLayoutData; import org.apache.isis.applib.layout.component.ActionLayoutDataOwner; import org.apache.isis.applib.layout.component.CollectionLayoutData; import org.apache.isis.applib.layout.component.DomainObjectLayoutData; import org.apache.isis.applib.layout.component.FieldSet; import org.apache.isis.applib.layout.component.Grid; import org.apache.isis.applib.layout.component.PropertyLayoutData; 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.BS3Tab; import org.apache.isis.applib.layout.grid.bootstrap3.BS3TabGroup; import org.apache.isis.applib.layout.grid.bootstrap3.Size; import org.apache.isis.core.metamodel.facets.actions.position.ActionPositionFacet; import org.apache.isis.core.metamodel.facets.members.order.MemberOrderFacet; import org.apache.isis.core.metamodel.facets.members.order.annotprop.MemberOrderFacetAnnotation; import org.apache.isis.core.metamodel.facets.object.membergroups.MemberGroupLayoutFacet; import org.apache.isis.core.metamodel.services.grid.GridSystemServiceAbstract; 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.ObjectAction; import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation; import org.apache.isis.core.metamodel.spec.feature.ObjectMember; import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation; import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation; @DomainService( nature = NatureOfService.DOMAIN, menuOrder = "" + Integer.MAX_VALUE ) public class GridSystemServiceBS3 extends GridSystemServiceAbstract<BS3Grid> { public static final String TNS = "http://isis.apache.org/applib/layout/grid/bootstrap3"; public static final String SCHEMA_LOCATION = "http://isis.apache.org/applib/layout/grid/bootstrap3/bootstrap3.xsd"; public GridSystemServiceBS3() { super(BS3Grid.class, TNS, SCHEMA_LOCATION); } @Programmatic @Override public Grid defaultGrid(final Class<?> domainClass) { final BS3Grid bs3Grid = new BS3Grid(); final ObjectSpecification objectSpec = specificationLoader.loadSpecification(domainClass); bs3Grid.setDomainClass(domainClass); final BS3Row headerRow = new BS3Row(); bs3Grid.getRows().add(headerRow); final BS3Col headerRowCol = new BS3Col(); headerRowCol.setSpan(12); headerRowCol.setUnreferencedActions(true); headerRowCol.setDomainObject(new DomainObjectLayoutData()); headerRow.getCols().add(headerRowCol); final BS3Row propsRow = new BS3Row(); bs3Grid.getRows().add(propsRow); final MemberGroupLayoutFacet memberGroupLayoutFacet = objectSpec.getFacet(MemberGroupLayoutFacet.class); if(memberGroupLayoutFacet != null) { // if have @MemberGroupLayout (or equally, a .layout.json file) final MemberGroupLayout.ColumnSpans columnSpans = memberGroupLayoutFacet.getColumnSpans(); addFieldSetsToColumn(propsRow, columnSpans.getLeft(), memberGroupLayoutFacet.getLeft(), true); addFieldSetsToColumn(propsRow, columnSpans.getMiddle(), memberGroupLayoutFacet.getMiddle(), false); addFieldSetsToColumn(propsRow, columnSpans.getRight(), memberGroupLayoutFacet.getRight(), false); final BS3Col col = new BS3Col(); final int collectionSpan = columnSpans.getCollections(); col.setUnreferencedCollections(true); col.setSpan(collectionSpan > 0? collectionSpan: 12); propsRow.getCols().add(col); // will already be sorted per @MemberOrder final List<OneToManyAssociation> collections = objectSpec.getCollections(Contributed.INCLUDED); for (OneToManyAssociation collection : collections) { col.getCollections().add(new CollectionLayoutData(collection.getId())); } } else { // if no layout hints other than @MemberOrder addFieldSetsToColumn(propsRow, 4, Arrays.asList("General"), true); final BS3Col col = new BS3Col(); col.setUnreferencedCollections(true); col.setSpan(12); propsRow.getCols().add(col); } return bs3Grid; } static void addFieldSetsToColumn( final BS3Row propsRow, final int span, final List<String> memberGroupNames, final boolean unreferencedProperties) { if(span > 0 || unreferencedProperties) { final BS3Col col = new BS3Col(); col.setSpan(span); // in case we are here because of 'unreferencedProperties' needs setting propsRow.getCols().add(col); final List<String> leftMemberGroups = memberGroupNames; for (String memberGroup : leftMemberGroups) { final FieldSet fieldSet = new FieldSet(); fieldSet.setName(memberGroup); // fieldSet's id will be derived from the name later // during normalization phase. if(unreferencedProperties && col.getFieldSets().isEmpty()) { fieldSet.setUnreferencedProperties(true); } col.getFieldSets().add(fieldSet); } } } // ////////////////////////////////////// /** * Mandatory hook method defined in {@link GridSystemServiceAbstract superclass}, called by {@link #normalize(Grid, Class)}. */ @Override protected boolean validateAndNormalize( final Grid grid, final Class<?> domainClass) { final ObjectSpecification objectSpec = specificationLoader.loadSpecification(domainClass); final Map<String, OneToOneAssociation> oneToOneAssociationById = ObjectMember.Util.mapById(getOneToOneAssociations(objectSpec)); final Map<String, OneToManyAssociation> oneToManyAssociationById = ObjectMember.Util.mapById(getOneToManyAssociations(objectSpec)); final Map<String, ObjectAction> objectActionById = ObjectMember.Util.mapById( FluentIterable .from(objectSpec.getObjectActions(Contributed.INCLUDED)) .filter(Filters.asPredicate(ObjectAction.Filters.notBulkOnly())) .toList()); 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 final List<String> gridIds = Lists.newArrayList(); final LinkedHashMap<String, BS3Row> rowIds = Maps.newLinkedHashMap(); final LinkedHashMap<String, BS3Col> colIds = Maps.newLinkedHashMap(); final LinkedHashMap<String, FieldSet> fieldSetIds = Maps.newLinkedHashMap(); final AtomicReference<Boolean> duplicateIdDetected = new AtomicReference<>(false); bs3Grid.visit(new BS3Grid.VisitorAdapter(){ @Override public void visit(final BS3Row bs3Row) { final String id = bs3Row.getId(); if(id == null) { return; } if(gridIds.contains(id)) { bs3Row.setMetadataError("There is another element in the grid with this id"); duplicateIdDetected.set(true); return; } rowIds.put(id, bs3Row); gridIds.add(id); } @Override public void visit(final BS3Col bs3Col) { final String id = bs3Col.getId(); if(id == null) { return; } if(gridIds.contains(id)) { bs3Col.setMetadataError("There is another element in the grid with this id"); duplicateIdDetected.set(true); return; } colIds.put(id, bs3Col); gridIds.add(id); } @Override public void visit(final FieldSet fieldSet) { String id = fieldSet.getId(); if(id == null) { final String name = fieldSet.getName(); fieldSet.setId(id = asId(name)); } if(gridIds.contains(id)) { fieldSet.setMetadataError("There is another element in the grid with this id"); duplicateIdDetected.set(true); return; } fieldSetIds.put(id, fieldSet); gridIds.add(id); } }); if(duplicateIdDetected.get()) { return false; } // ensure that there is exactly one col with the // unreferencedActions, unreferencedProperties and unreferencedCollections attribute set. final AtomicReference<BS3Col> colForUnreferencedActionsRef = new AtomicReference<>(); final AtomicReference<BS3Col> colForUnreferencedCollectionsRef = new AtomicReference<>(); final AtomicReference<FieldSet> fieldSetForUnreferencedActionsRef = new AtomicReference<>(); final AtomicReference<FieldSet> fieldSetForUnreferencedPropertiesRef = new AtomicReference<>(); final AtomicReference<BS3TabGroup> tabGroupForUnreferencedCollectionsRef = new AtomicReference<>(); bs3Grid.visit(new BS3Grid.VisitorAdapter(){ @Override public void visit(final BS3Col bs3Col) { if(isSet(bs3Col.isUnreferencedActions())) { if(colForUnreferencedActionsRef.get() != null) { bs3Col.setMetadataError("More than one col with 'unreferencedActions' attribute set"); } else if(fieldSetForUnreferencedActionsRef.get() != null) { bs3Col.setMetadataError("Already found a fieldset with 'unreferencedActions' attribute set"); } else { colForUnreferencedActionsRef.set(bs3Col); } } if(isSet(bs3Col.isUnreferencedCollections())) { if(colForUnreferencedCollectionsRef.get() != null) { bs3Col.setMetadataError("More than one col with 'unreferencedCollections' attribute set"); } else if(tabGroupForUnreferencedCollectionsRef.get() != null) { bs3Col.setMetadataError("Already found a tabgroup with 'unreferencedCollections' attribute set"); } else { colForUnreferencedCollectionsRef.set(bs3Col); } } } @Override public void visit(final FieldSet fieldSet) { if(isSet(fieldSet.isUnreferencedActions())) { if(fieldSetForUnreferencedActionsRef.get() != null) { fieldSet.setMetadataError("More than one fieldset with 'unreferencedActions' attribute set"); } else if(colForUnreferencedActionsRef.get() != null) { fieldSet.setMetadataError("Already found a column with 'unreferencedActions' attribute set"); } else { fieldSetForUnreferencedActionsRef.set(fieldSet); } } if(isSet(fieldSet.isUnreferencedProperties())) { if(fieldSetForUnreferencedPropertiesRef.get() != null) { fieldSet.setMetadataError("More than one col with 'unreferencedProperties' attribute set"); } else { fieldSetForUnreferencedPropertiesRef.set(fieldSet); } } } @Override public void visit(final BS3TabGroup bs3TabGroup) { if(isSet(bs3TabGroup.isUnreferencedCollections())) { if(tabGroupForUnreferencedCollectionsRef.get() != null) { bs3TabGroup.setMetadataError("More than one tabgroup with 'unreferencedCollections' attribute set"); } else if(colForUnreferencedCollectionsRef.get() != null) { bs3TabGroup.setMetadataError("Already found a column with 'unreferencedCollections' attribute set"); } else { tabGroupForUnreferencedCollectionsRef.set(bs3TabGroup); } } } }); if(colForUnreferencedActionsRef.get() == null && fieldSetForUnreferencedActionsRef.get() == null) { bs3Grid.getMetadataErrors().add("No column and also no fieldset found with the 'unreferencedActions' attribute set"); } if(fieldSetForUnreferencedPropertiesRef.get() == null) { bs3Grid.getMetadataErrors().add("No fieldset found with the 'unreferencedProperties' attribute set"); } if(colForUnreferencedCollectionsRef.get() == null && tabGroupForUnreferencedCollectionsRef.get() == null) { bs3Grid.getMetadataErrors().add("No column and also no tabgroup found with the 'unreferencedCollections' attribute set"); } if( colForUnreferencedActionsRef.get() == null && fieldSetForUnreferencedActionsRef.get() == null || fieldSetForUnreferencedPropertiesRef.get() == null || colForUnreferencedCollectionsRef.get() == null && tabGroupForUnreferencedCollectionsRef.get() == null) { return false; } // add missing properties will be added to the first fieldset of the specified column final Tuple<List<String>> propertyIdTuple = surplusAndMissing(propertyLayoutDataById.keySet(), oneToOneAssociationById.keySet()); final List<String> surplusPropertyIds = propertyIdTuple.first; final List<String> missingPropertyIds = propertyIdTuple.second; for (String surplusPropertyId : surplusPropertyIds) { 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 : fieldSetIds.values()) { final String fieldSetId = fieldSet.getId(); Set<String> boundAssociationIds = boundAssociationIdsByFieldSetId.get(fieldSetId); if(boundAssociationIds == null) { boundAssociationIds = Sets.newLinkedHashSet(); boundAssociationIds.addAll( FluentIterable.from(fieldSet.getProperties()).transform( new Function<PropertyLayoutData, String>() { @Override public String apply(@Nullable final PropertyLayoutData propertyLayoutData) { return propertyLayoutData.getId(); } }).toList()); boundAssociationIdsByFieldSetId.put(fieldSetId, boundAssociationIds); } } // along with any specified by existing metadata for (OneToOneAssociation otoa : oneToOneAssociationById.values()) { final MemberOrderFacet memberOrderFacet = otoa.getFacet(MemberOrderFacet.class); if(memberOrderFacet != null) { final String id = asId(memberOrderFacet.name()); if(fieldSetIds.containsKey(id)) { Set<String> boundAssociationIds = boundAssociationIdsByFieldSetId.get(id); if(boundAssociationIds == null) { boundAssociationIds = Sets.newLinkedHashSet(); boundAssociationIdsByFieldSetId.put(id, boundAssociationIds); } boundAssociationIds.add(otoa.getId()); } } } if(!missingPropertyIds.isEmpty()) { final List<String> unboundPropertyIds = Lists.newArrayList(missingPropertyIds); for (final String fieldSetId : boundAssociationIdsByFieldSetId.keySet()) { final Set<String> boundPropertyIds = boundAssociationIdsByFieldSetId.get(fieldSetId); unboundPropertyIds.removeAll(boundPropertyIds); } for (final String fieldSetId : boundAssociationIdsByFieldSetId.keySet()) { final FieldSet fieldSet = fieldSetIds.get(fieldSetId); final Set<String> associationIds = boundAssociationIdsByFieldSetId.get(fieldSetId); final List<OneToOneAssociation> associations = Lists.newArrayList( FluentIterable.from(associationIds) .transform(new Function<String, OneToOneAssociation>() { @Nullable @Override public OneToOneAssociation apply(final String propertyId) { return oneToOneAssociationById.get(propertyId); } }) .filter(Predicates.<OneToOneAssociation>notNull()) ); Collections.sort(associations, ObjectMember.Comparators.byMemberOrderSequence()); addPropertiesTo(fieldSet, FluentIterable.from(associations) .transform(ObjectAssociation.Functions.toId()) .toList(), propertyLayoutDataById); } if(!unboundPropertyIds.isEmpty()) { final FieldSet fieldSet = fieldSetForUnreferencedPropertiesRef.get(); if(fieldSet != null) { addPropertiesTo(fieldSet, unboundPropertyIds, propertyLayoutDataById); } } } // any missing collections will be added as tabs to a new TabGroup in the specified column final Tuple<List<String>> collectionIdTuple = surplusAndMissing(collectionLayoutDataById.keySet(), oneToManyAssociationById.keySet()); final List<String> surplusCollectionIds = collectionIdTuple.first; final List<String> missingCollectionIds = collectionIdTuple.second; for (String surplusCollectionId : surplusCollectionIds) { collectionLayoutDataById.get(surplusCollectionId).setMetadataError("No such collection"); } if(!missingCollectionIds.isEmpty()) { List<OneToManyAssociation> sortedCollections = Lists.newArrayList( FluentIterable.from(missingCollectionIds) .transform(new Function<String, OneToManyAssociation>() { @Nullable @Override public OneToManyAssociation apply(@Nullable final String collectionId) { return oneToManyAssociationById.get(collectionId); } }) .toSortedList(ObjectMember.Comparators.byMemberOrderSequence()) ); final ImmutableList<String> sortedMissingCollectionIds = FluentIterable.from(sortedCollections) .transform(ObjectAssociation.Functions.toId()).toList(); final BS3TabGroup bs3TabGroup = tabGroupForUnreferencedCollectionsRef.get(); if(bs3TabGroup != null) { addCollectionsTo(bs3TabGroup, sortedMissingCollectionIds, objectSpec); } else { final BS3Col bs3Col = colForUnreferencedCollectionsRef.get(); if(bs3Col != null) { addCollectionsTo(bs3Col, sortedMissingCollectionIds, collectionLayoutDataById); } } } // any missing actions will be added as actions in the specified column final Tuple<List<String>> actionIdTuple = surplusAndMissing(actionLayoutDataById.keySet(), objectActionById.keySet()); final List<String> surplusActionIds = actionIdTuple.first; final List<String> possiblyMissingActionIds = actionIdTuple.second; final List<String> associatedActionIds = Lists.newArrayList(); List<ObjectAction> sortedPossiblyMissingActions = Lists.newArrayList( FluentIterable.from(possiblyMissingActionIds) .transform(new Function<String, ObjectAction>() { @Nullable @Override public ObjectAction apply(@Nullable final String actionId) { return objectActionById.get(actionId); } }) .toSortedList(ObjectMember.Comparators.byMemberOrderSequence())); List<String> sortedPossiblyMissingActionIds = FluentIterable.from(sortedPossiblyMissingActions) .transform(ObjectMember.Functions.getId()) .toList(); for (String actionId : sortedPossiblyMissingActionIds) { final ObjectAction oa = objectActionById.get(actionId); final MemberOrderFacet memberOrderFacet = oa.getFacet(MemberOrderFacet.class); if(memberOrderFacet == null) { continue; } final String memberOrderName = memberOrderFacet.name(); if (memberOrderName == null) { continue; } final String id = asId(memberOrderName); if (oneToOneAssociationById.containsKey(id)) { associatedActionIds.add(actionId); if(!(memberOrderFacet instanceof MemberOrderFacetAnnotation)) { // if binding not via annotation, then explicitly bind this // action to the property final PropertyLayoutData propertyLayoutData = propertyLayoutDataById.get(id); final ActionLayoutData actionLayoutData = new ActionLayoutData(actionId); final ActionPositionFacet actionPositionFacet = oa.getFacet(ActionPositionFacet.class); final ActionLayoutDataOwner owner; final ActionLayout.Position position; if(actionPositionFacet != null) { position = actionPositionFacet.position(); owner = position == ActionLayout.Position.PANEL || position == ActionLayout.Position.PANEL_DROPDOWN ? propertyLayoutData.getOwner() : propertyLayoutData; } else { position = ActionLayout.Position.BELOW; owner = propertyLayoutData; } actionLayoutData.setPosition(position); addActionTo(owner, actionLayoutData); } continue; } if (oneToManyAssociationById.containsKey(id)) { associatedActionIds.add(actionId); if(!(memberOrderFacet instanceof MemberOrderFacetAnnotation)) { // if binding not via annotation, then explicitly bind this // action to the property final CollectionLayoutData collectionLayoutData = collectionLayoutDataById.get(id); final ActionLayoutData actionLayoutData = new ActionLayoutData(actionId); addActionTo(collectionLayoutData, actionLayoutData); } continue; } // if the @MemberOrder for the action references a field set (that has bound // associations), then don't mark it as missing, but instead explicitly add it to the // list of actions of that fieldset. final Set<String> boundAssociationIds = boundAssociationIdsByFieldSetId.get(id); if(boundAssociationIds != null && !boundAssociationIds.isEmpty()) { associatedActionIds.add(actionId); final ActionLayoutData actionLayoutData = new ActionLayoutData(actionId); actionLayoutData.setPosition(ActionLayout.Position.PANEL_DROPDOWN); final FieldSet fieldSet = fieldSetIds.get(id); addActionTo(fieldSet, actionLayoutData); continue; } } // ... the missing actions are those in the second tuple, excluding those associated (via @MemberOrder#name) // to a property or collection. final List<String> missingActionIds = Lists.newArrayList(sortedPossiblyMissingActionIds); missingActionIds.removeAll(associatedActionIds); for (String surplusActionId : surplusActionIds) { actionLayoutDataById.get(surplusActionId).setMetadataError("No such action"); } if(!missingActionIds.isEmpty()) { final BS3Col bs3Col = colForUnreferencedActionsRef.get(); if(bs3Col != null) { addActionsTo(bs3Col, missingActionIds, actionLayoutDataById); } else { final FieldSet fieldSet = fieldSetForUnreferencedActionsRef.get(); if(fieldSet != null) { addActionsTo(fieldSet, missingActionIds, actionLayoutDataById); } } } return true; } protected void addPropertiesTo( final FieldSet fieldSet, final List<String> propertyIds, final LinkedHashMap<String, PropertyLayoutData> propertyLayoutDataById) { final ImmutableList<String> existingIds = FluentIterable .from(fieldSet.getProperties()) .transform(new Function<PropertyLayoutData, String>() { @Nullable @Override public String apply(@Nullable final PropertyLayoutData propertyLayoutData) { return propertyLayoutData.getId(); } }) .toList(); 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); } } } protected void addCollectionsTo( final BS3Col tabRowCol, final List<String> collectionIds, final LinkedHashMap<String, CollectionLayoutData> collectionLayoutDataById) { for (final String collectionId : collectionIds) { final CollectionLayoutData collectionLayoutData = new CollectionLayoutData(collectionId); collectionLayoutData.setDefaultView("table"); tabRowCol.getCollections().add(collectionLayoutData); collectionLayoutDataById.put(collectionId, collectionLayoutData); } } protected void addCollectionsTo( final BS3TabGroup tabGroup, final List<String> collectionIds, final ObjectSpecification objectSpec) { for (final String collectionId : collectionIds) { final BS3Tab bs3Tab = new BS3Tab(); bs3Tab.setName(objectSpec.getAssociation(collectionId).getName()); tabGroup.getTabs().add(bs3Tab); bs3Tab.setOwner(tabGroup); final BS3Row tabRow = new BS3Row(); tabRow.setOwner(bs3Tab); bs3Tab.getRows().add(tabRow); final BS3Col tabRowCol = new BS3Col(); tabRowCol.setSpan(12); tabRowCol.setSize(Size.MD); tabRowCol.setOwner(tabRow); tabRow.getCols().add(tabRowCol); final CollectionLayoutData layoutMetadata = new CollectionLayoutData(collectionId); layoutMetadata.setDefaultView("table"); tabRowCol.getCollections().add(layoutMetadata); } } protected void addActionsTo( final BS3Col bs3Col, final List<String> actionIds, final LinkedHashMap<String, ActionLayoutData> actionLayoutDataById) { for (String actionId : actionIds) { final ActionLayoutData actionLayoutData = new ActionLayoutData(actionId); addActionTo(bs3Col, actionLayoutData); actionLayoutDataById.put(actionId, actionLayoutData); } } protected void addActionsTo( final FieldSet fieldSet, final List<String> actionIds, final LinkedHashMap<String, ActionLayoutData> actionLayoutDataById) { for (String actionId : actionIds) { final ActionLayoutData actionLayoutData = new ActionLayoutData(actionId); addActionTo(fieldSet, actionLayoutData); actionLayoutDataById.put(actionId, actionLayoutData); } } protected void addActionTo( final ActionLayoutDataOwner owner, final ActionLayoutData actionLayoutData) { List<ActionLayoutData> actions = owner.getActions(); if(actions == null) { owner.setActions(actions = Lists.newArrayList()); } actions.add(actionLayoutData); actionLayoutData.setOwner(owner); } private static Boolean isSet(final Boolean flag) { return flag != null && flag; } private static String asId(final String str) { if(Strings.isNullOrEmpty(str)) { return str; } final char c = str.charAt(0); return Character.toLowerCase(c) + str.substring(1).replaceAll("\\s+", ""); } }