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