001/*
002 * $Id: XPathUtils.java,v 1.8 2011/03/24 16:06:35 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2011
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 */
030package edu.wisc.ssec.mcidasv.util;
031
032import java.io.File;
033import java.io.InputStream;
034import java.util.ArrayList;
035import java.util.Iterator;
036import java.util.List;
037import java.util.Map;
038import java.util.concurrent.ConcurrentHashMap;
039
040import javax.xml.parsers.DocumentBuilder;
041import javax.xml.parsers.DocumentBuilderFactory;
042import javax.xml.xpath.XPathConstants;
043import javax.xml.xpath.XPathExpression;
044import javax.xml.xpath.XPathExpressionException;
045import javax.xml.xpath.XPathFactory;
046
047import org.w3c.dom.Document;
048import org.w3c.dom.Element;
049import org.w3c.dom.Node;
050import org.w3c.dom.NodeList;
051
052import ucar.unidata.idv.IntegratedDataViewer;
053import ucar.unidata.idv.IdvResourceManager.IdvResource;
054import ucar.unidata.idv.IdvResourceManager.XmlIdvResource;
055import ucar.unidata.util.ResourceCollection.Resource;
056import ucar.unidata.xml.XmlResourceCollection;
057
058import 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 */
064public 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}