1
2
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
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
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
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
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
164
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
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 }