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 }