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     * @param other another {@code NodeInfo} object
088     * @return number representing order of current object to specified one
089     */
090    @Override
091    public int compareOrder(NodeInfo other) {
092        int result = 0;
093        if (other instanceof AbstractNode) {
094            result = getDepth() - ((AbstractNode) other).getDepth();
095            if (result == 0) {
096                final ElementNode[] children = getCommonAncestorChildren(other);
097                result = children[0].indexAmongSiblings - children[1].indexAmongSiblings;
098            }
099        }
100        return result;
101    }
102
103    /**
104     * Finds the ancestors of the children whose parent is their common ancestor.
105     * @param other another {@code NodeInfo} object
106     * @return {@code ElementNode} immediate children(also ancestors of the given children) of the
107     *         common ancestor
108     */
109    private ElementNode[] getCommonAncestorChildren(NodeInfo other) {
110        NodeInfo child1 = this;
111        NodeInfo child2 = other;
112        while (!child1.getParent().equals(child2.getParent())) {
113            child1 = child1.getParent();
114            child2 = child2.getParent();
115        }
116        return new ElementNode[] {(ElementNode) child1, (ElementNode) child2};
117    }
118
119    /**
120     * Iterates children of the current node and
121     * recursively creates new Xpath-nodes.
122     */
123    private void createChildren() {
124        DetailAST currentChild = detailAst.getFirstChild();
125        while (currentChild != null) {
126            final AbstractNode child = new ElementNode(root, this, currentChild);
127            addChild(child);
128            currentChild = currentChild.getNextSibling();
129        }
130    }
131
132    /**
133     * Returns attribute value. Throws {@code UnsupportedOperationException} in case,
134     * when name of the attribute is not equal to 'text'.
135     *
136     * @param namespace namespace
137     * @param localPart actual name of the attribute
138     * @return attribute value
139     */
140    @Override
141    public String getAttributeValue(String namespace, String localPart) {
142        final String result;
143        if (TEXT_ATTRIBUTE_NAME.equals(localPart)) {
144            if (attributeNode == null) {
145                result = null;
146            }
147            else {
148                result = attributeNode.getStringValue();
149            }
150        }
151        else {
152            result = null;
153        }
154        return result;
155    }
156
157    /**
158     * Returns local part.
159     *
160     * @return local part
161     */
162    @Override
163    public String getLocalPart() {
164        return text;
165    }
166
167    /**
168     * Returns type of the node.
169     *
170     * @return node kind
171     */
172    @Override
173    public int getNodeKind() {
174        return Type.ELEMENT;
175    }
176
177    /**
178     * Returns parent.
179     *
180     * @return parent
181     */
182    @Override
183    public NodeInfo getParent() {
184        return parent;
185    }
186
187    /**
188     * Returns root.
189     *
190     * @return root
191     */
192    @Override
193    public NodeInfo getRoot() {
194        return root;
195    }
196
197    /**
198     * Determines axis iteration algorithm. Throws {@code UnsupportedOperationException} in case,
199     * when there is no axis iterator for given axisNumber.
200     *
201     * @param axisNumber element from {@code AxisInfo}
202     * @return {@code AxisIterator} object
203     */
204    @Override
205    public AxisIterator iterateAxis(byte axisNumber) {
206        final AxisIterator result;
207        switch (axisNumber) {
208            case AxisInfo.ANCESTOR:
209                try (AxisIterator iterator = new Navigator.AncestorEnumeration(this, false)) {
210                    result = iterator;
211                }
212                break;
213            case AxisInfo.ANCESTOR_OR_SELF:
214                try (AxisIterator iterator = new Navigator.AncestorEnumeration(this, true)) {
215                    result = iterator;
216                }
217                break;
218            case AxisInfo.ATTRIBUTE:
219                try (AxisIterator iterator = SingleNodeIterator.makeIterator(attributeNode)) {
220                    result = iterator;
221                }
222                break;
223            case AxisInfo.CHILD:
224                if (hasChildNodes()) {
225                    try (AxisIterator iterator = new ArrayIterator.OfNodes(
226                            getChildren().toArray(EMPTY_ABSTRACT_NODE_ARRAY))) {
227                        result = iterator;
228                    }
229                }
230                else {
231                    result = EmptyIterator.OfNodes.THE_INSTANCE;
232                }
233                break;
234            case AxisInfo.DESCENDANT:
235                if (hasChildNodes()) {
236                    try (AxisIterator iterator =
237                                 new Navigator.DescendantEnumeration(this, false, true)) {
238                        result = iterator;
239                    }
240                }
241                else {
242                    result = EmptyIterator.OfNodes.THE_INSTANCE;
243                }
244                break;
245            case AxisInfo.DESCENDANT_OR_SELF:
246                try (AxisIterator iterator =
247                             new Navigator.DescendantEnumeration(this, true, true)) {
248                    result = iterator;
249                }
250                break;
251            case AxisInfo.PARENT:
252                try (AxisIterator iterator = SingleNodeIterator.makeIterator(parent)) {
253                    result = iterator;
254                }
255                break;
256            case AxisInfo.SELF:
257                try (AxisIterator iterator = SingleNodeIterator.makeIterator(this)) {
258                    result = iterator;
259                }
260                break;
261            case AxisInfo.FOLLOWING_SIBLING:
262                result = getFollowingSiblingsIterator();
263                break;
264            case AxisInfo.PRECEDING_SIBLING:
265                result = getPrecedingSiblingsIterator();
266                break;
267            case AxisInfo.FOLLOWING:
268                try (AxisIterator iterator = new FollowingEnumeration(this)) {
269                    result = iterator;
270                }
271                break;
272            case AxisInfo.PRECEDING:
273                try (AxisIterator iterator = new Navigator.PrecedingEnumeration(this, true)) {
274                    result = iterator;
275                }
276                break;
277            default:
278                throw throwUnsupportedOperationException();
279        }
280        return result;
281    }
282
283    /**
284     * Returns line number.
285     *
286     * @return line number
287     */
288    @Override
289    public int getLineNumber() {
290        return detailAst.getLineNo();
291    }
292
293    /**
294     * Returns column number.
295     *
296     * @return column number
297     */
298    @Override
299    public int getColumnNumber() {
300        return detailAst.getColumnNo();
301    }
302
303    /**
304     * Getter method for token type.
305     *
306     * @return token type
307     */
308    @Override
309    public int getTokenType() {
310        return detailAst.getType();
311    }
312
313    /**
314     * Returns underlying node.
315     *
316     * @return underlying node
317     */
318    @Override
319    public DetailAST getUnderlyingNode() {
320        return detailAst;
321    }
322
323    /**
324     * Returns preceding sibling axis iterator.
325     *
326     * @return iterator
327     */
328    private AxisIterator getPrecedingSiblingsIterator() {
329        final AxisIterator result;
330        if (indexAmongSiblings == 0) {
331            result = EmptyIterator.OfNodes.THE_INSTANCE;
332        }
333        else {
334            try (AxisIterator iterator = new ArrayIterator.OfNodes(
335                    getPrecedingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY))) {
336                result = iterator;
337            }
338        }
339        return result;
340    }
341
342    /**
343     * Returns following sibling axis iterator.
344     *
345     * @return iterator
346     */
347    private AxisIterator getFollowingSiblingsIterator() {
348        final AxisIterator result;
349        if (indexAmongSiblings == parent.getChildren().size() - 1) {
350            result = EmptyIterator.OfNodes.THE_INSTANCE;
351        }
352        else {
353            try (AxisIterator iterator = new ArrayIterator.OfNodes(
354                    getFollowingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY))) {
355                result = iterator;
356            }
357        }
358        return result;
359    }
360
361    /**
362     * Returns following siblings of the current node.
363     *
364     * @return siblings
365     */
366    private List<AbstractNode> getFollowingSiblings() {
367        final List<AbstractNode> siblings = parent.getChildren();
368        return siblings.subList(indexAmongSiblings + 1, siblings.size());
369    }
370
371    /**
372     * Returns preceding siblings of the current node.
373     *
374     * @return siblings
375     */
376    private List<AbstractNode> getPrecedingSiblings() {
377        final List<AbstractNode> siblings = parent.getChildren();
378        return siblings.subList(0, indexAmongSiblings);
379    }
380
381    /**
382     * Checks if token type supports {@code @text} attribute,
383     * extracts its value, creates {@code AttributeNode} object and returns it.
384     * Value can be accessed using {@code @text} attribute.
385     */
386    private void createTextAttribute() {
387        AttributeNode attribute = null;
388        if (XpathUtil.supportsTextAttribute(detailAst)) {
389            attribute = new AttributeNode(TEXT_ATTRIBUTE_NAME,
390                    XpathUtil.getTextAttributeValue(detailAst));
391        }
392        attributeNode = attribute;
393    }
394
395    /**
396     * Returns UnsupportedOperationException exception.
397     *
398     * @return UnsupportedOperationException exception
399     */
400    private static UnsupportedOperationException throwUnsupportedOperationException() {
401        return new UnsupportedOperationException("Operation is not supported");
402    }
403
404    /**
405     * Implementation of the following axis, in terms of the child and following-sibling axes.
406     */
407    private static final class FollowingEnumeration implements AxisIterator {
408        /** Following-sibling axis iterator. */
409        private AxisIterator siblingEnum;
410        /** Child axis iterator. */
411        private AxisIterator descendEnum;
412
413        /**
414         * Create an iterator over the "following" axis.
415         *
416         * @param start the initial context node.
417         */
418        /* package */ FollowingEnumeration(NodeInfo start) {
419            siblingEnum = start.iterateAxis(AxisInfo.FOLLOWING_SIBLING);
420        }
421
422        /**
423         * Get the next item in the sequence.
424         *
425         * @return the next Item. If there are no more nodes, return null.
426         */
427        @Override
428        public NodeInfo next() {
429            NodeInfo result = null;
430            if (descendEnum != null) {
431                result = descendEnum.next();
432            }
433
434            if (result == null) {
435                descendEnum = null;
436                result = siblingEnum.next();
437                if (result == null) {
438                    siblingEnum = null;
439                }
440                else {
441                    descendEnum = new Navigator.DescendantEnumeration(result, true, false);
442                    result = next();
443                }
444            }
445            return result;
446        }
447    }
448
449}