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