Coverage Report - org.xembly.XpathDirective
 
Classes in this File Line Coverage Branch Coverage Complexity
XpathDirective
79%
34/43
46%
15/32
2.714
XpathDirective$1
100%
2/2
N/A
2.714
 
 1  
 /**
 2  
  * Copyright (c) 2013-2017, xembly.org
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions
 7  
  * are met: 1) Redistributions of source code must retain the above
 8  
  * copyright notice, this list of conditions and the following
 9  
  * disclaimer. 2) Redistributions in binary form must reproduce the above
 10  
  * copyright notice, this list of conditions and the following
 11  
  * disclaimer in the documentation and/or other materials provided
 12  
  * with the distribution. 3) Neither the name of the xembly.org nor
 13  
  * the names of its contributors may be used to endorse or promote
 14  
  * products derived from this software without specific prior written
 15  
  * permission.
 16  
  *
 17  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 19  
  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 20  
  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 21  
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 22  
  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23  
  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 24  
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 25  
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 26  
  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 27  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 28  
  * OF THE POSSIBILITY OF SUCH DAMAGE.
 29  
  */
 30  
 package org.xembly;
 31  
 
 32  
 import java.util.Collection;
 33  
 import java.util.Collections;
 34  
 import java.util.HashSet;
 35  
 import java.util.regex.Matcher;
 36  
 import java.util.regex.Pattern;
 37  
 import javax.xml.xpath.XPath;
 38  
 import javax.xml.xpath.XPathConstants;
 39  
 import javax.xml.xpath.XPathExpressionException;
 40  
 import javax.xml.xpath.XPathFactory;
 41  
 import lombok.EqualsAndHashCode;
 42  
 import org.w3c.dom.Document;
 43  
 import org.w3c.dom.Node;
 44  
 import org.w3c.dom.NodeList;
 45  
 
 46  
 /**
 47  
  * XPATH directive.
 48  
  *
 49  
  * <p>The class is immutable and thread-safe.
 50  
  *
 51  
  * @author Yegor Bugayenko (yegor256@gmail.com)
 52  
  * @version $Id: d6a654a8967d1121bb2fcd6f7a83c1b460760345 $
 53  
  * @since 0.1
 54  
  */
 55  0
 @EqualsAndHashCode(of = "expr")
 56  
 final class XpathDirective implements Directive {
 57  
 
 58  
     /**
 59  
      * XPath factory.
 60  
      */
 61  1
     private static final ThreadLocal<XPathFactory> FACTORY =
 62  8
         new ThreadLocal<XPathFactory>() {
 63  
             @Override
 64  
             protected XPathFactory initialValue() {
 65  7
                 return XPathFactory.newInstance();
 66  
             }
 67  
         };
 68  
 
 69  
     /**
 70  
      * Pattern to match root-only XPath queries.
 71  
      */
 72  2
     private static final Pattern ROOT_ONLY =
 73  1
         Pattern.compile("/([^/\\(\\[\\{:]+)");
 74  
 
 75  
     /**
 76  
      * XPath to use.
 77  
      */
 78  
     private final transient Arg expr;
 79  
 
 80  
     /**
 81  
      * Public ctor.
 82  
      * @param path XPath
 83  
      * @throws XmlContentException If invalid input
 84  
      */
 85  25
     XpathDirective(final String path) throws XmlContentException {
 86  25
         this.expr = new Arg(path);
 87  25
     }
 88  
 
 89  
     @Override
 90  
     public String toString() {
 91  2
         return String.format("XPATH %s", this.expr);
 92  
     }
 93  
 
 94  
     @Override
 95  
     public Directive.Cursor exec(final Node dom,
 96  
         final Directive.Cursor cursor, final Directive.Stack stack)
 97  
         throws ImpossibleModificationException {
 98  
         final Collection<Node> targets;
 99  20
         final String query = this.expr.raw();
 100  20
         final Matcher matcher = XpathDirective.ROOT_ONLY.matcher(query);
 101  20
         if (matcher.matches()) {
 102  11
             targets = XpathDirective.rootOnly(matcher.group(1), dom);
 103  
         } else {
 104  9
             targets = XpathDirective.traditional(query, dom, cursor);
 105  
         }
 106  20
         return new DomCursor(targets);
 107  
     }
 108  
 
 109  
     /**
 110  
      * Fetches only root node.
 111  
      * @param root Root node name
 112  
      * @param dom Document
 113  
      * @return Found nodes
 114  
      */
 115  
     private static Collection<Node> rootOnly(final String root,
 116  
         final Node dom) {
 117  
         final Node target;
 118  11
         if (dom.getOwnerDocument() == null) {
 119  11
             target = Document.class.cast(dom).getDocumentElement();
 120  
         } else {
 121  0
             target = dom.getOwnerDocument().getDocumentElement();
 122  
         }
 123  
         final Collection<Node> targets;
 124  11
         if (root != null && target != null
 125  10
             && ("*".equals(root) || target.getNodeName().equals(root))) {
 126  9
             targets = Collections.singletonList(target);
 127  
         } else {
 128  2
             targets = Collections.emptyList();
 129  
         }
 130  11
         return targets;
 131  
     }
 132  
 
 133  
     /**
 134  
      * Fetch them in traditional way.
 135  
      * @param query XPath query
 136  
      * @param dom Document
 137  
      * @param current Nodes we're currently at
 138  
      * @return Found nodes
 139  
      * @throws ImpossibleModificationException If fails
 140  
      */
 141  
     private static Collection<Node> traditional(final String query,
 142  
         final Node dom, final Collection<Node> current)
 143  
         throws ImpossibleModificationException {
 144  9
         final XPath xpath = XpathDirective.FACTORY.get().newXPath();
 145  9
         final Collection<Node> targets = new HashSet<Node>(0);
 146  9
         for (final Node node : XpathDirective.roots(dom, current)) {
 147  
             final NodeList list;
 148  
             try {
 149  18
                 list = NodeList.class.cast(
 150  9
                     xpath.evaluate(query, node, XPathConstants.NODESET)
 151  
                 );
 152  0
             } catch (final XPathExpressionException ex) {
 153  0
                 throw new ImpossibleModificationException(
 154  0
                     String.format("invalid XPath expr '%s'", query), ex
 155  
                 );
 156  9
             }
 157  9
             final int len = list.getLength();
 158  24
             for (int idx = 0; idx < len; ++idx) {
 159  15
                 targets.add(list.item(idx));
 160  
             }
 161  9
         }
 162  9
         return targets;
 163  
     }
 164  
 
 165  
     /**
 166  
      * Get roots to start searching from.
 167  
      * @param dom Document
 168  
      * @param nodes Current nodes
 169  
      * @return Root nodes to start searching from
 170  
      */
 171  
     private static Iterable<Node> roots(final Node dom,
 172  
         final Collection<Node> nodes) {
 173  
         final Collection<Node> roots;
 174  9
         if (nodes.isEmpty()) {
 175  0
             if (dom.getOwnerDocument() == null) {
 176  0
                 roots = Collections.singletonList(dom);
 177  
             } else {
 178  0
                 roots = Collections.<Node>singletonList(
 179  0
                     dom.getOwnerDocument().getDocumentElement()
 180  
                 );
 181  
             }
 182  
         } else {
 183  9
             roots = nodes;
 184  
         }
 185  9
         return roots;
 186  
     }
 187  
 
 188  
 }