001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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 com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.TokenTypes;
024import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
025import net.sf.saxon.om.AxisInfo;
026import net.sf.saxon.om.NodeInfo;
027import net.sf.saxon.tree.iter.ArrayIterator;
028import net.sf.saxon.tree.iter.AxisIterator;
029import net.sf.saxon.tree.iter.EmptyIterator;
030import net.sf.saxon.tree.iter.SingleNodeIterator;
031import net.sf.saxon.tree.util.Navigator;
032import net.sf.saxon.type.Type;
033
034/**
035 * Represents element node of Xpath-tree.
036 *
037 * @author Timur Tibeyev
038 */
039public class ElementNode extends AbstractNode {
040    /** String literal for text attribute. */
041    private static final String TEXT_ATTRIBUTE_NAME = "text";
042
043    /** The root node. */
044    private final AbstractNode root;
045
046    /** The parent of the current node. */
047    private final AbstractNode parent;
048
049    /** The ast node. */
050    private final DetailAST detailAst;
051
052    /** Represents text of the DetailAST. */
053    private final String text;
054
055    /** The attributes. */
056    private AbstractNode[] attributes;
057
058    /** Represents value of TokenTypes#IDENT. */
059    private String ident;
060
061    /**
062     * Creates a new {@code ElementNode} instance.
063     *
064     * @param root {@code Node} root of the tree
065     * @param parent {@code Node} parent of the current node
066     * @param detailAst reference to {@code DetailAST}
067     */
068    public ElementNode(AbstractNode root, AbstractNode parent, DetailAST detailAst) {
069        this.parent = parent;
070        this.root = root;
071        this.detailAst = detailAst;
072        setIdent();
073        createChildren();
074        text = TokenUtils.getTokenName(detailAst.getType());
075    }
076
077    /**
078     * Iterates children of the current node and
079     * recursively creates new Xpath-nodes.
080     */
081    private void createChildren() {
082        DetailAST currentChild = detailAst.getFirstChild();
083        while (currentChild != null) {
084            if (currentChild.getType() != TokenTypes.IDENT) {
085                final ElementNode child = new ElementNode(root, this, currentChild);
086                addChild(child);
087            }
088            currentChild = currentChild.getNextSibling();
089        }
090    }
091
092    /**
093     * Returns attribute value. Throws {@code UnsupportedOperationException} in case,
094     * when name of the attribute is not equal to 'text'.
095     * @param namespace namespace
096     * @param localPart actual name of the attribute
097     * @return attribute value
098     */
099    @Override
100    public String getAttributeValue(String namespace, String localPart) {
101        if (TEXT_ATTRIBUTE_NAME.equals(localPart)) {
102            return ident;
103        }
104        else {
105            throw throwUnsupportedOperationException();
106        }
107    }
108
109    /**
110     * Returns local part.
111     * @return local part
112     */
113    // -@cs[SimpleAccessorNameNotation] Overrides method from the base class.
114    // Issue: https://github.com/sevntu-checkstyle/sevntu.checkstyle/issues/166
115    @Override
116    public String getLocalPart() {
117        return text;
118    }
119
120    /**
121     * Returns type of the node.
122     * @return node kind
123     */
124    @Override
125    public int getNodeKind() {
126        return Type.ELEMENT;
127    }
128
129    /**
130     * Returns parent.
131     * @return parent
132     */
133    @Override
134    public NodeInfo getParent() {
135        return parent;
136    }
137
138    /**
139     * Returns root.
140     * @return root
141     */
142    @Override
143    public NodeInfo getRoot() {
144        return root;
145    }
146
147    /**
148     * Returns string value.
149     * @return string value
150     */
151    // -@cs[SimpleAccessorNameNotation] Overrides method from the base class.
152    // Issue: https://github.com/sevntu-checkstyle/sevntu.checkstyle/issues/166
153    @Override
154    public String getStringValue() {
155        return text;
156    }
157
158    /**
159     * Determines axis iteration algorithm. Throws {@code UnsupportedOperationException} in case,
160     * when there is no axis iterator for given axisNumber.
161     *
162     * @param axisNumber element from {@code AxisInfo}
163     * @return {@code AxisIterator} object
164     */
165    @Override
166    public AxisIterator iterateAxis(byte axisNumber) {
167        final AxisIterator result;
168        switch (axisNumber) {
169            case AxisInfo.ANCESTOR:
170                result = new Navigator.AncestorEnumeration(this, false);
171                break;
172            case AxisInfo.ANCESTOR_OR_SELF:
173                result = new Navigator.AncestorEnumeration(this, true);
174                break;
175            case AxisInfo.ATTRIBUTE:
176                if (attributes == null) {
177                    result = EmptyIterator.OfNodes.THE_INSTANCE;
178                }
179                else {
180                    result = new ArrayIterator.OfNodes(attributes);
181                }
182                break;
183            case AxisInfo.CHILD:
184                if (hasChildNodes()) {
185                    result = new ArrayIterator.OfNodes(
186                            getChildren().toArray(new AbstractNode[getChildren().size()]));
187                }
188                else {
189                    result = EmptyIterator.OfNodes.THE_INSTANCE;
190                }
191                break;
192            case AxisInfo.DESCENDANT:
193                if (hasChildNodes()) {
194                    result = new Navigator.DescendantEnumeration(this, false, true);
195                }
196                else {
197                    result = EmptyIterator.OfNodes.THE_INSTANCE;
198                }
199                break;
200            case AxisInfo.DESCENDANT_OR_SELF:
201                result = new Navigator.DescendantEnumeration(this, true, true);
202                break;
203            case AxisInfo.PARENT:
204                result = SingleNodeIterator.makeIterator(parent);
205                break;
206            case AxisInfo.SELF:
207                result = SingleNodeIterator.makeIterator(this);
208                break;
209            default:
210                throw throwUnsupportedOperationException();
211        }
212        return result;
213    }
214
215    /**
216     * Returns line number.
217     * @return line number
218     */
219    @Override
220    public int getLineNumber() {
221        return detailAst.getLineNo();
222    }
223
224    /**
225     * Returns column number.
226     * @return column number
227     */
228    @Override
229    public int getColumnNumber() {
230        return detailAst.getColumnNo();
231    }
232
233    /**
234     * Getter method for token type.
235     * @return token type
236     */
237    @Override
238    public int getTokenType() {
239        return detailAst.getType();
240    }
241
242    /**
243     * Returns underlying node.
244     * @return underlying node
245     */
246    // -@cs[SimpleAccessorNameNotation] Overrides method from the base class.
247    // Issue: https://github.com/sevntu-checkstyle/sevntu.checkstyle/issues/166
248    @Override
249    public DetailAST getUnderlyingNode() {
250        return detailAst;
251    }
252
253    /**
254     * Finds child element with {@link TokenTypes#IDENT}, extracts its value and stores it.
255     * Value can be accessed using {@code @text} attribute. Now {@code @text} attribute is only
256     * supported attribute.
257     */
258    private void setIdent() {
259        final DetailAST identAst = detailAst.findFirstToken(TokenTypes.IDENT);
260        if (identAst != null) {
261            ident = identAst.getText();
262            attributes = new AbstractNode[1];
263            attributes[0] = new AttributeNode(TEXT_ATTRIBUTE_NAME, ident);
264        }
265    }
266
267    /**
268     * Returns UnsupportedOperationException exception.
269     * @return UnsupportedOperationException exception
270     */
271    private static UnsupportedOperationException throwUnsupportedOperationException() {
272        return new UnsupportedOperationException("Operation is not supported");
273    }
274}