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.facets;
021
022import java.lang.reflect.Method;
023import java.util.List;
024
025import com.google.common.collect.Lists;
026
027import org.apache.isis.applib.DomainObjectContainer;
028import org.apache.isis.applib.filter.Filter;
029import org.apache.isis.applib.filter.Filters;
030import org.apache.isis.applib.services.wrapper.WrapperFactory;
031import org.apache.isis.core.commons.lang.ObjectExtensions;
032import org.apache.isis.core.metamodel.facetapi.DecoratingFacet;
033import org.apache.isis.core.metamodel.facetapi.Facet;
034import org.apache.isis.core.metamodel.facetapi.FacetHolder;
035import org.apache.isis.core.metamodel.spec.ObjectSpecification;
036import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
037
038/**
039 * A {@link Facet} implementation that ultimately wraps a {@link Method} or
040 * possibly several equivalent methods, for a Java implementation of a
041 * {@link ObjectMember}.
042 * 
043 * <p>
044 * Used by <tt>ObjectSpecificationDefault#getMember(Method)</tt> in order to
045 * reverse lookup {@link ObjectMember}s from underlying {@link Method}s. So, for
046 * example, the facets that represents an action xxx, or an <tt>validateXxx</tt>
047 * method, or an <tt>addToXxx</tt> collection, can all be used to lookup the
048 * member.
049 * 
050 * <p>
051 * Note that {@link Facet}s relating to the class itself (ie for
052 * {@link ObjectSpecification}) should not implement this interface.
053 */
054public interface ImperativeFacet {
055
056    /**
057     * The {@link Method}s invoked by this {@link Facet}.
058     * 
059     * <p>
060     * In the vast majority of cases there is only a single {@link Method} (eg
061     * wrapping a property's getter). However, some {@link Facet}s, such as
062     * those for callbacks, could map to multiple {@link Method}s.
063     * Implementations that will return multiple {@link Method}s should
064     * implement the {@link ImperativeFacetMulti} sub-interface that provides
065     * the ability to {@link ImperativeFacetMulti#addMethod(Method) add}
066     * {@link Method}s as part of the interface API. For example:
067     * 
068     * <pre>
069     * if (someFacet instanceof ImperativeFacetMulti) {
070     *     ImperativeFacetMulti ifm = (ImperativeFacetMulti)someFacet;
071     *     ifm.addMethod(...);
072     * }
073     * </pre>
074     */
075    public List<Method> getMethods();
076
077    public static enum Intent {
078        CHECK_IF_HIDDEN,
079        CHECK_IF_DISABLED,
080        CHECK_IF_VALID,
081        ACCESSOR,
082        EXECUTE,
083        MODIFY_PROPERTY,
084        /**
085         * Modify property using modify/clear rather than simply using set.
086         */
087        MODIFY_PROPERTY_SUPPORTING, 
088        MODIFY_COLLECTION_ADD,
089        MODIFY_COLLECTION_REMOVE,
090        CHOICES_OR_AUTOCOMPLETE,
091        DEFAULTS, 
092        INITIALIZATION,
093        LIFECYCLE, 
094        UI_HINT
095    }
096    
097    /**
098     * The intent of this method, so that the {@link WrapperFactory} knows whether to delegate on or to reject.
099     * @param method - one of the methods returned from {@link #getMethods()}
100     */
101    public Intent getIntent(Method method);
102
103
104    /**
105     * For use by
106     * {@link FacetHolder#getFacets(org.apache.isis.core.metamodel.facetapi.progmodel.facets.org.apache.isis.nof.arch.facets.Facet.Filter)}
107     */
108    public static Filter<Facet> FILTER = new Filter<Facet>() {
109        @Override
110        public boolean accept(final Facet facet) {
111            return ImperativeFacet.Util.isImperativeFacet(facet);
112        }
113    };
114
115    /**
116     * Whether invoking this requires a
117     * {@link DomainObjectContainer#resolve(Object)} to occur first.
118     */
119    public boolean impliesResolve();
120
121    /**
122     * Whether invoking this method requires an
123     * {@link DomainObjectContainer#objectChanged(Object)} to occur afterwards.
124     * 
125     * @return
126     */
127    public boolean impliesObjectChanged();
128
129
130    // //////////////////////////////////////
131
132    public static class Util {
133        private Util(){}
134
135        /**
136         * Returns the provided {@link Facet facet} as an {@link ImperativeFacet} if
137         * it either is one or if it is a {@link DecoratingFacet} that in turn wraps
138         * an {@link ImperativeFacet}.
139         * 
140         * <p>
141         * Otherwise, returns <tt>null</tt>.
142         */
143        public static ImperativeFacet getImperativeFacet(final Facet facet) {
144            if (facet instanceof ImperativeFacet) {
145                return (ImperativeFacet) facet;
146            }
147            if (facet.getUnderlyingFacet() instanceof ImperativeFacet) {
148                return (ImperativeFacet) facet.getUnderlyingFacet();
149            }
150            if (facet instanceof DecoratingFacet) {
151                final DecoratingFacet<?> decoratingFacet = ObjectExtensions.asT(facet);
152                return getImperativeFacet(decoratingFacet.getDecoratedFacet());
153            }
154            return null;
155        }
156        
157        public static boolean isImperativeFacet(final Facet facet) {
158            return getImperativeFacet(facet) != null;
159        }
160
161        public static Flags getFlags(final ObjectMember member, final Method method) {
162            final Flags flags = new Flags();
163            if (member == null) {
164                return flags;
165            }
166            final List<Facet> allFacets = member.getFacets(Filters.anyOfType(Facet.class));
167            for (final Facet facet : allFacets) {
168                final ImperativeFacet imperativeFacet = ImperativeFacet.Util.getImperativeFacet(facet);
169                if (imperativeFacet == null) {
170                    continue;
171                }
172                final List<Method> methods = imperativeFacet.getMethods();
173                if (!methods.contains(method)) {
174                    continue;
175                }
176                flags.apply(imperativeFacet);
177
178                // no need to search further
179                if (flags.bothSet()) {
180                    break;
181                }
182            }
183            return flags;
184        }
185        
186        public static Intent getIntent(final ObjectMember member, final Method method) {
187            final List<Facet> allFacets = member.getFacets(Filters.anyOfType(Facet.class));
188            final List<ImperativeFacet> imperativeFacets = Lists.newArrayList();
189            for (final Facet facet : allFacets) {
190                final ImperativeFacet imperativeFacet = ImperativeFacet.Util.getImperativeFacet(facet);
191                if (imperativeFacet == null) {
192                    continue;
193                }
194                final List<Method> methods = imperativeFacet.getMethods();
195                if (!methods.contains(method)) {
196                    continue;
197                }
198                imperativeFacets.add(imperativeFacet);
199            }
200            switch(imperativeFacets.size()) {
201                case 0:
202                    break;
203                case 1:
204                    return imperativeFacets.get(0).getIntent(method);
205                default:
206                    Intent intentToReturn = null;
207                    for (ImperativeFacet imperativeFacet : imperativeFacets) {
208                        Intent intent = imperativeFacet.getIntent(method);
209                        if(intentToReturn == null) {
210                            intentToReturn = intent;
211                        } else if(intentToReturn != intent) {
212                            throw new IllegalArgumentException(member.getIdentifier().toClassAndNameIdentityString() +  ": more than one ImperativeFacet for method " + method.getName() + " , with inconsistent intents: " + imperativeFacets.toString());
213                        }
214                    }
215                    return intentToReturn;
216            }
217            throw new IllegalArgumentException(member.getIdentifier().toClassAndNameIdentityString() +  ": unable to determine intent of " + method.getName());
218        }
219    }
220
221    public static class Flags {
222        private boolean impliesResolve;
223        private boolean impliesObjectChanged;
224
225        public void apply(final ImperativeFacet imperativeFacet) {
226            this.impliesResolve |= imperativeFacet.impliesResolve();
227            this.impliesObjectChanged |= imperativeFacet.impliesObjectChanged();
228        }
229
230        public boolean bothSet() {
231            return impliesResolve && impliesObjectChanged;
232        }
233
234        public boolean impliesResolve() {
235            return impliesResolve;
236        }
237
238        public boolean impliesObjectChanged() {
239            return impliesObjectChanged;
240        }
241    }
242
243}