001 /**
002 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003 */
004 package net.sourceforge.pmd.lang.xml.ast;
005
006 import java.io.IOException;
007 import java.io.Reader;
008 import java.lang.reflect.InvocationHandler;
009 import java.lang.reflect.Method;
010 import java.lang.reflect.Proxy;
011 import java.util.ArrayList;
012 import java.util.Arrays;
013 import java.util.Collections;
014 import java.util.HashMap;
015 import java.util.Iterator;
016 import java.util.LinkedHashSet;
017 import java.util.List;
018 import java.util.Map;
019 import java.util.Set;
020
021 import javax.xml.parsers.DocumentBuilder;
022 import javax.xml.parsers.DocumentBuilderFactory;
023 import javax.xml.parsers.ParserConfigurationException;
024
025 import net.sourceforge.pmd.lang.ast.ParseException;
026 import net.sourceforge.pmd.lang.ast.RootNode;
027 import net.sourceforge.pmd.lang.ast.xpath.Attribute;
028 import net.sourceforge.pmd.util.CompoundIterator;
029
030 import org.w3c.dom.Document;
031 import org.w3c.dom.NamedNodeMap;
032 import org.w3c.dom.Node;
033 import org.w3c.dom.Text;
034 import org.xml.sax.InputSource;
035 import org.xml.sax.SAXException;
036
037 public class XmlParser {
038
039 protected Map<Node, XmlNode> nodeCache = new HashMap<Node, XmlNode>();
040
041 protected Document parseDocument(Reader reader) throws ParseException {
042 nodeCache.clear();
043 try {
044 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
045 DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
046 Document document = documentBuilder.parse(new InputSource(reader));
047 return document;
048 } catch (ParserConfigurationException e) {
049 throw new ParseException(e);
050 } catch (SAXException e) {
051 throw new ParseException(e);
052 } catch (IOException e) {
053 throw new ParseException(e);
054 }
055 }
056
057 public XmlNode parse(Reader reader) {
058 Document document = parseDocument(reader);
059 return createProxy(document.getDocumentElement());
060 }
061
062 public XmlNode createProxy(Node node) {
063 XmlNode proxy = nodeCache.get(node);
064 if (proxy != null) {
065 return proxy;
066 }
067
068 // TODO Change Parser interface to take ClassLoader?
069 LinkedHashSet<Class<?>> interfaces = new LinkedHashSet<Class<?>>();
070 interfaces.add(XmlNode.class);
071 if (node.getParentNode() instanceof Document) {
072 interfaces.add(RootNode.class);
073 }
074 addAllInterfaces(interfaces, node.getClass());
075
076 proxy = (XmlNode) Proxy.newProxyInstance(XmlParser.class.getClassLoader(), interfaces
077 .toArray(new Class[interfaces.size()]), new XmlNodeInvocationHandler(node));
078 nodeCache.put(node, proxy);
079 return proxy;
080 }
081
082 public void addAllInterfaces(Set<Class<?>> interfaces, Class<?> clazz) {
083 interfaces.addAll(Arrays.asList((Class<?>[]) clazz.getInterfaces()));
084 if (clazz.getSuperclass() != null) {
085 addAllInterfaces(interfaces, clazz.getSuperclass());
086 }
087 }
088
089 public class XmlNodeInvocationHandler implements InvocationHandler {
090 private final Node node;
091
092 public XmlNodeInvocationHandler(Node node) {
093 this.node = node;
094 }
095
096 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
097 // XmlNode method?
098 if (method.getDeclaringClass().isAssignableFrom(XmlNode.class)
099 && !"java.lang.Object".equals(method.getDeclaringClass().getName())) {
100 if ("jjtGetNumChildren".equals(method.getName())) {
101 return node.hasChildNodes() ? node.getChildNodes().getLength() : 0;
102 } else if ("jjtGetChild".equals(method.getName())) {
103 return createProxy(node.getChildNodes().item(((Integer) args[0]).intValue()));
104 } else if ("getImage".equals(method.getName())) {
105 if (node instanceof Text) {
106 return ((Text) node).getData();
107 } else {
108 return null;
109 }
110 } else if ("jjtGetParent".equals(method.getName())) {
111 Node parent = node.getParentNode();
112 if (parent != null && !(parent instanceof Document)) {
113 return createProxy(parent);
114 } else {
115 return null;
116 }
117 } else if ("getAttributeIterator".equals(method.getName())) {
118 List<Iterator<Attribute>> iterators = new ArrayList();
119
120 // Expose DOM Attributes
121 final NamedNodeMap attributes = node.getAttributes();
122 iterators.add(new Iterator<Attribute>() {
123 private int index;
124
125 public boolean hasNext() {
126 return attributes != null && index < attributes.getLength();
127 }
128
129 public Attribute next() {
130 Node attributeNode = attributes.item(index++);
131 return new Attribute(createProxy(node), attributeNode.getNodeName(), attributeNode
132 .getNodeValue());
133 }
134
135 public void remove() {
136 throw new UnsupportedOperationException();
137 }
138 });
139
140 // Expose Text/CDATA nodes to have an 'Image' attribute like AST Nodes
141 if (proxy instanceof Text) {
142 iterators.add(Collections.singletonList(
143 new Attribute((net.sourceforge.pmd.lang.ast.Node) proxy, "Image", ((Text) proxy)
144 .getData())).iterator());
145 }
146
147 // Expose Java Attributes
148 // iterators.add(new AttributeAxisIterator((net.sourceforge.pmd.lang.ast.Node) p));
149
150 return new CompoundIterator<Attribute>(iterators.toArray(new Iterator[iterators.size()]));
151 } else if ("getBeginLine".equals(method.getName())) {
152 return Integer.valueOf(-1);
153 } else if ("getBeginColumn".equals(method.getName())) {
154 return Integer.valueOf(-1);
155 } else if ("getEndLine".equals(method.getName())) {
156 return Integer.valueOf(-1);
157 } else if ("getEndColumn".equals(method.getName())) {
158 return Integer.valueOf(-1);
159 } else if ("getNode".equals(method.getName())) {
160 return node;
161 }
162 throw new UnsupportedOperationException("Method not supported for XmlNode: " + method);
163 }
164 // Delegate method
165 else {
166 if ("toString".equals(method.getName())) {
167 String s = ((Node) node).getNodeName();
168 s = s.replace("#", "");
169 return s;
170 }
171 Object result = method.invoke(node, args);
172 return result;
173 }
174 }
175 }
176 }
|