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    
020    package org.apache.isis.core.progmodel.facets.collections.modify;
021    
022    import java.lang.reflect.Method;
023    
024    import org.apache.isis.core.commons.lang.NameUtils;
025    import org.apache.isis.core.metamodel.adapter.ObjectDirtier;
026    import org.apache.isis.core.metamodel.adapter.ObjectDirtierAware;
027    import org.apache.isis.core.metamodel.exceptions.MetaModelException;
028    import org.apache.isis.core.metamodel.facetapi.FacetHolder;
029    import org.apache.isis.core.metamodel.facetapi.FacetUtil;
030    import org.apache.isis.core.metamodel.facetapi.FeatureType;
031    import org.apache.isis.core.metamodel.facets.FacetFactory;
032    import org.apache.isis.core.metamodel.facets.collections.modify.CollectionAddToFacet;
033    import org.apache.isis.core.metamodel.facets.collections.modify.CollectionRemoveFromFacet;
034    import org.apache.isis.core.metamodel.methodutils.MethodScope;
035    import org.apache.isis.core.progmodel.facets.MethodFinderUtils;
036    import org.apache.isis.core.progmodel.facets.MethodPrefixBasedFacetFactoryAbstract;
037    import org.apache.isis.core.progmodel.facets.MethodPrefixConstants;
038    import org.apache.isis.core.progmodel.facets.collections.validate.CollectionValidateAddToFacetViaMethod;
039    import org.apache.isis.core.progmodel.facets.collections.validate.CollectionValidateRemoveFromFacetViaMethod;
040    
041    /**
042     * TODO: should probably split out into two {@link FacetFactory}s, one for <tt>addTo()</tt>/<tt>removeFrom()</tt> and
043     * one for <tt>validateAddTo()</tt>/<tt>validateRemoveFrom()</tt>.
044     */
045    public class CollectionAddRemoveAndValidateFacetFactory extends MethodPrefixBasedFacetFactoryAbstract implements
046        ObjectDirtierAware {
047    
048        private static final String[] PREFIXES = {};
049    
050        private ObjectDirtier objectDirtier;
051    
052        public CollectionAddRemoveAndValidateFacetFactory() {
053            super(FeatureType.COLLECTIONS_ONLY, PREFIXES);
054        }
055    
056        @Override
057        public void process(final ProcessMethodContext processMethodContext) {
058    
059            final Class<?> collectionType = attachAddToFacetAndRemoveFromFacet(processMethodContext);
060            attachValidateAddToAndRemoveFromFacetIfMethodsFound(processMethodContext, collectionType);
061        }
062    
063        private Class<?> attachAddToFacetAndRemoveFromFacet(final ProcessMethodContext processMethodContext) {
064    
065            final Method accessorMethod = processMethodContext.getMethod();
066            final String capitalizedName = NameUtils.javaBaseName(accessorMethod.getName());
067    
068            final Class<?> cls = processMethodContext.getCls();
069    
070            // add
071            final Method addToMethod =
072                MethodFinderUtils.findMethod(cls, MethodScope.OBJECT,
073                    MethodPrefixConstants.ADD_TO_PREFIX + capitalizedName, void.class);
074            processMethodContext.removeMethod(addToMethod);
075    
076            // remove
077            final Method removeFromMethod =
078                MethodFinderUtils.findMethod(cls, MethodScope.OBJECT, MethodPrefixConstants.REMOVE_FROM_PREFIX
079                    + capitalizedName, void.class);
080            processMethodContext.removeMethod(removeFromMethod);
081    
082            // add facets
083            final FacetHolder collection = processMethodContext.getFacetHolder();
084            FacetUtil.addFacet(createAddToFacet(addToMethod, accessorMethod, collection));
085            FacetUtil.addFacet(createRemoveFromFacet(removeFromMethod, accessorMethod, collection));
086    
087            // infer typ
088            final Class<?> addToType =
089                ((addToMethod == null || addToMethod.getParameterTypes().length != 1) ? null : addToMethod
090                    .getParameterTypes()[0]);
091            final Class<?> removeFromType =
092                ((removeFromMethod == null || removeFromMethod.getParameterTypes().length != 1) ? null : removeFromMethod
093                    .getParameterTypes()[0]);
094    
095            return inferTypeOfIfPossible(accessorMethod, addToType, removeFromType, collection);
096        }
097    
098        /**
099         * TODO need to distinguish between Java collections, arrays and other collections!
100         */
101        private CollectionAddToFacet createAddToFacet(final Method addToMethodIfAny, final Method accessorMethod,
102            final FacetHolder holder) {
103            if (addToMethodIfAny != null) {
104                return new CollectionAddToFacetViaMethod(addToMethodIfAny, holder);
105            } else {
106                return new CollectionAddToFacetViaAccessor(accessorMethod, holder, getObjectDirtier());
107            }
108        }
109    
110        /**
111         * TODO need to distinguish between Java collections, arrays and other collections!
112         */
113        private CollectionRemoveFromFacet createRemoveFromFacet(final Method removeFromMethodIfAny,
114            final Method accessorMethod, final FacetHolder holder) {
115            if (removeFromMethodIfAny != null) {
116                return new CollectionRemoveFromFacetViaMethod(removeFromMethodIfAny, holder);
117            } else {
118                return new CollectionRemoveFromFacetViaAccessor(accessorMethod, holder, getObjectDirtier());
119            }
120        }
121    
122        private Class<?> inferTypeOfIfPossible(final Method getMethod, final Class<?> addType, final Class<?> removeType,
123            final FacetHolder collection) {
124    
125            if (addType != null && removeType != null && addType != removeType) {
126                throw new MetaModelException("The addTo/removeFrom methods for " + getMethod.getDeclaringClass() + " must "
127                    + "both deal with same type of object: " + addType + "; " + removeType);
128            }
129    
130            final Class<?> type = addType != null ? addType : removeType;
131            if (type != null) {
132                FacetUtil
133                    .addFacet(new TypeOfFacetInferredFromSupportingMethods(type, collection, getSpecificationLookup()));
134            }
135            return type;
136        }
137    
138        private void attachValidateAddToAndRemoveFromFacetIfMethodsFound(final ProcessMethodContext processMethodContext,
139            final Class<?> collectionType) {
140            attachValidateAddToFacetIfValidateAddToMethodIsFound(processMethodContext, collectionType);
141            attachValidateRemoveFacetIfValidateRemoveFromMethodIsFound(processMethodContext, collectionType);
142        }
143    
144        private void attachValidateAddToFacetIfValidateAddToMethodIsFound(final ProcessMethodContext processMethodContext,
145            final Class<?> collectionType) {
146    
147            final Method getMethod = processMethodContext.getMethod();
148            final String capitalizedName = NameUtils.javaBaseName(getMethod.getName());
149    
150            final Class<?> cls = processMethodContext.getCls();
151            final Class<?>[] paramTypes = MethodFinderUtils.paramTypesOrNull(collectionType);
152            Method validateAddToMethod =
153                MethodFinderUtils.findMethod(cls, MethodScope.OBJECT, MethodPrefixConstants.VALIDATE_ADD_TO_PREFIX
154                    + capitalizedName, String.class, paramTypes);
155            if (validateAddToMethod == null) {
156                validateAddToMethod =
157                    MethodFinderUtils.findMethod(cls, MethodScope.OBJECT, MethodPrefixConstants.VALIDATE_ADD_TO_PREFIX_2
158                        + capitalizedName, String.class, MethodFinderUtils.paramTypesOrNull(collectionType));
159            }
160            if (validateAddToMethod == null) {
161                return;
162            }
163            processMethodContext.removeMethod(validateAddToMethod);
164    
165            final FacetHolder collection = processMethodContext.getFacetHolder();
166            FacetUtil.addFacet(new CollectionValidateAddToFacetViaMethod(validateAddToMethod, collection));
167        }
168    
169        private void attachValidateRemoveFacetIfValidateRemoveFromMethodIsFound(
170            final ProcessMethodContext processMethodContext, final Class<?> collectionType) {
171    
172            final Method getMethod = processMethodContext.getMethod();
173            final String capitalizedName = NameUtils.javaBaseName(getMethod.getName());
174    
175            final Class<?> cls = processMethodContext.getCls();
176            final Class<?>[] paramTypes = MethodFinderUtils.paramTypesOrNull(collectionType);
177            Method validateRemoveFromMethod =
178                MethodFinderUtils.findMethod(cls, MethodScope.OBJECT, MethodPrefixConstants.VALIDATE_REMOVE_FROM_PREFIX
179                    + capitalizedName, String.class, paramTypes);
180            if (validateRemoveFromMethod == null) {
181                validateRemoveFromMethod =
182                    MethodFinderUtils.findMethod(cls, MethodScope.OBJECT,
183                        MethodPrefixConstants.VALIDATE_REMOVE_FROM_PREFIX_2 + capitalizedName, String.class,
184                        MethodFinderUtils.paramTypesOrNull(collectionType));
185            }
186            if (validateRemoveFromMethod == null) {
187                return;
188            }
189            processMethodContext.removeMethod(validateRemoveFromMethod);
190    
191            final FacetHolder collection = processMethodContext.getFacetHolder();
192            FacetUtil.addFacet(new CollectionValidateRemoveFromFacetViaMethod(validateRemoveFromMethod, collection));
193        }
194    
195        // ///////////////////////////////////////////////////////
196        // Dependencies (injected)
197        // ///////////////////////////////////////////////////////
198    
199        protected ObjectDirtier getObjectDirtier() {
200            return objectDirtier;
201        }
202    
203        @Override
204        public void setObjectDirtier(final ObjectDirtier objectDirtier) {
205            this.objectDirtier = objectDirtier;
206        }
207    
208    }