XmlParser.java
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 readerthrows 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 = (XmlNodeProxy.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[] argsthrows 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(((Integerargs[0]).intValue()));
104     else if ("getImage".equals(method.getName())) {
105         if (node instanceof Text) {
106       return ((Textnode).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.Nodeproxy, "Image"((Textproxy)
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 = ((Nodenode).getNodeName();
168         s = s.replace("#""");
169         return s;
170     }
171     Object result = method.invoke(node, args);
172     return result;
173       }
174   }
175     }
176 }