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