001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.xpath;
021
022import java.util.List;
023
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
026import com.puppycrawl.tools.checkstyle.utils.XpathUtil;
027import net.sf.saxon.om.AxisInfo;
028import net.sf.saxon.om.NodeInfo;
029import net.sf.saxon.tree.iter.ArrayIterator;
030import net.sf.saxon.tree.iter.AxisIterator;
031import net.sf.saxon.tree.iter.EmptyIterator;
032import net.sf.saxon.tree.iter.SingleNodeIterator;
033import net.sf.saxon.tree.util.Navigator;
034import net.sf.saxon.type.Type;
035
036/**
037 * Represents element node of Xpath-tree.
038 *
039 */
040public class ElementNode extends AbstractNode {
041
042    /** String literal for text attribute. */
043    private static final String TEXT_ATTRIBUTE_NAME = "text";
044
045    /** Constant for optimization. */
046    private static final AbstractNode[] EMPTY_ABSTRACT_NODE_ARRAY = new AbstractNode[0];
047
048    /** The root node. */
049    private final AbstractNode root;
050
051    /** The parent of the current node. */
052    private final AbstractNode parent;
053
054    /** The ast node. */
055    private final DetailAST detailAst;
056
057    /** Represents text of the DetailAST. */
058    private final String text;
059
060    /** Represents index among siblings. */
061    private final int indexAmongSiblings;
062
063    /** The text attribute node. */
064    private AttributeNode attributeNode;
065
066    /**
067     * Creates a new {@code ElementNode} instance.
068     *
069     * @param root {@code Node} root of the tree
070     * @param parent {@code Node} parent of the current node
071     * @param detailAst reference to {@code DetailAST}
072     */
073    public ElementNode(AbstractNode root, AbstractNode parent, DetailAST detailAst) {
074        super(root.getTreeInfo());
075        this.parent = parent;
076        this.root = root;
077        this.detailAst = detailAst;
078        text = TokenUtil.getTokenName(detailAst.getType());
079        indexAmongSiblings = parent.getChildren().size();
080        setDepth(parent.getDepth() + 1);
081        createTextAttribute();
082        createChildren();
083    }
084
085    /**
086     * Compares current object with specified for order.
087     *
088     * @param other another {@code NodeInfo} object
089     * @return number representing order of current object to specified one
090     */
091    @Override
092    public int compareOrder(NodeInfo other) {
093        int result = 0;
094        if (other instanceof AbstractNode) {
095            result = getDepth() - ((AbstractNode) other).getDepth();
096            if (result == 0) {
097                final ElementNode[] children = getCommonAncestorChildren(other);
098                result = children[0].indexAmongSiblings - children[1].indexAmongSiblings;
099            }
100        }
101        return result;
102    }
103
104    /**
105     * Finds the ancestors of the children whose parent is their common ancestor.
106     *
107     * @param other another {@code NodeInfo} object
108     * @return {@code ElementNode} immediate children(also ancestors of the given children) of the
109     *         common ancestor
110     */
111    private ElementNode[] getCommonAncestorChildren(NodeInfo other) {
112        NodeInfo child1 = this;
113        NodeInfo child2 = other;
114        while (!child1.getParent().equals(child2.getParent())) {
115            child1 = child1.getParent();
116            child2 = child2.getParent();
117        }
118        return new ElementNode[] {(ElementNode) child1, (ElementNode) child2};
119    }
120
121    /**
122     * Iterates children of the current node and
123     * recursively creates new Xpath-nodes.
124     */
125    private void createChildren() {
126        DetailAST currentChild = detailAst.getFirstChild();
127        while (currentChild != null) {
128            final AbstractNode child = new ElementNode(root, this, currentChild);
129            addChild(child);
130            currentChild = currentChild.getNextSibling();
131        }
132    }
133
134    /**
135     * Returns attribute value. Throws {@code UnsupportedOperationException} in case,
136     * when name of the attribute is not equal to 'text'.
137     *
138     * @param namespace namespace
139     * @param localPart actual name of the attribute
140     * @return attribute value
141     */
142    @Override
143    public String getAttributeValue(String namespace, String localPart) {
144        final String result;
145        if (TEXT_ATTRIBUTE_NAME.equals(localPart)) {
146            if (attributeNode == null) {
147                result = null;
148            }
149            else {
150                result = attributeNode.getStringValue();
151            }
152        }
153        else {
154            result = null;
155        }
156        return result;
157    }
158
159    /**
160     * Returns local part.
161     *
162     * @return local part
163     */
164    @Override
165    public String getLocalPart() {
166        return text;
167    }
168
169    /**
170     * Returns type of the node.
171     *
172     * @return node kind
173     */
174    @Override
175    public int getNodeKind() {
176        return Type.ELEMENT;
177    }
178
179    /**
180     * Returns parent.
181     *
182     * @return parent
183     */
184    @Override
185    public NodeInfo getParent() {
186        return parent;
187    }
188
189    /**
190     * Returns root.
191     *
192     * @return root
193     */
194    @Override
195    public NodeInfo getRoot() {
196        return root;
197    }
198
199    /**
200     * Determines axis iteration algorithm. Throws {@code UnsupportedOperationException} in case,
201     * when there is no axis iterator for given axisNumber.
202     *
203     * @param axisNumber element from {@code AxisInfo}
204     * @return {@code AxisIterator} object
205     */
206    @Override
207    public AxisIterator iterateAxis(byte axisNumber) {
208        final AxisIterator result;
209        switch (axisNumber) {
210            case AxisInfo.ANCESTOR:
211                try (AxisIterator iterator = new Navigator.AncestorEnumeration(this, false)) {
212                    result = iterator;
213                }
214                break;
215            case AxisInfo.ANCESTOR_OR_SELF:
216                try (AxisIterator iterator = new Navigator.AncestorEnumeration(this, true)) {
217                    result = iterator;
218                }
219                break;
220            case AxisInfo.ATTRIBUTE:
221                try (AxisIterator iterator = SingleNodeIterator.makeIterator(attributeNode)) {
222                    result = iterator;
223                }
224                break;
225            case AxisInfo.CHILD:
226                if (hasChildNodes()) {
227                    try (AxisIterator iterator = new ArrayIterator.OfNodes(
228                            getChildren().toArray(EMPTY_ABSTRACT_NODE_ARRAY))) {
229                        result = iterator;
230                    }
231                }
232                else {
233                    result = EmptyIterator.OfNodes.THE_INSTANCE;
234                }
235                break;
236            case AxisInfo.DESCENDANT:
237                if (hasChildNodes()) {
238                    try (AxisIterator iterator =
239                                 new Navigator.DescendantEnumeration(this, false, true)) {
240                        result = iterator;
241                    }
242                }
243                else {
244                    result = EmptyIterator.OfNodes.THE_INSTANCE;
245                }
246                break;
247            case AxisInfo.DESCENDANT_OR_SELF:
248                try (AxisIterator iterator =
249                             new Navigator.DescendantEnumeration(this, true, true)) {
250                    result = iterator;
251                }
252                break;
253            case AxisInfo.PARENT:
254                try (AxisIterator iterator = SingleNodeIterator.makeIterator(parent)) {
255                    result = iterator;
256                }
257                break;
258            case AxisInfo.SELF:
259                try (AxisIterator iterator = SingleNodeIterator.makeIterator(this)) {
260                    result = iterator;
261                }
262                break;
263            case AxisInfo.FOLLOWING_SIBLING:
264                result = getFollowingSiblingsIterator();
265                break;
266            case AxisInfo.PRECEDING_SIBLING:
267                result = getPrecedingSiblingsIterator();
268                break;
269            case AxisInfo.FOLLOWING:
270                try (AxisIterator iterator = new FollowingEnumeration(this)) {
271                    result = iterator;
272                }
273                break;
274            case AxisInfo.PRECEDING:
275                try (AxisIterator iterator = new Navigator.PrecedingEnumeration(this, true)) {
276                    result = iterator;
277                }
278                break;
279            default:
280                throw throwUnsupportedOperationException();
281        }
282        return result;
283    }
284
285    /**
286     * Returns line number.
287     *
288     * @return line number
289     */
290    @Override
291    public int getLineNumber() {
292        return detailAst.getLineNo();
293    }
294
295    /**
296     * Returns column number.
297     *
298     * @return column number
299     */
300    @Override
301    public int getColumnNumber() {
302        return detailAst.getColumnNo();
303    }
304
305    /**
306     * Getter method for token type.
307     *
308     * @return token type
309     */
310    @Override
311    public int getTokenType() {
312        return detailAst.getType();
313    }
314
315    /**
316     * Returns underlying node.
317     *
318     * @return underlying node
319     */
320    @Override
321    public DetailAST getUnderlyingNode() {
322        return detailAst;
323    }
324
325    /**
326     * Returns preceding sibling axis iterator.
327     *
328     * @return iterator
329     */
330    private AxisIterator getPrecedingSiblingsIterator() {
331        final AxisIterator result;
332        if (indexAmongSiblings == 0) {
333            result = EmptyIterator.OfNodes.THE_INSTANCE;
334        }
335        else {
336            try (AxisIterator iterator = new ArrayIterator.OfNodes(
337                    getPrecedingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY))) {
338                result = iterator;
339            }
340        }
341        return result;
342    }
343
344    /**
345     * Returns following sibling axis iterator.
346     *
347     * @return iterator
348     */
349    private AxisIterator getFollowingSiblingsIterator() {
350        final AxisIterator result;
351        if (indexAmongSiblings == parent.getChildren().size() - 1) {
352            result = EmptyIterator.OfNodes.THE_INSTANCE;
353        }
354        else {
355            try (AxisIterator iterator = new ArrayIterator.OfNodes(
356                    getFollowingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY))) {
357                result = iterator;
358            }
359        }
360        return result;
361    }
362
363    /**
364     * Returns following siblings of the current node.
365     *
366     * @return siblings
367     */
368    private List<AbstractNode> getFollowingSiblings() {
369        final List<AbstractNode> siblings = parent.getChildren();
370        return siblings.subList(indexAmongSiblings + 1, siblings.size());
371    }
372
373    /**
374     * Returns preceding siblings of the current node.
375     *
376     * @return siblings
377     */
378    private List<AbstractNode> getPrecedingSiblings() {
379        final List<AbstractNode> siblings = parent.getChildren();
380        return siblings.subList(0, indexAmongSiblings);
381    }
382
383    /**
384     * Checks if token type supports {@code @text} attribute,
385     * extracts its value, creates {@code AttributeNode} object and returns it.
386     * Value can be accessed using {@code @text} attribute.
387     */
388    private void createTextAttribute() {
389        AttributeNode attribute = null;
390        if (XpathUtil.supportsTextAttribute(detailAst)) {
391            attribute = new AttributeNode(TEXT_ATTRIBUTE_NAME,
392                    XpathUtil.getTextAttributeValue(detailAst));
393        }
394        attributeNode = attribute;
395    }
396
397    /**
398     * Returns UnsupportedOperationException exception.
399     *
400     * @return UnsupportedOperationException exception
401     */
402    private static UnsupportedOperationException throwUnsupportedOperationException() {
403        return new UnsupportedOperationException("Operation is not supported");
404    }
405
406    /**
407     * Implementation of the following axis, in terms of the child and following-sibling axes.
408     */
409    private static final class FollowingEnumeration implements AxisIterator {
410        /** Following-sibling axis iterator. */
411        private AxisIterator siblingEnum;
412        /** Child axis iterator. */
413        private AxisIterator descendEnum;
414
415        /**
416         * Create an iterator over the "following" axis.
417         *
418         * @param start the initial context node.
419         */
420        /* package */ FollowingEnumeration(NodeInfo start) {
421            siblingEnum = start.iterateAxis(AxisInfo.FOLLOWING_SIBLING);
422        }
423
424        /**
425         * Get the next item in the sequence.
426         *
427         * @return the next Item. If there are no more nodes, return null.
428         */
429        @Override
430        public NodeInfo next() {
431            NodeInfo result = null;
432            if (descendEnum != null) {
433                result = descendEnum.next();
434            }
435
436            if (result == null) {
437                descendEnum = null;
438                result = siblingEnum.next();
439                if (result == null) {
440                    siblingEnum = null;
441                }
442                else {
443                    descendEnum = new Navigator.DescendantEnumeration(result, true, false);
444                    result = next();
445                }
446            }
447            return result;
448        }
449    }
450
451}