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