001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019
020package org.apache.isis.core.metamodel.util;
021
022import java.util.List;
023
024import com.google.common.collect.Lists;
025
026import org.apache.isis.applib.annotation.Where;
027import org.apache.isis.applib.filter.Filters;
028import org.apache.isis.core.commons.authentication.AuthenticationSession;
029import org.apache.isis.core.commons.debug.DebugBuilder;
030import org.apache.isis.core.commons.debug.DebugString;
031import org.apache.isis.core.commons.debug.DebugUtils;
032import org.apache.isis.core.commons.debug.DebuggableWithTitle;
033import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
034import org.apache.isis.core.metamodel.facetapi.Facet;
035import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
036import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacetUtils;
037import org.apache.isis.core.metamodel.facets.object.bounded.ChoicesFacetUtils;
038import org.apache.isis.core.metamodel.facets.object.cached.CachedFacetUtils;
039import org.apache.isis.core.metamodel.facets.object.immutable.ImmutableFacetUtils;
040import org.apache.isis.core.metamodel.spec.ActionType;
041import org.apache.isis.core.metamodel.spec.ObjectSpecification;
042import org.apache.isis.core.metamodel.spec.feature.Contributed;
043import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
044import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter;
045import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
046
047public final class Dump {
048
049    private static DebugBuilder debugBuilder;
050
051    // REVIEW: should provide this rendering context, rather than hardcoding.
052    // the net effect currently is that class members annotated with 
053    // @Hidden(where=Where.ANYWHERE) or @Disabled(where=Where.ANYWHERE) will indeed
054    // be hidden/disabled, but will be visible/enabled (perhaps incorrectly) 
055    // for any other value for Where
056    private final static Where where = Where.ANYWHERE;
057
058    private Dump() {
059    }
060
061    // /////////////////////////////////////////////////////////////////////
062    // specification
063    // /////////////////////////////////////////////////////////////////////
064
065    /**
066     * @see #specification(ObjectAdapter, DebugBuilder)
067     * @see #specification(ObjectSpecification, DebugBuilder)
068     */
069    public static String specification(final ObjectAdapter adapter) {
070        final DebugBuilder debugBuilder = new DebugString();
071        specification(adapter, debugBuilder);
072        return debugBuilder.toString();
073    }
074
075    /**
076     * Convenience overload of
077     * {@link #specification(ObjectSpecification, DebugBuilder)} that takes the
078     * {@link ObjectSpecification} ( {@link ObjectAdapter#getSpecification()})
079     * of the provided {@link ObjectAdapter}
080     * 
081     * @see #specification(ObjectAdapter)
082     * @see #specification(ObjectSpecification, DebugBuilder)
083     */
084    public static void specification(final ObjectAdapter adapter, final DebugBuilder debugBuilder) {
085        final ObjectSpecification specification = adapter.getSpecification();
086        specification(specification, debugBuilder);
087    }
088
089    public static void specification(final ObjectSpecification specification, final DebugBuilder debugBuilder) {
090        try {
091            debugBuilder.appendTitle(specification.getClass().getName());
092            debugBuilder.appendAsHexln("Hash code", specification.hashCode());
093            debugBuilder.appendln("ID", specification.getIdentifier());
094            debugBuilder.appendln("Full Name", specification.getFullIdentifier());
095            debugBuilder.appendln("Short Name", specification.getShortIdentifier());
096            debugBuilder.appendln("Singular Name", specification.getSingularName());
097            debugBuilder.appendln("Plural Name", specification.getPluralName());
098            debugBuilder.appendln("Description", specification.getDescription());
099            debugBuilder.blankLine();
100            debugBuilder.appendln("Features", featureList(specification));
101            debugBuilder.appendln("Type", specification.isParentedOrFreeCollection() ? "Collection" : "Object");
102            if (specification.superclass() != null) {
103                debugBuilder.appendln("Superclass", specification.superclass().getFullIdentifier());
104            }
105            debugBuilder.appendln("Interfaces", specificationNames(specification.interfaces()));
106            debugBuilder.appendln("Subclasses", specificationNames(specification.subclasses()));
107            debugBuilder.blankLine();
108            debugBuilder.appendln("Service", specification.isService());
109            debugBuilder.appendln("Encodable", specification.isEncodeable());
110            debugBuilder.appendln("Parseable", specification.isParseable());
111            debugBuilder.appendln("Aggregated", specification.isValueOrIsParented());
112        } catch (final RuntimeException e) {
113            debugBuilder.appendException(e);
114        }
115
116        if (specification instanceof DebuggableWithTitle) {
117            ((DebuggableWithTitle) specification).debugData(debugBuilder);
118        }
119
120        debugBuilder.blankLine();
121
122        debugBuilder.appendln("Facets");
123        final Class<? extends Facet>[] facetTypes = specification.getFacetTypes();
124        debugBuilder.indent();
125        if (facetTypes.length == 0) {
126            debugBuilder.appendln("none");
127        } else {
128            for (final Class<? extends Facet> type : facetTypes) {
129                final Facet facet = specification.getFacet(type);
130                debugBuilder.appendln(facet.toString());
131            }
132        }
133        debugBuilder.unindent();
134        debugBuilder.blankLine();
135
136        debugBuilder.appendln("Fields");
137        debugBuilder.indent();
138        specificationFields(specification, debugBuilder);
139        debugBuilder.unindent();
140
141        debugBuilder.appendln("Object Actions");
142        debugBuilder.indent();
143        specificationActionMethods(specification, debugBuilder);
144        debugBuilder.unindent();
145    }
146
147    private static String[] specificationNames(final List<ObjectSpecification> specifications) {
148        final String[] names = new String[specifications.size()];
149        for (int i = 0; i < names.length; i++) {
150            names[i] = specifications.get(i).getFullIdentifier();
151        }
152        return names;
153    }
154
155    private static void specificationActionMethods(final ObjectSpecification specification, final DebugBuilder debugBuilder) {
156        try {
157            final List<ObjectAction> userActions = specification.getObjectActions(ActionType.USER, Contributed.INCLUDED, Filters.<ObjectAction>any());
158            final List<ObjectAction> explActions = specification.getObjectActions(ActionType.EXPLORATION, Contributed.INCLUDED, Filters.<ObjectAction>any());
159            final List<ObjectAction> prototypeActions = specification.getObjectActions(ActionType.PROTOTYPE, Contributed.INCLUDED, Filters.<ObjectAction>any());
160            final List<ObjectAction> debActions = specification.getObjectActions(ActionType.DEBUG, Contributed.INCLUDED, Filters.<ObjectAction>any());
161            specificationMethods(userActions, explActions, prototypeActions, debActions, debugBuilder);
162        } catch (final RuntimeException e) {
163            debugBuilder.appendException(e);
164        }
165    }
166
167    private static void specificationFields(final ObjectSpecification specification, final DebugBuilder debugBuilder) {
168        final List<ObjectAssociation> fields = specification.getAssociations(Contributed.EXCLUDED);
169        debugBuilder.appendln("All");
170        debugBuilder.indent();
171        for (int i = 0; i < fields.size(); i++) {
172            debugBuilder.appendln((i + 1) + "." + fields.get(i).getId());
173        }
174        debugBuilder.unindent();
175
176        final List<ObjectAssociation> fields2 = specification.getAssociations(Contributed.EXCLUDED, ObjectAssociation.Filters.VISIBLE_AT_LEAST_SOMETIMES);
177        debugBuilder.appendln("Static");
178        debugBuilder.indent();
179        for (int i = 0; i < fields2.size(); i++) {
180            debugBuilder.appendln((i + 1) + "." + fields2.get(i).getId());
181        }
182        debugBuilder.unindent();
183        debugBuilder.appendln();
184
185        try {
186            if (fields.size() == 0) {
187                debugBuilder.appendln("none");
188            } else {
189                for (int i = 0; i < fields.size(); i++) {
190
191                    final ObjectAssociation field = fields.get(i);
192                    debugBuilder.appendln((i + 1) + "." + field.getId() + "  (" + field.getClass().getName() + ")");
193
194                    debugBuilder.indent();
195                    final String description = field.getDescription();
196                    if (description != null && !description.equals("")) {
197                        debugBuilder.appendln("Description", description);
198                    }
199                    final String help = field.getHelp();
200                    if (help != null && !help.equals("")) {
201                        debugBuilder.appendln("Help", help.substring(0, Math.min(30, help.length())) + (help.length() > 30 ? "..." : ""));
202                    }
203
204                    
205                    debugBuilder.appendln("ID", field.getIdentifier());
206                    debugBuilder.appendln("Short ID", field.getId());
207                    debugBuilder.appendln("Name", field.getName());
208                    final String type = field.isOneToManyAssociation() ? "Collection" : field.isOneToOneAssociation() ? "Object" : "Unknown";
209                    debugBuilder.appendln("Type", type);
210                    final ObjectSpecification fieldSpec = field.getSpecification();
211                    final boolean hasIdentity = !(fieldSpec.isParentedOrFreeCollection() || fieldSpec.isParented() || fieldSpec.isValue());
212                    debugBuilder.appendln("Has identity", hasIdentity);
213                    debugBuilder.appendln("Spec", fieldSpec.getFullIdentifier());
214
215                    debugBuilder.appendln("Flags", (field.isAlwaysHidden() ? "" : "Visible ") + (field.isNotPersisted() ? "Not Persisted " : " ") + (field.isMandatory() ? "Mandatory " : ""));
216
217                    final Class<? extends Facet>[] facets = field.getFacetTypes();
218                    if (facets.length > 0) {
219                        debugBuilder.appendln("Facets");
220                        debugBuilder.indent();
221                        boolean none = true;
222                        for (final Class<? extends Facet> facet : facets) {
223                            debugBuilder.appendln(field.getFacet(facet).toString());
224                            none = false;
225                        }
226                        if (none) {
227                            debugBuilder.appendln("none");
228                        }
229                        debugBuilder.unindent();
230                    }
231
232                    debugBuilder.appendln(field.debugData());
233
234                    debugBuilder.unindent();
235                }
236            }
237        } catch (final RuntimeException e) {
238            debugBuilder.appendException(e);
239        }
240
241    }
242
243    private static void specificationMethods(final List<ObjectAction> userActions, final List<ObjectAction> explActions, final List<ObjectAction> prototypeActions, final List<ObjectAction> debugActions, final DebugBuilder debugBuilder) {
244        if (userActions.size() == 0 && explActions.size() == 0 && prototypeActions.size() == 0 && debugActions.size() == 0) {
245            debugBuilder.appendln("no actions...");
246        } else {
247            appendActionDetails(debugBuilder, "User actions", userActions);
248            appendActionDetails(debugBuilder, "Exploration actions", explActions);
249            appendActionDetails(debugBuilder, "Prototype actions", prototypeActions);
250            appendActionDetails(debugBuilder, "Debug actions", debugActions);
251        }
252    }
253
254    private static void appendActionDetails(final DebugBuilder debug, final String desc, final List<ObjectAction> actions) {
255        debug.appendln(desc);
256        debug.indent();
257        for (int i = 0; i < actions.size(); i++) {
258            actionDetails(actions.get(i), 8, i, debug);
259        }
260        debug.unindent();
261    }
262
263    private static void actionDetails(final ObjectAction objectAction, final int indent, final int count, final DebugBuilder debugBuilder) {
264        debugBuilder.appendln((count + 1) + "." + objectAction.getId() + " (" + objectAction.getClass().getName() + ")");
265        debugBuilder.indent();
266        try {
267            if (objectAction.getDescription() != null && !objectAction.getDescription().equals("")) {
268                debugBuilder.appendln("Description", objectAction.getDescription());
269            }
270            debugBuilder.appendln("ID", objectAction.getId());
271
272            debugBuilder.appendln(objectAction.debugData());
273            debugBuilder.appendln("On type", objectAction.getOnType());
274
275            final Class<? extends Facet>[] facets = objectAction.getFacetTypes();
276            if (facets.length > 0) {
277                debugBuilder.appendln("Facets");
278                debugBuilder.indent();
279                for (final Class<? extends Facet> facet : facets) {
280                    debugBuilder.appendln(objectAction.getFacet(facet).toString());
281                }
282                debugBuilder.unindent();
283            }
284
285            final ObjectSpecification returnType = objectAction.getReturnType();
286            debugBuilder.appendln("Returns", returnType == null ? "VOID" : returnType.toString());
287
288            final List<ObjectActionParameter> parameters = objectAction.getParameters();
289            if (parameters.size() == 0) {
290                debugBuilder.appendln("Parameters", "none");
291            } else {
292                debugBuilder.appendln("Parameters");
293                debugBuilder.indent();
294                final List<ObjectActionParameter> p = objectAction.getParameters();
295                for (int j = 0; j < parameters.size(); j++) {
296                    debugBuilder.append(p.get(j).getName());
297                    debugBuilder.append(" (");
298                    debugBuilder.append(parameters.get(j).getSpecification().getFullIdentifier());
299                    debugBuilder.appendln(")");
300                    debugBuilder.indent();
301                    final Class<? extends Facet>[] parameterFacets = p.get(j).getFacetTypes();
302                    for (final Class<? extends Facet> parameterFacet : parameterFacets) {
303                        debugBuilder.appendln(p.get(j).getFacet(parameterFacet).toString());
304                    }
305                    debugBuilder.unindent();
306                }
307                debugBuilder.unindent();
308            }
309        } catch (final RuntimeException e) {
310            debugBuilder.appendException(e);
311        }
312
313        debugBuilder.unindent();
314    }
315
316    private static String featureList(final ObjectSpecification specification) {
317        final StringBuilder str = new StringBuilder();
318        if (specification.isAbstract()) {
319            str.append("Abstract ");
320        }
321        if (ChoicesFacetUtils.hasChoices(specification)) {
322            str.append("WithChoices ");
323        }
324        if (CachedFacetUtils.isCached(specification)) {
325            str.append("Cached ");
326        }
327        if (ImmutableFacetUtils.isAlwaysImmutable(specification)) {
328            str.append("Immutable (always) ");
329        }
330        if (ImmutableFacetUtils.isImmutableOncePersisted(specification)) {
331            str.append("Immutable (once persisted) ");
332        }
333        if (specification.isService()) {
334            str.append("Service ");
335        }
336        return str.toString();
337    }
338
339    // /////////////////////////////////////////////////////////////////////
340    // adapter
341    // /////////////////////////////////////////////////////////////////////
342
343    /**
344     * @see #adapter(ObjectAdapter, DebugBuilder)
345     */
346    public static String adapter(final ObjectAdapter adapter) {
347        final DebugBuilder debugBuilder = new DebugString();
348        adapter(adapter, debugBuilder);
349        return debugBuilder.toString();
350    }
351
352    /**
353     * @see #adapter(ObjectAdapter)
354     */
355    public static void adapter(final ObjectAdapter adapter, final DebugBuilder builder) {
356        try {
357            builder.appendln("Adapter", adapter.getClass().getName());
358            builder.appendln("Class", adapter.getObject() == null ? "none" : adapter.getObject().getClass().getName());
359            builder.appendAsHexln("Hash", adapter.hashCode());
360            builder.appendln("Object", adapter.getObject());
361            builder.appendln("Title", adapter.titleString());
362            builder.appendln("Specification", adapter.getSpecification().getFullIdentifier());
363
364            builder.appendln();
365
366            builder.appendln("Icon", adapter.getIconName());
367            builder.appendln("OID", adapter.getOid());
368            builder.appendln("State", adapter.getResolveState());
369            builder.appendln("Version", adapter.getVersion());
370
371        } catch (final RuntimeException e) {
372            builder.appendException(e);
373        }
374
375    }
376
377    // /////////////////////////////////////////////////////////////////////
378    // graph
379    // /////////////////////////////////////////////////////////////////////
380
381    /**
382     * Creates an ascii object graph diagram for the specified object, up to
383     * three levels deep.
384     * 
385     * @see #graph(ObjectAdapter, AuthenticationSession, DebugBuilder)
386     */
387    public static String graph(final ObjectAdapter adapter, final AuthenticationSession authenticationSession) {
388        debugBuilder = new DebugString();
389        graph(adapter, authenticationSession, debugBuilder);
390        return debugBuilder.toString();
391    }
392
393    /**
394     * As {@link #graph(ObjectAdapter, AuthenticationSession)}, but appending to
395     * the provided {@link DebugBuilder}.
396     * 
397     * @see #graph(ObjectAdapter, AuthenticationSession)
398     */
399    public static void graph(final ObjectAdapter object, final AuthenticationSession authenticationSession, final DebugBuilder debugBuilder) {
400        simpleObject(object, debugBuilder);
401        debugBuilder.appendln();
402        debugBuilder.append(object);
403        graph(object, 0, Lists.<ObjectAdapter> newArrayList(), authenticationSession, debugBuilder);
404    }
405
406    private static void simpleObject(final ObjectAdapter collectionAdapter, final DebugBuilder debugBuilder) {
407        debugBuilder.appendln(collectionAdapter.titleString());
408        final ObjectSpecification objectSpec = collectionAdapter.getSpecification();
409        if (objectSpec.isParentedOrFreeCollection()) {
410            final CollectionFacet facet = CollectionFacetUtils.getCollectionFacetFromSpec(collectionAdapter);
411            int i = 1;
412            for (final ObjectAdapter element : facet.collection(collectionAdapter)) {
413                debugBuilder.appendln(i++ + " " + element.titleString());
414            }
415        } else {
416            // object is a regular Object
417            try {
418                final List<ObjectAssociation> fields = objectSpec.getAssociations(Contributed.EXCLUDED);
419                for (int i = 0; i < fields.size(); i++) {
420                    final ObjectAssociation field = fields.get(i);
421                    final ObjectAdapter obj = field.get(collectionAdapter);
422
423                    final String name = field.getId();
424                    if (obj == null) {
425                        debugBuilder.appendln(name, "null");
426                    } else {
427                        debugBuilder.appendln(name, obj.titleString());
428                    }
429                }
430            } catch (final RuntimeException e) {
431                debugBuilder.appendException(e);
432            }
433        }
434    }
435
436    private static void collectionGraph(final ObjectAdapter collectionAdapter, final int level, final List<ObjectAdapter> ignoreAdapters, final AuthenticationSession authenticationSession, final DebugBuilder debugBuilder) {
437
438        if (ignoreAdapters.contains(collectionAdapter)) {
439            debugBuilder.append("*\n");
440        } else {
441            ignoreAdapters.add(collectionAdapter);
442            final CollectionFacet facet = CollectionFacetUtils.getCollectionFacetFromSpec(collectionAdapter);
443            for (final ObjectAdapter element : facet.collection(collectionAdapter)) {
444                graphIndent(level, debugBuilder);
445                debugBuilder.append(element);
446                if (ignoreAdapters.contains(element)) {
447                    debugBuilder.append("*\n");
448                } else {
449                    graph(element, level + 1, ignoreAdapters, authenticationSession, debugBuilder);
450                }
451            }
452        }
453    }
454
455    private static void graph(final ObjectAdapter adapter, final int level, final List<ObjectAdapter> ignoreAdapters, final AuthenticationSession authenticationSession, final DebugBuilder debugBuilder) {
456        if (level > 3) {
457            debugBuilder.appendln("..."); // only go 3 levels?
458        } else {
459            debugBuilder.append("\n");
460            if (adapter.getSpecification().isParentedOrFreeCollection()) {
461                collectionGraph(adapter, level, ignoreAdapters, authenticationSession, debugBuilder);
462            } else if (adapter.getSpecification().isNotCollection()) {
463                objectGraph(adapter, level, ignoreAdapters, debugBuilder, authenticationSession);
464            } else {
465                debugBuilder.append("??? " + adapter);
466            }
467        }
468    }
469
470    private static void graphIndent(final int level, final DebugBuilder debugBuilder) {
471        for (int indent = 0; indent < level; indent++) {
472            debugBuilder.append(DebugUtils.indentString(4) + "|");
473        }
474        debugBuilder.append(DebugUtils.indentString(4) + "+--");
475    }
476
477    private static void objectGraph(final ObjectAdapter adapter, final int level, final List<ObjectAdapter> ignoreAdapters, final DebugBuilder s, final AuthenticationSession authenticationSession) {
478        ignoreAdapters.add(adapter);
479
480        try {
481            // work through all its fields
482            final List<ObjectAssociation> fields = adapter.getSpecification().getAssociations(Contributed.EXCLUDED);
483            for (int i = 0; i < fields.size(); i++) {
484                final ObjectAssociation field = fields.get(i);
485                final ObjectAdapter obj = field.get(adapter);
486                final String name = field.getId();
487                graphIndent(level, s);
488
489                if (field.isVisible(authenticationSession, adapter, where).isVetoed()) {
490                    s.append(name + ": (not visible)");
491                    s.append("\n");
492                } else {
493                    if (obj == null) {
494                        s.append(name + ": null\n");
495                        /*
496                         * } else if (obj.getSpecification().isParseable()) {
497                         * s.append(name + ": " + obj.titleString());
498                         * s.append("\n");
499                         */} else {
500                        if (ignoreAdapters.contains(obj)) {
501                            s.append(name + ": " + obj + "*\n");
502                        } else {
503                            s.append(name + ": " + obj);
504                            graph(obj, level + 1, ignoreAdapters, authenticationSession, s);
505
506                        }
507                    }
508                }
509            }
510        } catch (final RuntimeException e) {
511            s.appendException(e);
512        }
513    }
514
515}