001 package 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 022 import static java.lang.String.format; 023 024 import java.util.Arrays; 025 026 import org.apache.commons.digester3.CallMethodRule; 027 028 /** 029 * Builder chained when invoking {@link LinkedRuleBuilder#callMethod(String)}. 030 * 031 * @since 3.0 032 */ 033 public 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 }