001    /*
002     * This file is part of McIDAS-V
003     *
004     * Copyright 2007-2013
005     * Space Science and Engineering Center (SSEC)
006     * University of Wisconsin - Madison
007     * 1225 W. Dayton Street, Madison, WI 53706, USA
008     * https://www.ssec.wisc.edu/mcidas
009     * 
010     * All Rights Reserved
011     * 
012     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013     * some McIDAS-V source code is based on IDV and VisAD source code.  
014     * 
015     * McIDAS-V is free software; you can redistribute it and/or modify
016     * it under the terms of the GNU Lesser Public License as published by
017     * the Free Software Foundation; either version 3 of the License, or
018     * (at your option) any later version.
019     * 
020     * McIDAS-V is distributed in the hope that it will be useful,
021     * but WITHOUT ANY WARRANTY; without even the implied warranty of
022     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023     * GNU Lesser Public License for more details.
024     * 
025     * You should have received a copy of the GNU Lesser Public License
026     * along with this program.  If not, see http://www.gnu.org/licenses.
027     */
028    package edu.wisc.ssec.mcidasv.util;
029    
030    import java.io.File;
031    import java.io.InputStream;
032    import java.util.ArrayList;
033    import java.util.Iterator;
034    import java.util.List;
035    import java.util.Map;
036    import java.util.concurrent.ConcurrentHashMap;
037    
038    import javax.xml.parsers.DocumentBuilder;
039    import javax.xml.parsers.DocumentBuilderFactory;
040    import javax.xml.xpath.XPathConstants;
041    import javax.xml.xpath.XPathExpression;
042    import javax.xml.xpath.XPathExpressionException;
043    import javax.xml.xpath.XPathFactory;
044    
045    import org.w3c.dom.Document;
046    import org.w3c.dom.Element;
047    import org.w3c.dom.Node;
048    import org.w3c.dom.NodeList;
049    
050    import ucar.unidata.idv.IntegratedDataViewer;
051    import ucar.unidata.idv.IdvResourceManager.IdvResource;
052    import ucar.unidata.idv.IdvResourceManager.XmlIdvResource;
053    import ucar.unidata.util.ResourceCollection.Resource;
054    import ucar.unidata.xml.XmlResourceCollection;
055    
056    import edu.wisc.ssec.mcidasv.util.Contract;
057    
058    /**
059     * Documentation is still forthcoming, but remember that <b>no methods accept 
060     * {@code null} parameters!</b>
061     */
062    public final class XPathUtils {
063    
064        /** Maps (and caches) the XPath {@link String} to its compiled {@link XPathExpression}. */
065        private static final Map<String, XPathExpression> pathMap = new ConcurrentHashMap<String, XPathExpression>();
066    
067        /**
068         * Thou shalt not create an instantiation of this class!
069         */
070        private XPathUtils() {}
071    
072        public static XPathExpression expr(String xPath) {
073            Contract.notNull(xPath, "Cannot compile a null string");
074    
075            XPathExpression expr = pathMap.get(xPath);
076            if (expr == null) {
077                try {
078                    expr = XPathFactory.newInstance().newXPath().compile(xPath);
079                    pathMap.put(xPath, expr);
080                } catch (XPathExpressionException e) {
081                    throw new RuntimeException("Error compiling xpath", e);
082                }
083            }
084            return expr;
085        }
086    
087        public static List<Node> eval(final XmlResourceCollection collection, final String xPath) {
088            Contract.notNull(collection, "Cannot search a null resource collection");
089            Contract.notNull(xPath, "Cannot search using a null XPath query");
090    
091            try {
092                List<Node> nodeList = new ArrayList<Node>();
093                XPathExpression expression = expr(xPath);
094    
095                // Resources are the only things added to the list returned by 
096                // getResources().
097                @SuppressWarnings("unchecked")
098                List<Resource> files = collection.getResources();
099    
100                for (int i = 0; i < files.size(); i++) {
101                    if (!collection.isValid(i))
102                        continue;
103    
104                    InputStream in = XPathUtils.class.getResourceAsStream(files.get(i).toString());
105                    if (in == null)
106                        continue;
107    
108                    NodeList tmpList = (NodeList)expression.evaluate(loadXml(in), XPathConstants.NODESET);
109                    for (int j = 0; j < tmpList.getLength(); j++) {
110                        nodeList.add(tmpList.item(j));
111                    }
112                }
113                return nodeList;
114            } catch (XPathExpressionException e) {
115                throw new RuntimeException("Error evaluating xpath", e);
116            }
117        }
118    
119        public static NodeList eval(final String xmlFile, final String xPath) {
120            Contract.notNull(xmlFile, "Null path to a XML file");
121            Contract.notNull(xPath, "Cannot search using a null XPath query");
122    
123            try {
124                return (NodeList)expr(xPath).evaluate(loadXml(xmlFile), XPathConstants.NODESET);
125            } catch (XPathExpressionException e) {
126                throw new RuntimeException("Error evaluation xpath", e);
127            }
128        }
129    
130        public static NodeList eval(final Node root, final String xPath) {
131            Contract.notNull(root, "Cannot search a null root node");
132            Contract.notNull(xPath, "Cannot search using a null XPath query");
133    
134            try {
135                return (NodeList)expr(xPath).evaluate(root, XPathConstants.NODESET);
136            } catch (XPathExpressionException e) {
137                throw new RuntimeException("Error evaluation xpath", e);
138            }
139        }
140    
141        public static List<Node> nodes(final IntegratedDataViewer idv, final IdvResource collectionId, final String xPath) {
142            Contract.notNull(idv);
143            Contract.notNull(collectionId);
144            Contract.notNull(xPath);
145    
146            XmlResourceCollection collection = idv.getResourceManager().getXmlResources(collectionId);
147            return nodes(collection, xPath);
148        }
149    
150        public static List<Node> nodes(final XmlResourceCollection collection, final String xPath) {
151            Contract.notNull(collection);
152            Contract.notNull(xPath);
153            return eval(collection, xPath);
154        }
155    
156        public static NodeListIterator nodes(final String xmlFile, final String xPath) {
157            Contract.notNull(xmlFile);
158            Contract.notNull(xPath);
159            return new NodeListIterator(eval(xmlFile, xPath));
160        }
161    
162        public static NodeListIterator nodes(final Node root, final String xPath) {
163            Contract.notNull(root);
164            Contract.notNull(xPath);
165            return new NodeListIterator(eval(root, xPath));
166        }
167    
168        public static NodeListIterator nodes(final Node root) {
169            Contract.notNull(root);
170            return nodes(root, "//*");
171        }
172    
173        public static List<Element> elements(final IntegratedDataViewer idv, final IdvResource collectionId, final String xPath) {
174            Contract.notNull(idv);
175            Contract.notNull(collectionId);
176            Contract.notNull(xPath);
177    
178            XmlResourceCollection collection = idv.getResourceManager().getXmlResources(collectionId);
179            return elements(collection, xPath);
180        }
181    
182        public static List<Element> elements(final XmlResourceCollection collection, final String xPath) {
183            Contract.notNull(collection);
184            Contract.notNull(xPath);
185            List<Element> elements = new ArrayList<Element>();
186            for (Node n : eval(collection, xPath))
187                elements.add((Element)n);
188            return elements;
189        }
190    
191        public static ElementListIterator elements(final String xmlFile, final String xPath) {
192            Contract.notNull(xmlFile);
193            Contract.notNull(xPath);
194            return new ElementListIterator(eval(xmlFile, xPath));
195        }
196    
197        public static ElementListIterator elements(final Node root) {
198            Contract.notNull(root);
199            return elements(root, "//*");
200        }
201    
202        public static ElementListIterator elements(final Node root, final String xPath) {
203            Contract.notNull(root);
204            Contract.notNull(xPath);
205            return new ElementListIterator(eval(root, xPath));
206        }
207    
208        public static Document loadXml(final String xmlFile) {
209            Contract.notNull(xmlFile);
210    
211            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
212            factory.setNamespaceAware(false);
213            try {
214                DocumentBuilder builder = factory.newDocumentBuilder();
215                return builder.parse(xmlFile);
216            } catch (Exception e) {
217                throw new RuntimeException("Error loading XML file: "+e.getMessage(), e);
218            }
219        }
220    
221        public static Document loadXml(final File xmlFile) {
222            Contract.notNull(xmlFile);
223    
224            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
225            factory.setNamespaceAware(false);
226            try {
227                DocumentBuilder builder = factory.newDocumentBuilder();
228                return builder.parse(xmlFile);
229            } catch (Exception e) {
230                throw new RuntimeException("Error loading XML file: "+e.getMessage(), e);
231            }
232        }
233    
234        public static Document loadXml(final InputStream in) {
235            Contract.notNull(in);
236            
237            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
238            factory.setNamespaceAware(false);
239            try {
240                DocumentBuilder builder = factory.newDocumentBuilder();
241                return builder.parse(in);
242            } catch (Exception e) {
243                throw new RuntimeException("Error loading XML from input stream: "+e.getMessage(), e);
244            }
245        }
246    
247        public static class NodeListIterator implements Iterable<Node>, Iterator<Node> {
248            private final NodeList nodeList;
249            private int index = 0;
250    
251            public NodeListIterator(final NodeList nodeList) {
252                Contract.notNull(nodeList);
253                this.nodeList = nodeList;
254            }
255    
256            public Iterator<Node> iterator() {
257                return this;
258            }
259    
260            public boolean hasNext() {
261                return (index < nodeList.getLength());
262            }
263    
264            public Node next() {
265                return nodeList.item(index++);
266            }
267    
268            public void remove() {
269                throw new UnsupportedOperationException("not implemented");
270            }
271        }
272    
273        public static class ElementListIterator implements Iterable<Element>, Iterator<Element> {
274            private final NodeList nodeList;
275            private int index = 0;
276    
277            public ElementListIterator(final NodeList nodeList) {
278                Contract.notNull(nodeList);
279                this.nodeList = nodeList;
280            }
281    
282            public Iterator<Element> iterator() {
283                return this;
284            }
285    
286            public boolean hasNext() {
287                return (index < nodeList.getLength());
288            }
289    
290            public Element next() {
291                return (Element)nodeList.item(index++);
292            }
293    
294            public void remove() {
295                throw new UnsupportedOperationException("not implemented");
296            }
297        }
298    }