001// Copyright 2004, 2005 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007//     http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.hivemind.schema.rules;
016
017import java.lang.reflect.Method;
018
019import org.apache.hivemind.ApplicationRuntimeException;
020import org.apache.hivemind.Element;
021import org.apache.hivemind.schema.SchemaProcessor;
022
023/**
024 * Rule used to connect a child object to its parent by invoking a method on the parent, passing the
025 * child. The child object is the top object on the stack and the parent object is the next object
026 * down on the stack. Created from the <code>&lt;invoke-parent&gt;</code> element. Generally, this
027 * is the last rule in a sequence of rules.
028 * 
029 * @author Howard Lewis Ship
030 */
031public class InvokeParentRule extends BaseRule
032{
033    private String _methodName;
034
035    private int _depth = 1;
036
037    public InvokeParentRule()
038    {
039
040    }
041
042    public InvokeParentRule(String methodName)
043    {
044        _methodName = methodName;
045    }
046
047    /**
048     * Invokes the named method on the parent object (using reflection).
049     */
050    public void begin(SchemaProcessor processor, Element element)
051    {
052        Object child = processor.peek();
053        Class childClass = child == null ? null : child.getClass();
054        Object parent = processor.peek(_depth);
055
056        try
057        {
058            Method m = findMethod(parent, _methodName, childClass);
059
060            m.invoke(parent, new Object[]
061            { child });
062        }
063        catch (Exception ex)
064        {
065            throw new ApplicationRuntimeException(RulesMessages.errorInvokingMethod(
066                    _methodName,
067                    parent,
068                    getLocation(),
069                    ex), getLocation(), ex);
070        }
071    }
072
073    public String getMethodName()
074    {
075        return _methodName;
076    }
077
078    public void setMethodName(String string)
079    {
080        _methodName = string;
081    }
082
083    /**
084     * @since 1.1
085     */
086    public int getDepth()
087    {
088        return _depth;
089    }
090
091    /**
092     * Sets the depth of the parent object. The default is 1.
093     */
094    public void setDepth(int i)
095    {
096        _depth = i;
097    }
098
099    /**
100     * Searches for the *first* public method the has the right name, and takes a single parameter
101     * that is compatible with the parameter type.
102     * 
103     * @throws NoSuchMethodException
104     *             if a method can't be found
105     */
106    private Method findMethod(Object target, String name, Class parameterType)
107            throws NoSuchMethodException
108    {
109        Method[] methods = target.getClass().getMethods();
110
111        for (int i = 0; i < methods.length; i++)
112        {
113            Method m = methods[i];
114            Class[] parameterTypes = m.getParameterTypes();
115
116            if (parameterTypes.length != 1)
117                continue;
118
119            if (!m.getName().equals(name))
120                continue;
121
122            if ((parameterType != null && parameterTypes[0].isAssignableFrom(parameterType))
123                    || (parameterType == null && !parameterTypes[0].isPrimitive()))
124                return m;
125        }
126
127        throw new NoSuchMethodException(name);
128    }
129}