001/**
002 *  Licensed to the Apache Software Foundation (ASF) under one or more
003 *  contributor license agreements.  See the NOTICE file distributed with
004 *  this work for additional information regarding copyright ownership.
005 *  The ASF licenses this file to You under the Apache License, Version 2.0
006 *  (the "License"); you may not use this file except in compliance with
007 *  the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 */
017package org.apache.isis.core.metamodel.layoutmetadata.json;
018
019import java.io.IOException;
020import java.util.Collections;
021import java.util.Comparator;
022import java.util.List;
023import java.util.Map;
024import java.util.Properties;
025import java.util.Set;
026
027import com.google.common.base.Function;
028import com.google.common.base.Joiner;
029import com.google.common.base.Strings;
030import com.google.common.collect.Iterables;
031import com.google.common.collect.Lists;
032import com.google.common.collect.Maps;
033import com.google.common.collect.Sets;
034import com.google.gson.Gson;
035import com.google.gson.GsonBuilder;
036
037import org.apache.isis.applib.annotation.MemberGroupLayout.ColumnSpans;
038import org.apache.isis.applib.annotation.Render;
039import org.apache.isis.applib.annotation.Render.Type;
040import org.apache.isis.applib.annotation.When;
041import org.apache.isis.applib.annotation.Where;
042import org.apache.isis.applib.filter.Filter;
043import org.apache.isis.applib.filter.Filters;
044import org.apache.isis.applib.services.memento.MementoService.Memento;
045import org.apache.isis.core.commons.lang.ClassExtensions;
046import org.apache.isis.core.metamodel.facets.describedas.DescribedAsFacet;
047import org.apache.isis.core.metamodel.facets.hide.HiddenFacet;
048import org.apache.isis.core.metamodel.facets.members.cssclass.CssClassFacet;
049import org.apache.isis.core.metamodel.facets.members.order.MemberOrderFacet;
050import org.apache.isis.core.metamodel.facets.members.resolve.RenderFacet;
051import org.apache.isis.core.metamodel.facets.multiline.MultiLineFacet;
052import org.apache.isis.core.metamodel.facets.named.NamedFacet;
053import org.apache.isis.core.metamodel.facets.object.membergroups.MemberGroupLayoutFacet;
054import org.apache.isis.core.metamodel.facets.object.paged.PagedFacet;
055import org.apache.isis.core.metamodel.facets.typicallen.TypicalLengthFacet;
056import org.apache.isis.core.metamodel.layout.memberorderfacet.MemberOrderFacetComparator;
057import org.apache.isis.core.metamodel.layoutmetadata.ActionRepr;
058import org.apache.isis.core.metamodel.layoutmetadata.ColumnRepr;
059import org.apache.isis.core.metamodel.layoutmetadata.CssClassFacetRepr;
060import org.apache.isis.core.metamodel.layoutmetadata.DescribedAsFacetRepr;
061import org.apache.isis.core.metamodel.layoutmetadata.DisabledFacetRepr;
062import org.apache.isis.core.metamodel.layoutmetadata.HiddenFacetRepr;
063import org.apache.isis.core.metamodel.layoutmetadata.LayoutMetadata;
064import org.apache.isis.core.metamodel.layoutmetadata.LayoutMetadataReader;
065import org.apache.isis.core.metamodel.layoutmetadata.MemberGroupRepr;
066import org.apache.isis.core.metamodel.layoutmetadata.MemberRepr;
067import org.apache.isis.core.metamodel.layoutmetadata.MultiLineFacetRepr;
068import org.apache.isis.core.metamodel.layoutmetadata.NamedFacetRepr;
069import org.apache.isis.core.metamodel.layoutmetadata.PagedFacetRepr;
070import org.apache.isis.core.metamodel.layoutmetadata.RenderFacetRepr;
071import org.apache.isis.core.metamodel.layoutmetadata.TypicalLengthFacetRepr;
072import org.apache.isis.core.metamodel.spec.ActionType;
073import org.apache.isis.core.metamodel.spec.ObjectSpecification;
074import org.apache.isis.core.metamodel.spec.ObjectSpecifications;
075import org.apache.isis.core.metamodel.spec.ObjectSpecifications.MemberGroupLayoutHint;
076import org.apache.isis.core.metamodel.spec.feature.Contributed;
077import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
078import org.apache.isis.core.metamodel.spec.feature.ObjectActions;
079import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
080import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
081import org.apache.isis.core.progmodel.facets.members.disabled.DisabledFacet;
082import org.apache.isis.core.progmodel.facets.members.disabled.DisabledFacetImpl;
083import org.apache.isis.core.progmodel.facets.members.disabled.annotation.DisabledFacetAnnotation;
084import org.apache.isis.core.progmodel.facets.members.disabled.annotation.DisabledFacetFromProperties;
085
086public class LayoutMetadataReaderFromJson implements LayoutMetadataReader {
087
088    public Properties asProperties(Class<?> domainClass) {
089        LayoutMetadata metadata;
090        try {
091            metadata = readMetadata(domainClass);
092        } catch (Exception e) {
093            throw new ReaderException("Failed to locate/parse " + domainClass.getName() + ".layout.json file (" + e.getMessage() + ")", e);
094        }
095        if(metadata.getColumns() == null || metadata.getColumns().size() != 4) {
096            throw new ReaderException("JSON metadata must have precisely 4 columns (prop/prop/prop/coll)");
097        }
098
099        final Properties props = new Properties();
100        
101        setMemberGroupLayoutColumnSpans(metadata, props);
102        setMemberGroupLayoutColumnLists(metadata, 0, "left", props);
103        setMemberGroupLayoutColumnLists(metadata, 1, "middle", props);
104        setMemberGroupLayoutColumnLists(metadata, 2, "right", props);
105        
106        int[] memberSeq = {0};
107        setProperties(metadata, props, memberSeq);
108        setCollections(metadata, props, memberSeq);
109        setFreestandingActions(metadata, props);
110
111        return props;
112    }
113
114    private static void setMemberGroupLayoutColumnSpans(LayoutMetadata metadata, final Properties props) {
115        final List<ColumnRepr> columns = metadata.getColumns();
116        final String columnSpansStr = Joiner.on(",").join(Iterables.transform(columns, new Function<ColumnRepr,Integer>(){
117            @Override
118            public Integer apply(ColumnRepr input) {
119                return input.span;
120            }}));
121        props.setProperty("class.memberGroupLayout.columnSpans", columnSpansStr);
122    }
123
124    private static void setMemberGroupLayoutColumnLists(LayoutMetadata metadata, int colIdx, String propkey, Properties props) {
125        final ColumnRepr column = metadata.getColumns().get(colIdx);
126        final Map<String, MemberGroupRepr> memberGroups = column.memberGroups;
127        final String val = memberGroups != null ? Joiner.on(",").join(memberGroups.keySet()) : "";
128        props.setProperty("class.memberGroupLayout." + propkey, val);
129    }
130
131    private static void setProperties(LayoutMetadata metadata, Properties props, int[] memberSeq) {
132        final List<ColumnRepr> columns = metadata.getColumns();
133        for (final ColumnRepr columnRepr : columns) {
134            final Map<String, MemberGroupRepr> memberGroups = columnRepr.memberGroups;
135            
136            if(memberGroups == null) {
137                continue;
138            }
139            
140            for (final String memberGroupName : memberGroups.keySet()) {
141                final MemberGroupRepr memberGroup = memberGroups.get(memberGroupName);
142                final Map<String, MemberRepr> members = memberGroup.members;
143                
144                if(members == null) {
145                    continue;
146                }
147                setMembersAndAssociatedActions(props, memberGroupName, members, memberSeq);
148            }
149        }
150    }
151
152    private static void setCollections(LayoutMetadata metadata, Properties props, int[] memberSeq) {
153        final ColumnRepr columnRepr = metadata.getColumns().get(3);
154        final Map<String, MemberRepr> collections = columnRepr.collections;
155        setMembersAndAssociatedActions(props, null, collections, memberSeq);
156    }
157
158    private static void setMembersAndAssociatedActions(Properties props, final String memberGroupName, final Map<String, MemberRepr> members, int[] memberSeq) {
159        for(final String memberName: members.keySet()) {
160            props.setProperty("member." + memberName + ".memberOrder.sequence", ""+ ++memberSeq[0]);
161            if(memberGroupName != null) {
162                props.setProperty("member." + memberName + ".memberOrder.name", memberGroupName);
163            }
164            
165            final MemberRepr memberRepr = members.get(memberName);
166
167            final NamedFacetRepr named = memberRepr.named;
168            if(named != null) {
169                props.setProperty("member." + memberName + ".named.value", named.value);
170            }
171            final DescribedAsFacetRepr describedAs = memberRepr.describedAs;
172            if(describedAs!= null) {
173                props.setProperty("member." + memberName + ".describedAs.value", describedAs.value);
174            }
175            final CssClassFacetRepr cssClass = memberRepr.cssClass;
176            if(cssClass!= null) {
177                props.setProperty("member." + memberName + ".cssClass.value", cssClass.value);
178            }
179            final TypicalLengthFacetRepr typicalLength = memberRepr.typicalLength;
180            if(typicalLength!= null) {
181                props.setProperty("member." + memberName + ".typicalLength.value", ""+typicalLength.value);
182            }
183            final MultiLineFacetRepr multiLine = memberRepr.multiLine;
184            if(multiLine!= null) {
185                props.setProperty("member." + memberName + ".multiLine.numberOfLines", ""+multiLine.numberOfLines);
186            }
187            final PagedFacetRepr paged = memberRepr.paged;
188            if(paged != null) {
189                props.setProperty("member." + memberName + ".paged.value", ""+paged.value);
190            }
191            final DisabledFacetRepr disabled = memberRepr.disabled;
192            if(disabled != null) {
193                // same default as in Disabled.when()
194                final When disabledWhen = disabled.when!=null?disabled.when: When.ALWAYS;
195                props.setProperty("member." + memberName + ".disabled.when", disabledWhen.toString());
196                // same default as in Disabled.where()
197                final Where disabledWhere = disabled.where!=null?disabled.where: Where.ANYWHERE;
198                props.setProperty("member." + memberName + ".disabled.where", disabledWhere.toString());
199                // same default as in Disabled.reason()
200                final String disabledReason = disabled.reason!=null?disabled.reason: "";
201                props.setProperty("member." + memberName + ".disabled.reason", disabledReason);
202            }
203            final HiddenFacetRepr hidden = memberRepr.hidden;
204            if(hidden != null) {
205                // same default as in Hidden.when()
206                final When hiddenWhen = hidden.when!=null?hidden.when: When.ALWAYS;
207                props.setProperty("member." + memberName + ".hidden.when", hiddenWhen.toString());
208                // same default as in Hidden.where()
209                final Where hiddenWhere = hidden.where!=null?hidden.where: Where.ANYWHERE;
210                props.setProperty("member." + memberName + ".hidden.where", hiddenWhere.toString());
211            }
212            final RenderFacetRepr render = memberRepr.render;
213            if(render != null) {
214                // same default as in Render.Type.value()
215                final Type renderType = render.value!=null?render.value: Render.Type.EAGERLY;
216                props.setProperty("member." + memberName + ".render.value", renderType.toString());
217            }
218            
219            final Map<String, ActionRepr> actions = memberRepr.actions;
220            if(actions != null) {
221                int actSeq = 0;
222                for(final String actionName: actions.keySet()) {
223                    final ActionRepr actionRepr = actions.get(actionName);
224                    String nameKey = "action." + actionName + ".memberOrder.name";
225                    props.setProperty(nameKey, memberName);
226                    setRemainingActionProperties(props, "action", actionName, actionRepr, ++actSeq);
227                }
228            }
229        }
230    }
231
232    private static void setFreestandingActions(LayoutMetadata metadata, Properties props) {
233        if(metadata.getActions() == null) {
234            return;
235        }
236        int xeq=0;
237        final Map<String, ActionRepr> actions = metadata.getActions();
238        for (final String actionName : actions.keySet()) {
239            final ActionRepr actionRepr = actions.get(actionName);
240            setRemainingActionProperties(props, "member", actionName, actionRepr, ++xeq);
241        }
242    }
243
244    private static void setRemainingActionProperties(Properties props, String prefix, final String actionNameOrig, final ActionRepr actionRepr, final int seq) {
245        final String actionName = actionNameOrig + ("action".equals(prefix)?"":"()");
246        props.setProperty(prefix + "." + actionName + ".memberOrder.sequence", ""+ seq);
247        
248        final NamedFacetRepr actionNamed = actionRepr.named;
249        if(actionNamed != null) {
250            props.setProperty(prefix +"." + actionName + ".named.value", actionNamed.value);
251        }
252        final DescribedAsFacetRepr describedAs = actionRepr.describedAs;
253        if(describedAs!= null) {
254            props.setProperty(prefix +"." + actionName + ".describedAs.value", describedAs.value);
255        }
256        final CssClassFacetRepr cssClass = actionRepr.cssClass;
257        if(cssClass!= null) {
258            props.setProperty(prefix +"." + actionName + ".cssClass.value", cssClass.value);
259        }
260    }
261
262    public LayoutMetadata asLayoutMetadata(Class<?> domainClass) throws ReaderException {
263        try {
264            return readMetadata(domainClass);
265        } catch (IOException e) {
266            throw new ReaderException(e);
267        } catch (RuntimeException e) {
268            throw new ReaderException(e);
269        }
270    }
271
272    // //////////////////////////////////////
273
274    private LayoutMetadata readMetadata(Class<?> domainClass) throws IOException {
275        final String content = ClassExtensions.resourceContent(domainClass, ".layout.json");
276        return readMetadata(content);
277    }
278
279    LayoutMetadata readMetadata(final String content) {
280        final Gson gson = new GsonBuilder().create();
281        return gson.fromJson(content, LayoutMetadata.class);
282    }
283
284    // //////////////////////////////////////
285
286    private final static MemberOrderFacetComparator memberOrderFacetComparator = new MemberOrderFacetComparator(false);
287
288    /**
289     * not API
290     */
291    public String asJson(ObjectSpecification objectSpec) {
292        final LayoutMetadata metadata = new LayoutMetadata();
293        metadata.setColumns(Lists.<ColumnRepr>newArrayList());
294        
295        final MemberGroupLayoutFacet mglf = objectSpec.getFacet(MemberGroupLayoutFacet.class);
296        final ColumnSpans columnSpans = mglf.getColumnSpans();
297        
298        Set<String> actionIdsForAssociations = Sets.newTreeSet();
299        
300        ColumnRepr columnRepr;
301        
302        columnRepr = addColumnWithSpan(metadata, columnSpans.getLeft());
303        updateColumnMemberGroups(objectSpec, MemberGroupLayoutHint.LEFT, columnRepr, actionIdsForAssociations);
304        
305        columnRepr = addColumnWithSpan(metadata, columnSpans.getMiddle());
306        updateColumnMemberGroups(objectSpec, MemberGroupLayoutHint.MIDDLE, columnRepr, actionIdsForAssociations);
307        
308        columnRepr = addColumnWithSpan(metadata, columnSpans.getRight());
309        updateColumnMemberGroups(objectSpec, MemberGroupLayoutHint.RIGHT, columnRepr, actionIdsForAssociations);
310        
311        columnRepr = addColumnWithSpan(metadata, columnSpans.getCollections());
312        updateCollectionColumnRepr(objectSpec, columnRepr, actionIdsForAssociations);
313
314        addActions(objectSpec, metadata, actionIdsForAssociations);
315        
316        final Gson gson = new GsonBuilder().setPrettyPrinting().create();
317        return gson.toJson(metadata);
318    }
319
320    private static void updateColumnMemberGroups(ObjectSpecification objectSpec, final MemberGroupLayoutHint hint, ColumnRepr columnRepr, Set<String> actionIdsForAssociations) {
321        final List<ObjectAssociation> objectAssociations = propertiesOf(objectSpec);
322        final Map<String, List<ObjectAssociation>> associationsByGroup = ObjectAssociation.Util.groupByMemberOrderName(objectAssociations);
323        
324        final List<String> groupNames = ObjectSpecifications.orderByMemberGroups(objectSpec, associationsByGroup.keySet(), hint);
325        
326        columnRepr.memberGroups = Maps.newLinkedHashMap();
327        for (String groupName : groupNames) {
328            final MemberGroupRepr memberGroupRepr = new MemberGroupRepr();
329            columnRepr.memberGroups.put(groupName, memberGroupRepr);
330            final List<ObjectAssociation> associationsInGroup = associationsByGroup.get(groupName);
331            memberGroupRepr.members = Maps.newLinkedHashMap();
332            if(associationsInGroup == null) {
333                continue;
334            }
335            for (ObjectAssociation assoc : associationsInGroup) {
336                final MemberRepr memberRepr = newMemberRepr(objectSpec, assoc, actionIdsForAssociations);
337                memberGroupRepr.members.put(assoc.getId(), memberRepr);
338            }
339        }
340    }
341    private static void addActions(ObjectSpecification objectSpec, final LayoutMetadata metadata, Set<String> actionIdsForAssociations) {
342        Map<String, ActionRepr> actions = Maps.newLinkedHashMap();
343        final List<ObjectAction> actionsOf = actionsOf(objectSpec, actionIdsForAssociations);
344        for(ObjectAction action: actionsOf) {
345            actions.put(action.getId(), newActionRepr(objectSpec, action));
346        }
347        metadata.setActions(actions);
348    }
349
350    private static ActionRepr newActionRepr(ObjectSpecification objectSpec, ObjectAction action) {
351        ActionRepr actionRepr = new ActionRepr();
352        
353        CssClassFacet cssClassFacet = action.getFacet(CssClassFacet.class);
354        if(cssClassFacet != null && !cssClassFacet.isNoop()) {
355            CssClassFacetRepr cssClassFacetRepr = new CssClassFacetRepr();
356            cssClassFacetRepr.value = cssClassFacet.value();
357            actionRepr.cssClass = cssClassFacetRepr;
358        }
359        DescribedAsFacet describedAsFacet = action.getFacet(DescribedAsFacet.class);
360        if(describedAsFacet != null && !describedAsFacet.isNoop() && !Strings.isNullOrEmpty(describedAsFacet.value())) {
361            DescribedAsFacetRepr describedAsFacetRepr = new DescribedAsFacetRepr();
362            describedAsFacetRepr.value = describedAsFacet.value();
363            actionRepr.describedAs = describedAsFacetRepr;
364        }
365        NamedFacet namedFacet = action.getFacet(NamedFacet.class);
366        if(namedFacet != null && !namedFacet.isNoop()) {
367            NamedFacetRepr namedFacetRepr = new NamedFacetRepr();
368            namedFacetRepr.value = namedFacet.value();
369            actionRepr.named = namedFacetRepr;
370        }
371        
372        return actionRepr;
373    }
374
375    private static void updateCollectionColumnRepr(ObjectSpecification objectSpec, ColumnRepr columnRepr, Set<String> actionIdsOfAssociations) {
376        final List<ObjectAssociation> objectAssociations = collectionsOf(objectSpec);
377        columnRepr.collections = Maps.newLinkedHashMap();
378        for(ObjectAssociation assoc: objectAssociations) {
379            final MemberRepr memberRepr = newMemberRepr(objectSpec, assoc, actionIdsOfAssociations);
380            columnRepr.collections.put(assoc.getId(), memberRepr);
381        }
382    }
383
384
385    private static MemberRepr newMemberRepr(ObjectSpecification objectSpec, ObjectAssociation assoc, Set<String> actionIdsForAssociations) {
386        final MemberRepr memberRepr = new MemberRepr();
387
388        CssClassFacet cssClassFacet = assoc.getFacet(CssClassFacet.class);
389        if(cssClassFacet != null && !cssClassFacet.isNoop()) {
390            CssClassFacetRepr cssClassFacetRepr = new CssClassFacetRepr();
391            cssClassFacetRepr.value = cssClassFacet.value();
392            memberRepr.cssClass = cssClassFacetRepr;
393        }
394        DescribedAsFacet describedAsFacet = assoc.getFacet(DescribedAsFacet.class);
395        if(describedAsFacet != null && !describedAsFacet.isNoop() && !Strings.isNullOrEmpty(describedAsFacet.value())) {
396            DescribedAsFacetRepr describedAsFacetRepr = new DescribedAsFacetRepr();
397            describedAsFacetRepr.value = describedAsFacet.value();
398            memberRepr.describedAs = describedAsFacetRepr;
399        }
400        NamedFacet namedFacet = assoc.getFacet(NamedFacet.class);
401        if(namedFacet != null && !namedFacet.isNoop()) {
402            NamedFacetRepr namedFacetRepr = new NamedFacetRepr();
403            namedFacetRepr.value = namedFacet.value();
404            memberRepr.named = namedFacetRepr;
405        }
406        DisabledFacet disabledFacet = assoc.getFacet(DisabledFacet.class);
407        if(disabledFacet != null && !disabledFacet.isNoop()) {
408            DisabledFacetRepr disabledFacetRepr = new DisabledFacetRepr();
409            if(disabledFacet instanceof DisabledFacetImpl) {
410                DisabledFacetImpl disabledFacetImpl = (DisabledFacetImpl) disabledFacet;
411                disabledFacetRepr.reason = Strings.emptyToNull(disabledFacetImpl.getReason());
412            }
413            disabledFacetRepr.when = whenAlwaysToNull(disabledFacet.when());
414            disabledFacetRepr.where = whereAnywhereToNull(disabledFacet.where());
415            memberRepr.disabled = disabledFacetRepr;
416        }
417        HiddenFacet hiddenFacet = assoc.getFacet(HiddenFacet.class);
418        if(hiddenFacet != null && !hiddenFacet.isNoop()) {
419            HiddenFacetRepr hiddenFacetRepr = new HiddenFacetRepr();
420            hiddenFacetRepr.when = whenAlwaysToNull(hiddenFacet.when());
421            hiddenFacetRepr.where = whereAnywhereToNull(hiddenFacet.where());
422            memberRepr.hidden = hiddenFacetRepr;
423        }
424        MultiLineFacet multiLineFacet = assoc.getFacet(MultiLineFacet.class);
425        if(multiLineFacet != null && !multiLineFacet.isNoop()) {
426            MultiLineFacetRepr multiLineFacetRepr = new MultiLineFacetRepr();
427            multiLineFacetRepr.numberOfLines = multiLineFacet.numberOfLines();
428            memberRepr.multiLine = multiLineFacetRepr;
429        }
430        PagedFacet pagedFacet = assoc.getFacet(PagedFacet.class);
431        if(pagedFacet != null && !pagedFacet.isNoop()) {
432            PagedFacetRepr pagedFacetRepr = new PagedFacetRepr();
433            pagedFacetRepr.value = pagedFacet.value();
434            memberRepr.paged = pagedFacetRepr;
435        }
436        RenderFacet renderFacet = assoc.getFacet(RenderFacet.class);
437        if(renderFacet != null && !renderFacet.isNoop()) {
438            RenderFacetRepr renderFacetRepr = new RenderFacetRepr();
439            renderFacetRepr.value = renderFacet.value();
440            memberRepr.render = renderFacetRepr;
441        }
442        TypicalLengthFacet typicalLengthFacet = assoc.getFacet(TypicalLengthFacet.class);
443        if(typicalLengthFacet != null && !typicalLengthFacet.isNoop()) {
444            TypicalLengthFacetRepr typicalLengthFacetRepr = new TypicalLengthFacetRepr();
445            typicalLengthFacetRepr.value = typicalLengthFacet.value();
446            memberRepr.typicalLength = typicalLengthFacetRepr;
447        }
448
449        final List<ObjectAction> actions = objectSpec.getObjectActions(
450                ActionType.USER, Contributed.INCLUDED, ObjectAction.Filters.memberOrderOf(assoc));
451        if(!actions.isEmpty()) {
452            memberRepr.actions = Maps.newLinkedHashMap();
453            
454            sortByMemberOrderFacet(actions);
455            
456            for (final ObjectAction action : actions) {
457                final String actionId = action.getId();
458                memberRepr.actions.put(actionId, new ActionRepr());
459                actionIdsForAssociations.add(actionId);
460            }
461        }
462        return memberRepr;
463    }
464
465    private static Where whereAnywhereToNull(Where where) {
466        return where != Where.ANYWHERE? where: null;
467    }
468
469    private static When whenAlwaysToNull(When when) {
470        return when != When.ALWAYS? when: null;
471    }
472
473    private static void sortByMemberOrderFacet(final List<ObjectAction> actions) {
474        Collections.sort(actions, new Comparator<ObjectAction>() {
475
476            @Override
477            public int compare(ObjectAction o1, ObjectAction o2) {
478                final MemberOrderFacet m1 = o1.getFacet(MemberOrderFacet.class);
479                final MemberOrderFacet m2 = o2.getFacet(MemberOrderFacet.class);
480                return memberOrderFacetComparator.compare(m1, m2);
481            }});
482    }
483
484    private static ColumnRepr addColumnWithSpan(final LayoutMetadata metadata, final int span) {
485        final ColumnRepr col = new ColumnRepr();
486        metadata.getColumns().add(col);
487        col.span = span;
488        return col;
489    }
490
491    
492    private static List<ObjectAssociation> propertiesOf(final ObjectSpecification objSpec) {
493        return objSpec.getAssociations(Contributed.EXCLUDED, ObjectAssociation.Filters.PROPERTIES);
494    }
495    private static List<ObjectAssociation> collectionsOf(final ObjectSpecification objSpec) {
496        return objSpec.getAssociations(Contributed.EXCLUDED, ObjectAssociation.Filters.COLLECTIONS);
497    }
498    private static List<ObjectAction> actionsOf(final ObjectSpecification objSpec, final Set<String> excludedActionIds) {
499        return objSpec.getObjectActions(ActionType.ALL, Contributed.INCLUDED, excluding(excludedActionIds));
500    }
501
502    @SuppressWarnings({ "deprecation" })
503    private static Filter<ObjectAction> excluding(final Set<String> excludedActionIds) {
504        return new Filter<ObjectAction>(){
505                    @Override
506                    public boolean accept(ObjectAction t) {
507                        return !excludedActionIds.contains(t.getId());
508                    }
509                };
510    }
511
512    @Override
513    public String toString() {
514        return getClass().getName();
515    }
516
517}