View Javadoc

    1   /**
    2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
    3    */
    4   package net.sourceforge.pmd.lang.xml.ast;
    5
    6   import java.io.IOException;
    7   import java.io.Reader;
    8   import java.lang.reflect.InvocationHandler;
    9   import java.lang.reflect.Method;
    10  import java.lang.reflect.Proxy;
    11  import java.util.ArrayList;
    12  import java.util.Arrays;
    13  import java.util.Collections;
    14  import java.util.HashMap;
    15  import java.util.Iterator;
    16  import java.util.LinkedHashSet;
    17  import java.util.List;
    18  import java.util.Map;
    19  import java.util.Set;
    20
    21  import javax.xml.parsers.DocumentBuilder;
    22  import javax.xml.parsers.DocumentBuilderFactory;
    23  import javax.xml.parsers.ParserConfigurationException;
    24
    25  import net.sourceforge.pmd.lang.ast.ParseException;
    26  import net.sourceforge.pmd.lang.ast.RootNode;
    27  import net.sourceforge.pmd.lang.ast.xpath.Attribute;
    28  import net.sourceforge.pmd.lang.xml.XmlParserOptions;
    29  import net.sourceforge.pmd.util.CompoundIterator;
    30
    31  import org.w3c.dom.Document;
    32  import org.w3c.dom.NamedNodeMap;
    33  import org.w3c.dom.Node;
    34  import org.w3c.dom.Text;
    35  import org.xml.sax.InputSource;
    36  import org.xml.sax.SAXException;
    37
    38  public class XmlParser {
    39      protected final XmlParserOptions parserOptions;
    40      protected Map<Node, XmlNode> nodeCache = new HashMap<Node, XmlNode>();
    41
    42      public XmlParser(XmlParserOptions parserOptions) {
    43  	this.parserOptions = parserOptions;
    44      }
    45
    46      protected Document parseDocument(Reader reader) throws ParseException {
    47  	nodeCache.clear();
    48  	try {
    49  	    DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
    50  	    documentBuilderFactory.setCoalescing(parserOptions.isCoalescing());
    51  	    documentBuilderFactory.setExpandEntityReferences(parserOptions.isExpandEntityReferences());
    52  	    documentBuilderFactory.setIgnoringComments(parserOptions.isIgnoringComments());
    53  	    documentBuilderFactory.setIgnoringElementContentWhitespace(parserOptions.isIgnoringElementContentWhitespace());
    54  	    documentBuilderFactory.setNamespaceAware(parserOptions.isNamespaceAware());
    55  	    documentBuilderFactory.setValidating(parserOptions.isValidating());
    56  	    documentBuilderFactory.setXIncludeAware(parserOptions.isXincludeAware());
    57
    58
    59  	    DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
    60  	    documentBuilder.setEntityResolver(parserOptions.getEntityResolver());
    61  	    Document document = documentBuilder.parse(new InputSource(reader));
    62  	    return document;
    63  	} catch (ParserConfigurationException e) {
    64  	    throw new ParseException(e);
    65  	} catch (SAXException e) {
    66  	    throw new ParseException(e);
    67  	} catch (IOException e) {
    68  	    throw new ParseException(e);
    69  	}
    70      }
    71
    72      public XmlNode parse(Reader reader) {
    73  	Document document = parseDocument(reader);
    74  	return createProxy(document);
    75      }
    76
    77      public XmlNode createProxy(Node node) {
    78  	XmlNode proxy = nodeCache.get(node);
    79  	if (proxy != null) {
    80  	    return proxy;
    81  	}
    82
    83  	// TODO Change Parser interface to take ClassLoader?
    84  	LinkedHashSet<Class<?>> interfaces = new LinkedHashSet<Class<?>>();
    85  	interfaces.add(XmlNode.class);
    86  	if (node instanceof Document) {
    87  	    interfaces.add(RootNode.class);
    88  	}
    89  	addAllInterfaces(interfaces, node.getClass());
    90
    91  	proxy = (XmlNode) Proxy.newProxyInstance(XmlParser.class.getClassLoader(), interfaces
    92  		.toArray(new Class[interfaces.size()]), new XmlNodeInvocationHandler(node));
    93  	nodeCache.put(node, proxy);
    94  	return proxy;
    95      }
    96
    97      public void addAllInterfaces(Set<Class<?>> interfaces, Class<?> clazz) {
    98  	interfaces.addAll(Arrays.asList((Class<?>[]) clazz.getInterfaces()));
    99  	if (clazz.getSuperclass() != null) {
    100 	    addAllInterfaces(interfaces, clazz.getSuperclass());
    101 	}
    102     }
    103
    104     public class XmlNodeInvocationHandler implements InvocationHandler {
    105 	private final Node node;
    106 	private Object userData;
    107
    108 	public XmlNodeInvocationHandler(Node node) {
    109 	    this.node = node;
    110 	}
    111
    112 	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    113 	    // XmlNode method?
    114 	    if (method.getDeclaringClass().isAssignableFrom(XmlNode.class)
    115 		    && !"java.lang.Object".equals(method.getDeclaringClass().getName())) {
    116 		if ("jjtGetNumChildren".equals(method.getName())) {
    117 		    return node.hasChildNodes() ? node.getChildNodes().getLength() : 0;
    118 		} else if ("jjtGetChild".equals(method.getName())) {
    119 		    return createProxy(node.getChildNodes().item(((Integer) args[0]).intValue()));
    120 		} else if ("getImage".equals(method.getName())) {
    121 		    if (node instanceof Text) {
    122 			return ((Text) node).getData();
    123 		    } else {
    124 			return null;
    125 		    }
    126 		} else if ("jjtGetParent".equals(method.getName())) {
    127 		    Node parent = node.getParentNode();
    128 		    if (parent != null && !(parent instanceof Document)) {
    129 			return createProxy(parent);
    130 		    } else {
    131 			return null;
    132 		    }
    133 		} else if ("getAttributeIterator".equals(method.getName())) {
    134 		    List<Iterator<Attribute>> iterators = new ArrayList<Iterator<Attribute>>();
    135
    136 		    // Expose DOM Attributes
    137 		    final NamedNodeMap attributes = node.getAttributes();
    138 		    iterators.add(new Iterator<Attribute>() {
    139 			private int index;
    140
    141 			public boolean hasNext() {
    142 			    return attributes != null && index < attributes.getLength();
    143 			}
    144
    145 			public Attribute next() {
    146 			    Node attributeNode = attributes.item(index++);
    147 			    return new Attribute(createProxy(node), attributeNode.getNodeName(), attributeNode
    148 				    .getNodeValue());
    149 			}
    150
    151 			public void remove() {
    152 			    throw new UnsupportedOperationException();
    153 			}
    154 		    });
    155
    156 		    // Expose Text/CDATA nodes to have an 'Image' attribute like AST Nodes
    157 		    if (proxy instanceof Text) {
    158 			iterators.add(Collections.singletonList(
    159 				new Attribute((net.sourceforge.pmd.lang.ast.Node) proxy, "Image", ((Text) proxy)
    160 					.getData())).iterator());
    161 		    }
    162
    163 		    // Expose Java Attributes
    164 		    // iterators.add(new AttributeAxisIterator((net.sourceforge.pmd.lang.ast.Node) p));
    165
    166 		    return new CompoundIterator<Attribute>(iterators.toArray(new Iterator[iterators.size()]));
    167 		} else if ("getBeginLine".equals(method.getName())) {
    168 		    return Integer.valueOf(-1);
    169 		} else if ("getBeginColumn".equals(method.getName())) {
    170 		    return Integer.valueOf(-1);
    171 		} else if ("getEndLine".equals(method.getName())) {
    172 		    return Integer.valueOf(-1);
    173 		} else if ("getEndColumn".equals(method.getName())) {
    174 		    return Integer.valueOf(-1);
    175 		} else if ("getNode".equals(method.getName())) {
    176 		    return node;
    177 		} else if ("getUserData".equals(method.getName())) {
    178 		    return userData;
    179 		} else if ("setUserData".equals(method.getName())) {
    180 		    userData = args[0];
    181 		    return null;
    182 		}
    183 		throw new UnsupportedOperationException("Method not supported for XmlNode: " + method);
    184 	    }
    185 	    // Delegate method
    186 	    else {
    187 		if ("toString".equals(method.getName())) {
    188 		    String s = node.getNodeName();
    189 		    s = s.replace("#", "");
    190 		    return s;
    191 		}
    192 		Object result = method.invoke(node, args);
    193 		return result;
    194 	    }
    195 	}
    196     }
    197 }