uk.co.grahamcox.xml.XmlHandler.java Source code

Java tutorial

Introduction

Here is the source code for uk.co.grahamcox.xml.XmlHandler.java

Source

/*
 * Copyright (C) 2011 graham
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package uk.co.grahamcox.xml;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * DefaultHandler implementation that delegates correctly to the annotated Handler implementation
 * @author graham
 */
public class XmlHandler extends DefaultHandler {
    /** The logger to use */
    private static final Log LOG = LogFactory.getLog(XmlHandler.class);
    /** The handler to ultimately delegate to */
    private final Handler handler;
    /** The stack of elements so far */
    private final Stack<String> elementStack = new Stack<String>();
    /** Map of the methods with StartElement annotations to the path the annotation describes */
    private final Map<Method, Path> startElements = new HashMap<Method, Path>();
    /** Map of the methods with EndElement annotations to the path the annotation describes */
    private final Map<Method, Path> endElements = new HashMap<Method, Path>();
    /** Map of the methods with Characters annotations to the path the annotation describes */
    private final Map<Method, Path> characters = new HashMap<Method, Path>();

    /**
     * Create the handler
     * @param handler The handler to delegate to
     */
    public XmlHandler(Handler handler) {
        this.handler = handler;
        if (handler != null) {
            for (Method method : handler.getClass().getMethods()) {
                StartElement startElement = method.getAnnotation(StartElement.class);
                if (startElement != null) {
                    LOG.debug("Found handler for start element: " + method);
                    startElements.put(method, new Path(startElement.value()));
                }
                EndElement endElement = method.getAnnotation(EndElement.class);
                if (endElement != null) {
                    LOG.debug("Found handler for end element: " + method);
                    endElements.put(method, new Path(endElement.value()));
                }
                Characters characters = method.getAnnotation(Characters.class);
                if (characters != null) {
                    LOG.debug("Found handler for characters: " + method);
                    this.characters.put(method, new Path(characters.value()));
                }
            }
        }
    }

    /**
     * Build a name string that is the URI in curly brackets iff there is a URI
     * with the local name appended to it
     * @param uri the URI
     * @param localName the local name
     * @return the built name
     */
    private String buildName(final String uri, final String localName) {
        String name = localName;
        if (uri != null && !uri.isEmpty()) {
            name = "{" + uri + "}" + localName;
        }
        return name;
    }

    /**
     * Handle the start of an element
     * @param uri The namespace URI
     * @param localName The local name - without prefix - of the element
     * @param qNAme The qualified name - with prefix - of the element
     * @param attributes The attributes of the element
     * @throws SAXException if an error occurs
     */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes)
            throws SAXException {
        String name = buildName(uri, localName);
        LOG.debug("Processing " + name);
        elementStack.push(name);
        for (Entry<Method, Path> entry : startElements.entrySet()) {
            if (entry.getValue().matches(elementStack)) {
                Method method = entry.getKey();
                LOG.debug("Found matching callback method: " + method);
                Class<?>[] parameterTypes = method.getParameterTypes();
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();

                Object[] params = new Object[parameterTypes.length];
                for (int i = 0; i < params.length; ++i) {
                    params[i] = null;
                    for (Annotation annotation : parameterAnnotations[i]) {
                        if (annotation instanceof Attribute) {
                            String attribute = ((Attribute) annotation).value();
                            String value = attributes.getValue(attribute);
                            params[i] = value;
                            break;
                        }
                    }
                }
                invokeCallback(method, params);
            }
        }
    }

    /**
     * Invoke the given callback on the handler
     * @param method the callback to invoke
     * @param params the parameters of the callback
     * @throws SAXException if an error occurs
     */
    private void invokeCallback(Method method, Object[] params) throws SAXException {
        try {
            method.invoke(handler, params);
        } catch (IllegalAccessException ex) {
            LOG.error("Illegal access calling method " + method, ex);
            throw new SAXException("Illegal access calling method " + method, ex);
        } catch (IllegalArgumentException ex) {
            LOG.error("Illegal argument calling method " + method, ex);
            throw new SAXException("Illegal argument calling method " + method, ex);
        } catch (InvocationTargetException ex) {
            LOG.error("Exception occurred calling method " + method, ex);
            SAXException toThrow = new SAXException("An error occurred in method " + method);
            toThrow.initCause(ex.getTargetException());
            throw toThrow;
        }
    }

    /**
     * Handle the end of an element
     * @param uri The namespace URI
     * @param localName The local name - without prefix - of the element
     * @param qNAme The qualified name - with prefix - of the element
     * @throws SAXException if an error occurs
     */
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        String name = buildName(uri, localName);
        LOG.debug("Processing " + name);
        String expected = elementStack.peek();
        if (expected.equals(name)) {
            for (Entry<Method, Path> entry : endElements.entrySet()) {
                if (entry.getValue().matches(elementStack)) {
                    Method method = entry.getKey();
                    LOG.debug("Found matching callback method: " + method);
                    Class<?>[] parameterTypes = method.getParameterTypes();

                    Object[] params = new Object[parameterTypes.length];
                    for (int i = 0; i < params.length; ++i) {
                        params[i] = null;
                    }
                    invokeCallback(method, params);
                }
            }
            elementStack.pop();
        } else {
            throw new SAXException("Unexpected end of element " + name + ". Expected " + expected);
        }
    }

    /**
     * Handle character data
     * @param chars the character data
     * @param start start index in the buffer
     * @param length length of data in the buffer
     * @throws SAXException if an error occurs
     */
    @Override
    public void characters(char[] chars, int start, int length) throws SAXException {
        String name = elementStack.peek();
        LOG.debug("Processing " + name);
        String value = new String(chars, start, length);
        for (Entry<Method, Path> entry : characters.entrySet()) {
            if (entry.getValue().matches(elementStack)) {
                Method method = entry.getKey();
                LOG.debug("Found matching callback method: " + method);
                Class<?>[] parameterTypes = method.getParameterTypes();

                Object[] params = new Object[parameterTypes.length];
                for (int i = 0; i < params.length; ++i) {
                    params[i] = null;
                    if (parameterTypes[i].isAssignableFrom(String.class)) {
                        params[i] = value;
                    }
                }
                invokeCallback(method, params);
            }
        }

    }

}