001: /**
002: * Copyright 2003-2007 Luck Consulting Pty Ltd
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */package net.sf.ehcache.config;
016:
017: import org.xml.sax.Attributes;
018: import org.xml.sax.Locator;
019: import org.xml.sax.SAXException;
020: import org.xml.sax.helpers.DefaultHandler;
021: import org.apache.commons.logging.Log;
022: import org.apache.commons.logging.LogFactory;
023:
024: import java.lang.reflect.Constructor;
025: import java.lang.reflect.InvocationTargetException;
026: import java.lang.reflect.Method;
027: import java.lang.reflect.Modifier;
028: import java.util.ArrayList;
029:
030: /**
031: * A SAX handler that configures a bean.
032: *
033: * @version $Id: BeanHandler.java 519 2007-07-27 07:11:45Z gregluck $
034: * @author Adam Murdoch
035: * @author Greg Luck
036: */
037: final class BeanHandler extends DefaultHandler {
038: private static final Log LOG = LogFactory.getLog(BeanHandler.class
039: .getName());
040: private final Object bean;
041: private ElementInfo element;
042: private Locator locator;
043:
044: /**
045: * Constructor.
046: */
047: public BeanHandler(final Object bean) {
048: this .bean = bean;
049: }
050:
051: /**
052: * Receive a Locator object for document events.
053: */
054: public final void setDocumentLocator(Locator locator) {
055: this .locator = locator;
056: }
057:
058: /**
059: * Receive notification of the start of an element.
060: */
061: public final void startElement(final String uri,
062: final String localName, final String qName,
063: final Attributes attributes) throws SAXException {
064: // Create the child object
065: if (element == null) {
066: element = new ElementInfo(qName, bean);
067: } else {
068: final Object child = createChild(element, qName);
069: element = new ElementInfo(element, qName, child);
070: }
071:
072: // Set the attributes
073: for (int i = 0; i < attributes.getLength(); i++) {
074: final String attrName = attributes.getQName(i);
075: final String attrValue = attributes.getValue(i);
076: setAttribute(element, attrName, attrValue);
077: }
078: }
079:
080: /**
081: * Receive notification of the end of an element.
082: */
083: public final void endElement(final String uri,
084: final String localName, final String qName)
085: throws SAXException {
086: if (element.parent != null) {
087: addChild(element.parent.bean, element.bean, qName);
088: }
089: element = element.parent;
090: }
091:
092: /**
093: * Creates a child element of an object.
094: */
095: private Object createChild(final ElementInfo parent,
096: final String name) throws SAXException {
097:
098: try {
099: // Look for a create<name> method
100: final Class parentClass = parent.bean.getClass();
101: Method method = findCreateMethod(parentClass, name);
102: if (method != null) {
103: return method.invoke(parent.bean, new Object[] {});
104: }
105:
106: // Look for an add<name> method
107: method = findSetMethod(parentClass, "add", name);
108: if (method != null) {
109: return createInstance(parent.bean, method
110: .getParameterTypes()[0]);
111: }
112: } catch (final Exception e) {
113: throw new SAXException(getLocation()
114: + ": Could not create nested element <" + name
115: + ">.");
116: }
117:
118: throw new SAXException(getLocation() + ": Element <"
119: + parent.elementName + "> does not allow nested <"
120: + name + "> elements.");
121: }
122:
123: /**
124: * Creates a child object.
125: */
126: private static Object createInstance(Object parent, Class childClass)
127: throws Exception {
128: final Constructor[] constructors = childClass.getConstructors();
129: ArrayList candidates = new ArrayList();
130: for (int i = 0; i < constructors.length; i++) {
131: final Constructor constructor = constructors[i];
132: final Class[] params = constructor.getParameterTypes();
133: if (params.length == 0) {
134: candidates.add(constructor);
135: } else if (params.length == 1
136: && params[0].isInstance(parent)) {
137: candidates.add(constructor);
138: }
139: }
140: switch (candidates.size()) {
141: case 0:
142: throw new Exception("No constructor for class "
143: + childClass.getName());
144: case 1:
145: break;
146: default:
147: throw new Exception("Multiple constructors for class "
148: + childClass.getName());
149: }
150:
151: final Constructor constructor = (Constructor) candidates
152: .remove(0);
153: if (constructor.getParameterTypes().length == 0) {
154: return constructor.newInstance(new Object[] {});
155: } else {
156: return constructor.newInstance(new Object[] { parent });
157: }
158: }
159:
160: /**
161: * Finds a creator method.
162: */
163: private static Method findCreateMethod(Class objClass, String name) {
164: final String methodName = makeMethodName("create", name);
165: final Method[] methods = objClass.getMethods();
166: for (int i = 0; i < methods.length; i++) {
167: final Method method = methods[i];
168: if (!method.getName().equals(methodName)) {
169: continue;
170: }
171: if (Modifier.isStatic(method.getModifiers())) {
172: continue;
173: }
174: if (method.getParameterTypes().length != 0) {
175: continue;
176: }
177: if (method.getReturnType().isPrimitive()
178: || method.getReturnType().isArray()) {
179: continue;
180: }
181: return method;
182: }
183:
184: return null;
185: }
186:
187: /**
188: * Builds a method name from an element or attribute name.
189: */
190: private static String makeMethodName(final String prefix,
191: final String name) {
192: return prefix + Character.toUpperCase(name.charAt(0))
193: + name.substring(1);
194: }
195:
196: /**
197: * Sets an attribute.
198: */
199: private void setAttribute(final ElementInfo element,
200: final String attrName, final String attrValue)
201: throws SAXException {
202: try {
203: // Look for a set<name> method
204: final Class objClass = element.bean.getClass();
205: final Method method = findSetMethod(objClass, "set",
206: attrName);
207: if (method != null) {
208: final Object realValue = convert(method
209: .getParameterTypes()[0], attrValue);
210: method.invoke(element.bean, new Object[] { realValue });
211: return;
212: } else {
213: //allow references to an XML schema but do not use it
214: if (element.elementName.equals("ehcache")) {
215: if (LOG.isDebugEnabled()) {
216: LOG.debug("Ignoring ehcache attribute "
217: + attrName);
218: }
219: return;
220: }
221: }
222: } catch (final InvocationTargetException e) {
223: throw new SAXException(getLocation()
224: + ": Could not set attribute \"" + attrName + "\"."
225: + ". Message was: " + e.getTargetException());
226: } catch (final Exception e) {
227: throw new SAXException(getLocation()
228: + ": Could not set attribute \"" + attrName + "\".");
229: }
230:
231: throw new SAXException(getLocation() + ": Element <"
232: + element.elementName + "> does not allow attribute \""
233: + attrName + "\".");
234: }
235:
236: /**
237: * Converts a string to an object of a particular class.
238: */
239: private static Object convert(final Class toClass,
240: final String value) throws Exception {
241: if (value == null) {
242: return null;
243: }
244: if (toClass.isInstance(value)) {
245: return value;
246: }
247: if (toClass == Long.class || toClass == Long.TYPE) {
248: return Long.decode(value);
249: }
250: if (toClass == Integer.class || toClass == Integer.TYPE) {
251: return Integer.decode(value);
252: }
253: if (toClass == Boolean.class || toClass == Boolean.TYPE) {
254: return Boolean.valueOf(value);
255: }
256: throw new Exception("Cannot convert attribute value to class "
257: + toClass.getName());
258: }
259:
260: /**
261: * Finds a setter method.
262: */
263: private Method findSetMethod(final Class objClass,
264: final String prefix, final String name) throws Exception {
265: final String methodName = makeMethodName(prefix, name);
266: final Method[] methods = objClass.getMethods();
267: Method candidate = null;
268: for (int i = 0; i < methods.length; i++) {
269: final Method method = methods[i];
270: if (!method.getName().equals(methodName)) {
271: continue;
272: }
273: if (Modifier.isStatic(method.getModifiers())) {
274: continue;
275: }
276: if (method.getParameterTypes().length != 1) {
277: continue;
278: }
279: if (!method.getReturnType().equals(Void.TYPE)) {
280: continue;
281: }
282: if (candidate != null) {
283: throw new Exception("Multiple " + methodName
284: + "() methods in class " + objClass.getName()
285: + ".");
286: }
287: candidate = method;
288: }
289:
290: return candidate;
291: }
292:
293: /**
294: * Attaches a child element to its parent.
295: */
296: private void addChild(final Object parent, final Object child,
297: final String name) throws SAXException {
298: try {
299: // Look for an add<name> method on the parent
300: final Method method = findSetMethod(parent.getClass(),
301: "add", name);
302: if (method != null) {
303: method.invoke(parent, new Object[] { child });
304: }
305: } catch (final InvocationTargetException e) {
306: final SAXException exc = new SAXException(getLocation()
307: + ": Could not finish element <" + name + ">."
308: + " Message was: " + e.getTargetException());
309: throw exc;
310: } catch (final Exception e) {
311: throw new SAXException(getLocation()
312: + ": Could not finish element <" + name + ">.");
313: }
314: }
315:
316: /**
317: * Formats the current document location.
318: */
319: private String getLocation() {
320: return locator.getSystemId() + ':' + locator.getLineNumber();
321: }
322:
323: /**
324: * Element info class
325: */
326: private static final class ElementInfo {
327: private final ElementInfo parent;
328: private final String elementName;
329: private final Object bean;
330:
331: public ElementInfo(final String elementName, final Object bean) {
332: parent = null;
333: this .elementName = elementName;
334: this .bean = bean;
335: }
336:
337: public ElementInfo(final ElementInfo parent,
338: final String elementName, final Object bean) {
339: this.parent = parent;
340: this.elementName = elementName;
341: this.bean = bean;
342: }
343: }
344: }
|