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