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