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