1 /**
2 * Copyright 2003-2006 Greg Luck
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 net.sf.ehcache.config;
18
19 import org.xml.sax.Attributes;
20 import org.xml.sax.Locator;
21 import org.xml.sax.SAXException;
22 import org.xml.sax.helpers.DefaultHandler;
23
24 import java.lang.reflect.Constructor;
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.lang.reflect.Modifier;
28 import java.util.ArrayList;
29
30 /**
31 * A SAX handler that configures a bean.
32 *
33 * @version $Id: BeanHandler.java 53 2006-04-25 08:56:21Z gregluck $
34 * @author Adam Murdoch
35 * @author Greg Luck
36 */
37 final class BeanHandler extends DefaultHandler {
38 private final Object bean;
39 private ElementInfo element;
40 private Locator locator;
41
42
43 /**
44 * Constructor.
45 */
46 public BeanHandler(final Object bean) {
47 this.bean = bean;
48 }
49
50 /**
51 * Receive a Locator object for document events.
52 */
53 public final void setDocumentLocator(Locator locator) {
54 this.locator = locator;
55 }
56
57 /**
58 * Receive notification of the start of an element.
59 */
60 public final void startElement(final String uri,
61 final String localName,
62 final String qName,
63 final Attributes attributes)
64 throws SAXException {
65
66 if (element == null) {
67 element = new ElementInfo(qName, bean);
68 } else {
69 final Object child = createChild(element, qName);
70 element = new ElementInfo(element, qName, child);
71 }
72
73
74 for (int i = 0; i < attributes.getLength(); i++) {
75 final String attrName = attributes.getQName(i);
76 final String attrValue = attributes.getValue(i);
77 setAttribute(element, attrName, attrValue);
78 }
79 }
80
81 /**
82 * Receive notification of the end of an element.
83 */
84 public final void endElement(final String uri,
85 final String localName,
86 final String qName)
87 throws SAXException {
88 if (element.parent != null) {
89 addChild(element.parent.bean, element.bean, qName);
90 }
91 element = element.parent;
92 }
93
94 /**
95 * Creates a child element of an object.
96 */
97 private Object createChild(final ElementInfo parent, final String name)
98 throws SAXException {
99
100 try {
101
102 final Class parentClass = parent.bean.getClass();
103 Method method = findCreateMethod(parentClass, name);
104 if (method != null) {
105 return method.invoke(parent.bean, new Object[] {});
106 }
107
108
109 method = findSetMethod(parentClass, "add", name);
110 if (method != null) {
111 return createInstance(parent.bean, method.getParameterTypes()[0]);
112 }
113 } catch (final Exception e) {
114 throw new SAXException(getLocation() + ": Could not create nested element <" + name + ">.");
115 }
116
117 throw new SAXException(getLocation()
118 + ": Element <"
119 + parent.elementName
120 + "> does not allow nested <"
121 + name
122 + "> elements.");
123 }
124
125 /**
126 * Creates a child object.
127 */
128 private static Object createInstance(Object parent, Class childClass)
129 throws Exception {
130 final Constructor[] constructors = childClass.getConstructors();
131 ArrayList candidates = new ArrayList();
132 for (int i = 0; i < constructors.length; i++) {
133 final Constructor constructor = constructors[i];
134 final Class[] params = constructor.getParameterTypes();
135 if (params.length == 0) {
136 candidates.add(constructor);
137 } else if (params.length == 1 && params[0].isInstance(parent)) {
138 candidates.add(constructor);
139 }
140 }
141 switch (candidates.size()) {
142 case 0:
143 throw new Exception("No constructor for class " + childClass.getName());
144 case 1:
145 break;
146 default:
147 throw new Exception("Multiple constructors for class " + childClass.getName());
148 }
149
150 final Constructor constructor = (Constructor) candidates.remove(0);
151 if (constructor.getParameterTypes().length == 0) {
152 return constructor.newInstance(new Object[] {});
153 } else {
154 return constructor.newInstance(new Object[]{parent});
155 }
156 }
157
158 /**
159 * Finds a creator method.
160 */
161 private static Method findCreateMethod(Class objClass, String name) {
162 final String methodName = makeMethodName("create", name);
163 final Method[] methods = objClass.getMethods();
164 for (int i = 0; i < methods.length; i++) {
165 final Method method = methods[i];
166 if (!method.getName().equals(methodName)) {
167 continue;
168 }
169 if (Modifier.isStatic(method.getModifiers())) {
170 continue;
171 }
172 if (method.getParameterTypes().length != 0) {
173 continue;
174 }
175 if (method.getReturnType().isPrimitive() || method.getReturnType().isArray()) {
176 continue;
177 }
178 return method;
179 }
180
181 return null;
182 }
183
184 /**
185 * Builds a method name from an element or attribute name.
186 */
187 private static String makeMethodName(final String prefix, final String name) {
188 return prefix + Character.toUpperCase(name.charAt(0)) + name.substring(1);
189 }
190
191 /**
192 * Sets an attribute.
193 */
194 private void setAttribute(final ElementInfo element,
195 final String attrName,
196 final String attrValue)
197 throws SAXException {
198 try {
199
200 final Class objClass = element.bean.getClass();
201 final Method method = findSetMethod(objClass, "set", attrName);
202 if (method != null) {
203 final Object realValue = convert(method.getParameterTypes()[0], attrValue);
204 method.invoke(element.bean, new Object[]{realValue});
205 return;
206 }
207 } catch (final InvocationTargetException e) {
208 throw new SAXException(getLocation() + ": Could not set attribute \"" + attrName + "\"."
209 + ". Message was: " + e.getTargetException());
210 } catch (final Exception e) {
211 throw new SAXException(getLocation() + ": Could not set attribute \"" + attrName + "\".");
212 }
213
214 throw new SAXException(getLocation()
215 + ": Element <"
216 + element.elementName
217 + "> does not allow attribute \""
218 + attrName
219 + "\".");
220 }
221
222 /**
223 * Converts a string to an object of a particular class.
224 */
225 private static Object convert(final Class toClass, final String value)
226 throws Exception {
227 if (value == null) {
228 return null;
229 }
230 if (toClass.isInstance(value)) {
231 return value;
232 }
233 if (toClass == Long.class || toClass == Long.TYPE) {
234 return Long.decode(value);
235 }
236 if (toClass == Integer.class || toClass == Integer.TYPE) {
237 return Integer.decode(value);
238 }
239 if (toClass == Boolean.class || toClass == Boolean.TYPE) {
240 return Boolean.valueOf(value);
241 }
242 throw new Exception("Cannot convert attribute value to class " + toClass.getName());
243 }
244
245 /**
246 * Finds a setter method.
247 */
248 private Method findSetMethod(final Class objClass,
249 final String prefix,
250 final String name)
251 throws Exception {
252 final String methodName = makeMethodName(prefix, name);
253 final Method[] methods = objClass.getMethods();
254 Method candidate = null;
255 for (int i = 0; i < methods.length; i++) {
256 final Method method = methods[i];
257 if (!method.getName().equals(methodName)) {
258 continue;
259 }
260 if (Modifier.isStatic(method.getModifiers())) {
261 continue;
262 }
263 if (method.getParameterTypes().length != 1) {
264 continue;
265 }
266 if (!method.getReturnType().equals(Void.TYPE)) {
267 continue;
268 }
269 if (candidate != null) {
270 throw new Exception("Multiple " + methodName + "() methods in class " + objClass.getName() + ".");
271 }
272 candidate = method;
273 }
274
275 return candidate;
276 }
277
278 /**
279 * Attaches a child element to its parent.
280 */
281 private void addChild(final Object parent,
282 final Object child,
283 final String name)
284 throws SAXException {
285 try {
286
287 final Method method = findSetMethod(parent.getClass(), "add", name);
288 if (method != null) {
289 method.invoke(parent, new Object[]{child});
290 }
291 } catch (final InvocationTargetException e) {
292 final SAXException exc = new SAXException(getLocation() + ": Could not finish element <" + name + ">." +
293 " Message was: " + e.getTargetException());
294 throw exc;
295 } catch (final Exception e) {
296 throw new SAXException(getLocation() + ": Could not finish element <" + name + ">.");
297 }
298 }
299
300 /**
301 * Formats the current document location.
302 */
303 private String getLocation() {
304 return locator.getSystemId() + ':' + locator.getLineNumber();
305 }
306
307 /**
308 * Element info class
309 */
310 private static final class ElementInfo {
311 private final ElementInfo parent;
312 private final String elementName;
313 private final Object bean;
314
315 public ElementInfo(final String elementName, final Object bean) {
316 parent = null;
317 this.elementName = elementName;
318 this.bean = bean;
319 }
320
321 public ElementInfo(final ElementInfo parent, final String elementName, final Object bean) {
322 this.parent = parent;
323 this.elementName = elementName;
324 this.bean = bean;
325 }
326 }
327 }