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.facetdecorator;
021
022import java.text.MessageFormat;
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.HashMap;
026import java.util.LinkedHashSet;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import org.apache.isis.core.commons.components.ApplicationScopedComponent;
032import org.apache.isis.core.commons.debug.DebugBuilder;
033import org.apache.isis.core.metamodel.exceptions.MetaModelException;
034import org.apache.isis.core.metamodel.facetapi.Facet;
035import org.apache.isis.core.metamodel.facetapi.FacetHolder;
036import org.apache.isis.core.metamodel.spec.ObjectSpecification;
037import org.apache.isis.core.metamodel.spec.feature.Contributed;
038import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
039import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter;
040import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
041
042public class FacetDecoratorSet implements ApplicationScopedComponent {
043
044    private final Map<Class<? extends Facet>, List<FacetDecorator>> facetDecoratorByFacetType = new HashMap<Class<? extends Facet>, List<FacetDecorator>>();
045    private final Set<FacetDecorator> facetDecoratorSet = new LinkedHashSet<FacetDecorator>();
046
047    // ////////////////////////////////////////////////////////////
048    // init, shutdown
049    // ////////////////////////////////////////////////////////////
050
051    @Override
052    public void init() {
053    }
054
055    @Override
056    public void shutdown() {
057    }
058
059    // ////////////////////////////////////////////////////////////
060    // add, get, isEmpty
061    // ////////////////////////////////////////////////////////////
062
063    public void add(final FacetDecorator decorator) {
064        final Class<? extends Facet>[] decoratedFacetTypes = decorator.getFacetTypes();
065        for (final Class<? extends Facet> decoratedFacetType : decoratedFacetTypes) {
066            getFacetDecoratorList(decoratedFacetType).add(decorator);
067            facetDecoratorSet.add(decorator);
068        }
069    }
070
071    private List<FacetDecorator> getFacetDecoratorList(final Class<? extends Facet> decoratedFacetType) {
072        List<FacetDecorator> facetDecoratorList = facetDecoratorByFacetType.get(decoratedFacetType);
073        if (facetDecoratorList == null) {
074            facetDecoratorList = new ArrayList<FacetDecorator>();
075            facetDecoratorByFacetType.put(decoratedFacetType, facetDecoratorList);
076        }
077        return facetDecoratorList;
078    }
079
080    public void add(final List<FacetDecorator> decorators) {
081        for (final FacetDecorator decorator : decorators) {
082            add(decorator);
083        }
084    }
085
086    public Set<FacetDecorator> getFacetDecorators() {
087        return Collections.unmodifiableSet(facetDecoratorSet);
088    }
089
090    public boolean isEmpty() {
091        return facetDecoratorByFacetType.isEmpty();
092    }
093
094    // ////////////////////////////////////////////////////////////
095    // decorate
096    // ////////////////////////////////////////////////////////////
097
098    /**
099     * @param holder
100     */
101    public void decorate(final ObjectSpecification holder) {
102        decorateAllFacets(holder);
103        for (final ObjectAssociation objectAssociation : holder.getAssociations(Contributed.EXCLUDED)) {
104            this.decorateAllFacets(objectAssociation);
105        }
106        for (final ObjectAction objectAction : holder.getObjectActions(Contributed.EXCLUDED)) {
107            decorateAllFacets(objectAction);
108            final List<ObjectActionParameter> parameters = objectAction.getParameters();
109            for (final ObjectActionParameter parameter : parameters) {
110                this.decorateAllFacets(parameter);
111            }
112        }
113    }
114
115    private void decorateAllFacets(final FacetHolder holder) {
116        if (isEmpty()) {
117            return;
118        }
119        final Class<? extends Facet>[] facetTypes = holder.getFacetTypes();
120        for (final Class<? extends Facet> facetType : facetTypes) {
121            final Facet facet = holder.getFacet(facetType);
122            decorateFacet(facet, holder);
123        }
124    }
125
126    /**
127     * REVIEW: the design is a little clumsy here. We want to decorate the
128     * provided {@link Facet}, but its owning {@link FacetHolder holder} turns
129     * out to be a runtime peer (eg <tt>JavaAction</tt>) rather than the
130     * metamodel (eg {@link ObjectAction}). Since we want to decorate the
131     * {@link ObjectAction}, we have to pass it through.
132     */
133    private void decorateFacet(final Facet facet, final FacetHolder requiredHolder) {
134        final Class<? extends Facet> facetType = facet.facetType();
135        final Class<? extends Facet> cls = facetType;
136        final List<FacetDecorator> decoratorList = facetDecoratorByFacetType.get(cls);
137        if (decoratorList == null) {
138            return;
139        }
140        for (final FacetDecorator facetDecorator : decoratorList) {
141            final Facet decoratingFacet = facetDecorator.decorate(facet, requiredHolder);
142            if (decoratingFacet == null) {
143                continue;
144            }
145            ensureDecoratorMetContract(facetDecorator, decoratingFacet, facetType, requiredHolder);
146        }
147    }
148
149    private static void ensureDecoratorMetContract(final FacetDecorator facetDecorator, final Facet decoratingFacet, final Class<? extends Facet> facetType, final FacetHolder originalFacetHolder) {
150        if (decoratingFacet.facetType() != facetType) {
151            throw new MetaModelException(MessageFormat.format("Problem with facet decorator '{0}'; inconsistent decorating facetType() for {1}; was {2} but expectected facetType() of {3}", facetDecorator.getClass().getName(), decoratingFacet.getClass().getName(), decoratingFacet.facetType()
152                    .getName(), facetType.getName()));
153        }
154        final Facet facetForFacetType = originalFacetHolder.getFacet(decoratingFacet.facetType());
155        if (facetForFacetType != decoratingFacet) {
156            throw new MetaModelException(MessageFormat.format("Problem with facet decorator '{0}'; has not replaced original facet for facetType() of {1}", facetDecorator.getClass().getName(), facetType.getName()));
157        }
158    }
159
160    // ////////////////////////////////////////////////////////////
161    // debugging
162    // ////////////////////////////////////////////////////////////
163
164    public void debugData(final DebugBuilder str) {
165        str.appendTitle("Facet decorators");
166        final Set<Class<? extends Facet>> facetTypes = facetDecoratorByFacetType.keySet();
167        if (facetTypes.size() == 0) {
168            str.append("none");
169        } else {
170            for (final Class<? extends Facet> cls : facetTypes) {
171                str.appendln(cls.getName(), facetDecoratorByFacetType.get(cls));
172            }
173        }
174    }
175
176}