Source code: org/apache/ws/jaxme/xs/parser/impl/AttributeSetterImpl.java
1 /*
2 * Copyright 2003, 2004 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15
16 */
17 package org.apache.ws.jaxme.xs.parser.impl;
18
19 import java.lang.reflect.Constructor;
20 import java.lang.reflect.InvocationTargetException;
21 import java.lang.reflect.Method;
22 import java.lang.reflect.Modifier;
23 import java.lang.reflect.UndeclaredThrowableException;
24
25 import org.apache.ws.jaxme.xs.XSParser;
26 import org.apache.ws.jaxme.xs.parser.*;
27 import org.xml.sax.SAXException;
28
29
30 /** <p>Default implementation of the {@link org.apache.ws.jaxme.xs.parser.AttributeSetter}
31 * interface.</p>
32 *
33 * @author <a href="mailto:joe@ispsoft.de">Jochen Wiedmann</a>
34 */
35 public class AttributeSetterImpl implements AttributeSetter {
36 static final Class[] ONE_STRING_CLASS = new Class[]{String.class};
37 private static final Class[] FOUR_STRING_CLASSES =
38 new Class[]{String.class, String.class, String.class, String.class};
39
40 protected XSContext getData() {
41 XSContext result = XSParser.getRunningInstance().getContext();
42 if (result == null) {
43 throw new IllegalStateException("Parser data is not set.");
44 }
45 return result;
46 }
47
48 /** <p>This method configures the bean <code>pBean</code> as follows:
49 * <ol>
50 * <li>If the bean has a method
51 * <code>setAttribute(String, String, String)</code>, it is invoked
52 * with the following arguments:
53 * <ul>
54 * <li>The attributes namespace URI (empty string for the default
55 * namespace),</li>
56 * <li>the attributes local name,</li>
57 * <li>and the property value</li>
58 * </ul>
59 * </li>
60 * <li>Otherwise invokes its own method {@link #setProperty(Object, String, String, String)}.</li>
61 * </ol>
62 */
63 public void setAttribute(String pQName, String pNamespaceURI, String pLocalName, String pValue)
64 throws SAXException {
65 XsSAXParser handler = ((XsSAXParser) getData().getCurrentContentHandler());
66 if (handler == null) {
67 throw new IllegalStateException("Current XsSAXParser is null.");
68 }
69 Object bean = ((XsSAXParser) getData().getCurrentContentHandler()).getBean();
70 try {
71 Method m = bean.getClass().getMethod("setAttribute", FOUR_STRING_CLASSES);
72 if (Modifier.isPublic(m.getModifiers())) {
73 Object[] o = new Object[]{pQName, pNamespaceURI, pLocalName, pValue};
74 Object result = invokeMethod(bean, m, pQName, o);
75 if (!boolean.class.equals(m.getReturnType()) || ((Boolean) result).booleanValue()) {
76 return;
77 }
78 }
79 } catch (NoSuchMethodException e) {
80 }
81
82 if (!setProperty(bean, pQName, pLocalName, pValue)) {
83 throw new IllegalStateException("Unknown attribute of " + bean.getClass().getName() + ": " + pQName);
84 }
85 }
86
87
88 /** <p>This method invokes the beans <code>pBean</code> method <code>pMethod</code>,
89 * setting the attribute <code>pName</code> to the value <code>pArgs</code>.</p>
90 */
91 protected Object invokeMethod(Object pBean, Method pMethod, String pName, Object[] pArgs) throws SAXException {
92 try {
93 return pMethod.invoke(pBean, pArgs);
94 } catch (InvocationTargetException e) {
95 Throwable t = e.getTargetException();
96 if (t instanceof SAXException) {
97 throw (SAXException) t;
98 } else if (t instanceof RuntimeException) {
99 throw (RuntimeException) t;
100 } else {
101 throw new UndeclaredThrowableException(t);
102 }
103 } catch (IllegalAccessException e) {
104 StringBuffer sb = new StringBuffer("Failed to invoke method ");
105 sb.append(pMethod.getName()).append(" of class ").append(pBean.getClass().getName());
106 sb.append(" with argument ");
107 for (int i = 0; i < pArgs.length; i++) {
108 if (i > 0) {
109 sb.append(", ");
110 }
111 sb.append(pArgs[i]);
112 }
113 sb.append(": ").append(e.getClass().getName()).append(", ").append(e.getMessage());
114 throw new IllegalStateException(sb.toString());
115 }
116 }
117
118 private interface ParameterClass {
119 public Object matches(Class pClass);
120 public void invoke(AttributeSetterImpl pAttributeSetter, Object pBean, String pValue,
121 Method pMethod, Object pMethodObject, String pQName)
122 throws SAXException;
123 }
124
125 private static class StringClass implements ParameterClass {
126 public Object matches(Class pClass) {
127 return String.class.equals(pClass) ? Boolean.TRUE : null;
128 }
129 public void invoke(AttributeSetterImpl pAttributeSetter, Object pBean, String pValue,
130 Method pMethod, Object pMethodObject, String pQName) throws SAXException {
131 pAttributeSetter.invokeMethod(pBean, pMethod, pQName, new Object[]{pValue});
132 }
133 }
134
135 private static class ValueOfParameterClass implements ParameterClass {
136 public Object matches(Class pClass) {
137 try {
138 Method valueOfMethod = pClass.getMethod("valueOf", ONE_STRING_CLASS);
139 if (Modifier.isPublic(valueOfMethod.getModifiers()) && !void.class.equals(valueOfMethod.getReturnType())) {
140 return valueOfMethod;
141 }
142 } catch (NoSuchMethodException e) {
143 }
144 return null;
145 }
146 public void invoke(AttributeSetterImpl pAttributeSetter, Object pBean, String pValue,
147 Method pMethod, Object pMethodObject, String pQName) throws SAXException {
148 Method m = (Method) pMethodObject;
149 Object o;
150 try {
151 o = m.invoke(null, new Object[]{pValue});
152 } catch (InvocationTargetException e) {
153 throw new IllegalArgumentException("Illegal argument for attribute '" + pQName + "': " + pValue +
154 "; " + e.getTargetException().getClass().getName() +
155 ", " + e.getTargetException().getMessage());
156 } catch (IllegalAccessException e) {
157 throw new IllegalStateException("Invalid access to method " + m.getName() + " of class " +
158 pBean.getClass() + ": IllegalAccessException, " + e.getMessage());
159 }
160 pAttributeSetter.invokeMethod(pBean, pMethod, pQName, new Object[]{o});
161 }
162 }
163
164 private static class StringConstructorClass implements ParameterClass {
165 public Object matches(Class pClass) {
166 try {
167 Constructor con = pClass.getConstructor(ONE_STRING_CLASS);
168 if (Modifier.isPublic(con.getModifiers())) {
169 return con;
170 }
171 } catch (NoSuchMethodException e) {
172 }
173 return null;
174 }
175 public void invoke(AttributeSetterImpl pAttributeSetter, Object pBean, String pValue,
176 Method pMethod, Object pMethodObject, String pQName) throws SAXException {
177 Constructor con = (Constructor) pMethodObject;
178 Object o;
179 try {
180 o = con.newInstance(new Object[]{pValue});
181 } catch (InvocationTargetException e) {
182 throw new IllegalArgumentException("Illegal argument for attribute '" + pQName + "': " + pValue +
183 "; " + e.getTargetException().getClass().getName() +
184 ", " + e.getTargetException().getMessage());
185 } catch (InstantiationException e) {
186 throw new IllegalStateException("Invalid access to constructor " + pBean.getClass().getName() +
187 "(): " + e.getClass().getName() + ", " + e.getMessage());
188 } catch (IllegalAccessException e) {
189 throw new IllegalStateException("Invalid access to constructor " + pBean.getClass().getName() +
190 "(): " + e.getClass().getName() + ", " + e.getMessage());
191 }
192 pAttributeSetter.invokeMethod(pBean, pMethod, pQName, new Object[]{o});
193 }
194 }
195
196 private static class PrimitiveParameterClass extends StringConstructorClass {
197 private final Class primitiveClass;
198 private final Class nonPrimitiveClass;
199 private final Constructor stringConstructor;
200 private PrimitiveParameterClass(Class pPrimitiveClass, Class pNonPrimitiveClass) {
201 primitiveClass = pPrimitiveClass;
202 nonPrimitiveClass = pNonPrimitiveClass;
203 try {
204 stringConstructor = pNonPrimitiveClass.getConstructor(ONE_STRING_CLASS);
205 } catch (NoSuchMethodException e) {
206 throw new IllegalStateException("The primitive class " + pNonPrimitiveClass.getName() +
207 " doesn't have a string valued constructor!");
208 }
209 }
210 public Object matches(Class pClass) {
211 return (primitiveClass.equals(pClass) || nonPrimitiveClass.equals(pClass)) ? stringConstructor : null;
212 }
213 }
214
215 private static class CharacterClass implements ParameterClass {
216 public Object matches(Class pClass) {
217 return (Character.TYPE.equals(pClass) || Character.class.equals(pClass)) ? Boolean.TRUE : null;
218 }
219
220 public void invoke(AttributeSetterImpl pAttributeSetter, Object pBean, String pValue, Method pMethod, Object pMethodObject, String pQName) throws SAXException {
221 if (pValue.length() != 1) {
222 throw new IllegalArgumentException("Invalid value for '" + pQName +"': " + pValue +
223 "; must have exactly one character.");
224 }
225 pAttributeSetter.invokeMethod(pBean, pMethod, pQName, new Object[]{new Character(pValue.charAt(0))});
226 }
227 }
228
229 private static class BooleanClass implements ParameterClass {
230 public Object matches(Class pClass) {
231 return (Boolean.TYPE.equals(pClass) || Boolean.class.equals(pClass)) ? Boolean.TRUE : null;
232 }
233
234 public void invoke(AttributeSetterImpl pAttributeSetter, Object pBean, String pValue, Method pMethod, Object pMethodObject, String pQName) throws SAXException {
235 Boolean b = ("true".equals(pValue) || "1".equals(pValue)) ? Boolean.TRUE : Boolean.FALSE;
236 pAttributeSetter.invokeMethod(pBean, pMethod, pQName, new Object[]{b});
237 }
238 }
239
240 private static final ParameterClass[] knownClasses = new ParameterClass[]{
241 new BooleanClass(),
242 new StringClass(),
243 new ValueOfParameterClass(),
244 new StringConstructorClass(),
245 new CharacterClass(),
246 new PrimitiveParameterClass(long.class, Long.class),
247 new PrimitiveParameterClass(int.class, Integer.class),
248 new PrimitiveParameterClass(short.class, Short.class),
249 new PrimitiveParameterClass(byte.class, Byte.class),
250 new PrimitiveParameterClass(double.class, Double.class),
251 new PrimitiveParameterClass(float.class, Float.class),
252 new CharacterClass(),
253 };
254
255 /** <p>This method is invoked from within {@link #setAttribute(String, String, String, String)}.
256 * It configures the bean <code>pBean</code> as follows;
257 * <ol>
258 * <li>If the bean has a method <code>setProperty(String)</code>
259 * this method is invoked with the attribute value.</li>
260 * <li>If the bean has a method <code>setProperty(T)</code>, and
261 * the class <code>T</code> has either of a method
262 * <code>public static T valueOf(String)</code> or a constructor
263 * <code>public T(String)</code> (in that order), then the method
264 * <code>setProperty(T)</code> is invoked with the value obtained
265 * by an invocation of the method <code>valueOf()</code>, or
266 * the constructor, respectively. Note, that this applies in
267 * particular to the classes {@link Long}, {@link Integer},
268 * {@link Short}, {@link Byte}, {@link Double}, {@link Float},
269 * <code>java.math.BigInteger</code>, <code>java.math.BigDecimal</code>,
270 * {@link java.io.File}, and {@link java.lang.StringBuffer}.</li>
271 * <li>If the bean has a method <code>setProperty(boolean)</code>,
272 * the method will be invoked with the value <i>true</i>
273 * (the value specified in the XML file is either of
274 * <code>true</code>, or <code>1</code>, otherwise with the
275 * value <code>false</code>.</li>
276 * <li>If the bean has a method <code>setProperty(char)</code>,
277 * or <code>setProperty(Character)</code>, the method will be
278 * invoked with the first character of the value specified in
279 * the XML file. If the value contains zero or multiple characters,
280 * an {@link IllegalArgumentException} is thrown.</li>
281 * <li>If the bean has either of the following methods, in that order:
282 * <ul>
283 * <li><code>setProperty(long)</code></li>
284 * <li><code>setProperty(int)</code></li>
285 * <li><code>setProperty(short)</code></li>
286 * <li><code>setProperty(byte)</code></li>
287 * <li><code>setProperty(double)</code></li>
288 * <li><code>setProperty(float)</code></li>
289 * </ul>
290 * then the property value is converted into the respective type
291 * and the method is invoked. An {@link IllegalArgumentException}
292 * is thrown, if the conversion fails.</li>
293 * <li>If the bean has a method <code>java.lang.Class</code>, the
294 * <code>XsSAXParser</code> will interpret the value given in the
295 * XML file as a Java class name and load the named class from its
296 * class loader. If the class cannot be loaded, it will also try
297 * to use the current threads context class loader. An
298 * exception is thrown, if neither of the class loaders can
299 * load the class.</li>
300 * </ol>
301 * </p>
302 *
303 * @return True, if a method for setting the property was found. Otherwise
304 * false.
305 */
306 protected boolean setProperty(Object pBean, String pQName, String pName, String pValue)
307 throws SAXException {
308 Class c = pBean.getClass();
309 String s = "set" + Character.toUpperCase(pName.charAt(0)) + pName.substring(1);
310 int parameterClassNum = knownClasses.length;
311 Method[] methods = c.getMethods();
312 Method method = null;
313 Object methodObject = null;
314 for (int i = 0; i < methods.length; i++) {
315 Method m = methods[i];
316 if (!s.equals(m.getName()) || !Modifier.isPublic(m.getModifiers())) {
317 continue;
318 }
319
320 Class[] params = m.getParameterTypes();
321 if (params.length != 1) {
322 continue;
323 }
324
325 Class paramsClass = params[0];
326 for (int j = 0; j < parameterClassNum; j++) {
327 ParameterClass parameterClass = knownClasses[j];
328 Object o = parameterClass.matches(paramsClass);
329 if (o != null) {
330 parameterClassNum = j;
331 method = m;
332 methodObject = o;
333 break;
334 }
335 }
336 }
337
338 if (method == null) {
339 return false;
340 } else {
341 knownClasses[parameterClassNum].invoke(this, pBean, pValue, method, methodObject, pQName);
342 return true;
343 }
344 }
345 }