001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2016
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 */
028package edu.wisc.ssec.mcidasv.util;
029
030import static java.util.Objects.requireNonNull;
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
058/**
059 * Documentation is still forthcoming, but remember that <b>no methods accept 
060 * {@code null} parameters!</b>
061 */
062public final class XPathUtils {
063
064    /**
065     * Maps (and caches) the XPath {@link String} to its compiled
066     * {@link XPathExpression}.
067     */
068    private static final Map<String, XPathExpression> pathMap =
069        new ConcurrentHashMap<>();
070
071    /**
072     * Thou shalt not create an instantiation of this class!
073     */
074    private XPathUtils() {}
075
076    public static XPathExpression expr(String xPath) {
077        requireNonNull(xPath, "Cannot compile a null string");
078
079        XPathExpression expr = pathMap.get(xPath);
080        if (expr == null) {
081            try {
082                expr = XPathFactory.newInstance().newXPath().compile(xPath);
083                pathMap.put(xPath, expr);
084            } catch (XPathExpressionException e) {
085                throw new RuntimeException("Error compiling xpath", e);
086            }
087        }
088        return expr;
089    }
090
091    public static List<Node> eval(final XmlResourceCollection collection, final String xPath) {
092        requireNonNull(collection, "Cannot search a null resource collection");
093        requireNonNull(xPath, "Cannot search using a null XPath query");
094
095        try {
096            List<Node> nodeList = new ArrayList<>();
097            XPathExpression expression = expr(xPath);
098
099            // Resources are the only things added to the list returned by 
100            // getResources().
101            @SuppressWarnings("unchecked")
102            List<Resource> files = collection.getResources();
103
104            for (int i = 0; i < files.size(); i++) {
105                if (!collection.isValid(i)) {
106                    continue;
107                }
108
109                InputStream in = XPathUtils.class.getResourceAsStream(files.get(i).toString());
110                if (in == null) {
111                    continue;
112                }
113
114                NodeList tmpList = (NodeList)expression.evaluate(loadXml(in), XPathConstants.NODESET);
115                for (int j = 0; j < tmpList.getLength(); j++) {
116                    nodeList.add(tmpList.item(j));
117                }
118            }
119            return nodeList;
120        } catch (XPathExpressionException e) {
121            throw new RuntimeException("Error evaluating xpath", e);
122        }
123    }
124
125    public static NodeList eval(final String xmlFile, final String xPath) {
126        requireNonNull(xmlFile, "Null path to a XML file");
127        requireNonNull(xPath, "Cannot search using a null XPath query");
128
129        try {
130            return (NodeList)expr(xPath).evaluate(loadXml(xmlFile), XPathConstants.NODESET);
131        } catch (XPathExpressionException e) {
132            throw new RuntimeException("Error evaluation xpath", e);
133        }
134    }
135
136    public static NodeList eval(final Node root, final String xPath) {
137        requireNonNull(root, "Cannot search a null root node");
138        requireNonNull(xPath, "Cannot search using a null XPath query");
139
140        try {
141            return (NodeList)expr(xPath).evaluate(root, XPathConstants.NODESET);
142        } catch (XPathExpressionException e) {
143            throw new RuntimeException("Error evaluation xpath", e);
144        }
145    }
146
147    public static List<Node> nodes(final IntegratedDataViewer idv, final IdvResource collectionId, final String xPath) {
148        requireNonNull(idv);
149        requireNonNull(collectionId);
150        requireNonNull(xPath);
151
152        XmlResourceCollection collection = idv.getResourceManager().getXmlResources(collectionId);
153        return nodes(collection, xPath);
154    }
155
156    public static List<Node> nodes(final XmlResourceCollection collection, final String xPath) {
157        requireNonNull(collection);
158        requireNonNull(xPath);
159        return eval(collection, xPath);
160    }
161
162    public static NodeListIterator nodes(final String xmlFile, final String xPath) {
163        requireNonNull(xmlFile);
164        requireNonNull(xPath);
165        return new NodeListIterator(eval(xmlFile, xPath));
166    }
167
168    public static NodeListIterator nodes(final Node root, final String xPath) {
169        requireNonNull(root);
170        requireNonNull(xPath);
171        return new NodeListIterator(eval(root, xPath));
172    }
173
174    public static NodeListIterator nodes(final Node root) {
175        requireNonNull(root);
176        return nodes(root, "//*");
177    }
178
179    public static List<Element> elements(final IntegratedDataViewer idv, final IdvResource collectionId, final String xPath) {
180        requireNonNull(idv);
181        requireNonNull(collectionId);
182        requireNonNull(xPath);
183
184        XmlResourceCollection collection = idv.getResourceManager().getXmlResources(collectionId);
185        return elements(collection, xPath);
186    }
187
188    public static List<Element> elements(final XmlResourceCollection collection, final String xPath) {
189        requireNonNull(collection);
190        requireNonNull(xPath);
191        List<Element> elements = new ArrayList<>();
192        for (Node n : eval(collection, xPath)) {
193            elements.add((Element)n);
194        }
195        return elements;
196    }
197
198    public static ElementListIterator elements(final String xmlFile, final String xPath) {
199        requireNonNull(xmlFile);
200        requireNonNull(xPath);
201        return new ElementListIterator(eval(xmlFile, xPath));
202    }
203
204    public static ElementListIterator elements(final Node root) {
205        requireNonNull(root);
206        return elements(root, "//*");
207    }
208
209    public static ElementListIterator elements(final Node root, final String xPath) {
210        requireNonNull(root);
211        requireNonNull(xPath);
212        return new ElementListIterator(eval(root, xPath));
213    }
214
215    public static Document loadXml(final String xmlFile) {
216        requireNonNull(xmlFile);
217
218        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
219        factory.setNamespaceAware(false);
220        try {
221            DocumentBuilder builder = factory.newDocumentBuilder();
222            return builder.parse(xmlFile);
223        } catch (Exception e) {
224            throw new RuntimeException("Error loading XML file: "+e.getMessage(), e);
225        }
226    }
227
228    public static Document loadXml(final File xmlFile) {
229        requireNonNull(xmlFile);
230
231        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
232        factory.setNamespaceAware(false);
233        try {
234            DocumentBuilder builder = factory.newDocumentBuilder();
235            return builder.parse(xmlFile);
236        } catch (Exception e) {
237            throw new RuntimeException("Error loading XML file: "+e.getMessage(), e);
238        }
239    }
240
241    public static Document loadXml(final InputStream in) {
242        requireNonNull(in);
243        
244        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
245        factory.setNamespaceAware(false);
246        try {
247            DocumentBuilder builder = factory.newDocumentBuilder();
248            return builder.parse(in);
249        } catch (Exception e) {
250            throw new RuntimeException("Error loading XML from input stream: "+e.getMessage(), e);
251        }
252    }
253
254    public static class NodeListIterator implements Iterable<Node>, Iterator<Node> {
255        private final NodeList nodeList;
256        private int index = 0;
257
258        public NodeListIterator(final NodeList nodeList) {
259            requireNonNull(nodeList);
260            this.nodeList = nodeList;
261        }
262
263        public Iterator<Node> iterator() {
264            return this;
265        }
266
267        public boolean hasNext() {
268            return index < nodeList.getLength();
269        }
270
271        public Node next() {
272            return nodeList.item(index++);
273        }
274
275        public void remove() {
276            throw new UnsupportedOperationException("not implemented");
277        }
278    }
279
280    public static class ElementListIterator implements Iterable<Element>, Iterator<Element> {
281        private final NodeList nodeList;
282        private int index = 0;
283
284        public ElementListIterator(final NodeList nodeList) {
285            requireNonNull(nodeList);
286            this.nodeList = nodeList;
287        }
288
289        public Iterator<Element> iterator() {
290            return this;
291        }
292
293        public boolean hasNext() {
294            return index < nodeList.getLength();
295        }
296
297        public Element next() {
298            return (Element)nodeList.item(index++);
299        }
300
301        public void remove() {
302            throw new UnsupportedOperationException("not implemented");
303        }
304    }
305}