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 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                try (AxisIterator iterator = new Navigator.AncestorEnumeration(this, false)) {
173                    result = iterator;
174                }
175                break;
176            case AxisInfo.ANCESTOR_OR_SELF:
177                try (AxisIterator iterator = new Navigator.AncestorEnumeration(this, true)) {
178                    result = iterator;
179                }
180                break;
181            case AxisInfo.ATTRIBUTE:
182                if (attributes == null) {
183                    result = EmptyIterator.OfNodes.THE_INSTANCE;
184                }
185                else {
186                    try (AxisIterator iterator = new ArrayIterator.OfNodes(attributes)) {
187                        result = iterator;
188                    }
189                }
190                break;
191            case AxisInfo.CHILD:
192                if (hasChildNodes()) {
193                    try (AxisIterator iterator = new ArrayIterator.OfNodes(
194                            getChildren().toArray(EMPTY_ABSTRACT_NODE_ARRAY))) {
195                        result = iterator;
196                    }
197                }
198                else {
199                    result = EmptyIterator.OfNodes.THE_INSTANCE;
200                }
201                break;
202            case AxisInfo.DESCENDANT:
203                if (hasChildNodes()) {
204                    try (AxisIterator iterator =
205                                 new Navigator.DescendantEnumeration(this, false, true)) {
206                        result = iterator;
207                    }
208                }
209                else {
210                    result = EmptyIterator.OfNodes.THE_INSTANCE;
211                }
212                break;
213            case AxisInfo.DESCENDANT_OR_SELF:
214                try (AxisIterator iterator =
215                             new Navigator.DescendantEnumeration(this, true, true)) {
216                    result = iterator;
217                }
218                break;
219            case AxisInfo.PARENT:
220                try (AxisIterator iterator = SingleNodeIterator.makeIterator(parent)) {
221                    result = iterator;
222                }
223                break;
224            case AxisInfo.SELF:
225                try (AxisIterator iterator = SingleNodeIterator.makeIterator(this)) {
226                    result = iterator;
227                }
228                break;
229            default:
230                throw throwUnsupportedOperationException();
231        }
232        return result;
233    }
234
235    /**
236     * Returns line number.
237     * @return line number
238     */
239    @Override
240    public int getLineNumber() {
241        return detailAst.getLineNo();
242    }
243
244    /**
245     * Returns column number.
246     * @return column number
247     */
248    @Override
249    public int getColumnNumber() {
250        return detailAst.getColumnNo();
251    }
252
253    /**
254     * Getter method for token type.
255     * @return token type
256     */
257    @Override
258    public int getTokenType() {
259        return detailAst.getType();
260    }
261
262    /**
263     * Returns underlying node.
264     * @return underlying node
265     */
266    // -@cs[SimpleAccessorNameNotation] Overrides method from the base class.
267    // Issue: https://github.com/sevntu-checkstyle/sevntu.checkstyle/issues/166
268    @Override
269    public DetailAST getUnderlyingNode() {
270        return detailAst;
271    }
272
273    /**
274     * Finds child element with {@link TokenTypes#IDENT}, extracts its value and stores it.
275     * Value can be accessed using {@code @text} attribute. Now {@code @text} attribute is only
276     * supported attribute.
277     */
278    private void setIdent() {
279        final DetailAST identAst = detailAst.findFirstToken(TokenTypes.IDENT);
280        if (identAst != null) {
281            ident = identAst.getText();
282            attributes = new AbstractNode[1];
283            attributes[0] = new AttributeNode(TEXT_ATTRIBUTE_NAME, ident);
284        }
285    }
286
287    /**
288     * Returns UnsupportedOperationException exception.
289     * @return UnsupportedOperationException exception
290     */
291    private static UnsupportedOperationException throwUnsupportedOperationException() {
292        return new UnsupportedOperationException("Operation is not supported");
293    }
294
295}