001package org.apache.commons.digester3.binder;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import static java.lang.String.format;
023
024import java.util.Arrays;
025
026import org.apache.commons.digester3.CallMethodRule;
027
028/**
029 * Builder chained when invoking {@link LinkedRuleBuilder#callMethod(String)}.
030 *
031 * @since 3.0
032 */
033public final class CallMethodBuilder
034    extends AbstractBackToLinkedRuleBuilder<CallMethodRule>
035{
036
037    private final String methodName;
038
039    private final ClassLoader classLoader;
040
041    private int targetOffset;
042
043    private int paramCount = 0;
044
045    private Class<?>[] paramTypes = new Class<?>[]{};
046
047    private boolean useExactMatch = false;
048
049    CallMethodBuilder( String keyPattern, String namespaceURI, RulesBinder mainBinder, LinkedRuleBuilder mainBuilder,
050                       String methodName, ClassLoader classLoader )
051    {
052        super( keyPattern, namespaceURI, mainBinder, mainBuilder );
053        this.methodName = methodName;
054        this.classLoader = classLoader;
055    }
056
057    /**
058     * Sets the location of the target object.
059     *
060     * Positive numbers are relative to the top of the digester object stack.
061     * Negative numbers are relative to the bottom of the stack. Zero implies the top object on the stack.
062     *
063     * @param targetOffset location of the target object.
064     * @return this builder instance
065     */
066    public CallMethodBuilder withTargetOffset( int targetOffset )
067    {
068        this.targetOffset = targetOffset;
069        return this;
070    }
071
072    /**
073     * Sets the Java class names that represent the parameter types of the method arguments.
074     *
075     * If you wish to use a primitive type, specify the corresonding Java wrapper class instead,
076     * such as {@code java.lang.Boolean.TYPE} for a {@code boolean} parameter.
077     *
078     * @param paramTypeNames The Java classes names that represent the parameter types of the method arguments
079     * @return this builder instance
080     */
081    public CallMethodBuilder withParamTypes( String... paramTypeNames )
082    {
083        Class<?>[] paramTypes = null;
084        if ( paramTypeNames != null )
085        {
086            paramTypes = new Class<?>[paramTypeNames.length];
087            for ( int i = 0; i < paramTypeNames.length; i++ )
088            {
089                try
090                {
091                    paramTypes[i] = classLoader.loadClass( paramTypeNames[i] );
092                }
093                catch ( ClassNotFoundException e )
094                {
095                    this.reportError( format( "callMethod( \"%s\" ).withParamTypes( %s )", this.methodName,
096                                                     Arrays.toString( paramTypeNames ) ),
097                                      format( "class '%s' cannot be load", paramTypeNames[i] ) );
098                }
099            }
100        }
101
102        return withParamTypes( paramTypes );
103    }
104
105    /**
106     * Sets the Java classes that represent the parameter types of the method arguments.
107     *
108     * If you wish to use a primitive type, specify the corresonding Java wrapper class instead,
109     * such as {@code java.lang.Boolean.TYPE} for a {@code boolean} parameter.
110     *
111     * @param paramTypes The Java classes that represent the parameter types of the method arguments
112     * @return this builder instance
113     */
114    public CallMethodBuilder withParamTypes( Class<?>... paramTypes )
115    {
116        this.paramTypes = paramTypes;
117
118        if ( paramTypes != null )
119        {
120            this.paramCount = paramTypes.length;
121        }
122        else
123        {
124            paramCount = 0;
125        }
126
127        return this;
128    }
129
130    /**
131     * Should <code>MethodUtils.invokeExactMethod</code> be used for the reflection.
132     *
133     * @param useExactMatch Flag to mark exact matching or not
134     * @return this builder instance
135     */
136    public CallMethodBuilder useExactMatch( boolean useExactMatch )
137    {
138        this.useExactMatch = useExactMatch;
139        return this;
140    }
141
142    /**
143     * The number of parameters to collect, or zero for a single argument from the body of this element.
144     *
145     * @param paramCount The number of parameters to collect, or zero for a single argument
146     *        from the body of this element.
147     * @return this builder instance
148     */
149    public CallMethodBuilder withParamCount( int paramCount )
150    {
151        if ( paramCount < 0 )
152        {
153            this.reportError( format( "callMethod(\"%s\").withParamCount(int)", this.methodName ),
154                              "negative parameters counter not allowed" );
155        }
156
157        this.paramCount = paramCount;
158
159        if ( this.paramCount == 0 )
160        {
161            if ( this.paramTypes == null || this.paramTypes.length != 1 )
162            {
163                this.paramTypes = new Class<?>[] { String.class };
164            }
165        }
166        else
167        {
168            this.paramTypes = new Class<?>[this.paramCount];
169            for ( int i = 0; i < paramTypes.length; i++ )
170            {
171                this.paramTypes[i] = String.class;
172            }
173        }
174        return this;
175    }
176
177    /**
178     * Prepare the {@link CallMethodRule} to be invoked using the matching element body as argument.
179     *
180     * @return this builder instance
181     */
182    public CallMethodBuilder usingElementBodyAsArgument()
183    {
184        return withParamCount( 0 );
185    }
186
187    /**
188     * {@inheritDoc}
189     */
190    @Override
191    protected CallMethodRule createRule()
192    {
193        CallMethodRule callMethodRule = new CallMethodRule( targetOffset, methodName, paramCount, paramTypes );
194        callMethodRule.setUseExactMatch( useExactMatch );
195        return callMethodRule;
196    }
197
198}