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;
021
022import java.io.File;
023import java.io.Reader;
024import java.io.StringReader;
025import java.util.Arrays;
026import java.util.Collection;
027import java.util.HashSet;
028import java.util.Locale;
029import java.util.Set;
030import java.util.SortedSet;
031import java.util.TreeSet;
032
033import antlr.CommonHiddenStreamToken;
034import antlr.RecognitionException;
035import antlr.Token;
036import antlr.TokenStreamException;
037import antlr.TokenStreamHiddenTokenFilter;
038import antlr.TokenStreamRecognitionException;
039import com.google.common.collect.HashMultimap;
040import com.google.common.collect.Multimap;
041import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
042import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
043import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
044import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
045import com.puppycrawl.tools.checkstyle.api.Configuration;
046import com.puppycrawl.tools.checkstyle.api.Context;
047import com.puppycrawl.tools.checkstyle.api.DetailAST;
048import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
049import com.puppycrawl.tools.checkstyle.api.FileContents;
050import com.puppycrawl.tools.checkstyle.api.FileText;
051import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
052import com.puppycrawl.tools.checkstyle.api.TokenTypes;
053import com.puppycrawl.tools.checkstyle.grammars.GeneratedJavaLexer;
054import com.puppycrawl.tools.checkstyle.grammars.GeneratedJavaRecognizer;
055import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
056import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
057
058/**
059 * Responsible for walking an abstract syntax tree and notifying interested
060 * checks at each each node.
061 *
062 * @author Oliver Burn
063 */
064// -@cs[ClassFanOutComplexity] To resolve issue 4714, new classes were imported. Number of
065// classes current class relies on currently is 27, which is above threshold 25.
066// see https://github.com/checkstyle/checkstyle/issues/4714.
067public final class TreeWalker extends AbstractFileSetCheck implements ExternalResourceHolder {
068
069    /** Default distance between tab stops. */
070    private static final int DEFAULT_TAB_WIDTH = 8;
071
072    /** Maps from token name to ordinary checks. */
073    private final Multimap<String, AbstractCheck> tokenToOrdinaryChecks =
074        HashMultimap.create();
075
076    /** Maps from token name to comment checks. */
077    private final Multimap<String, AbstractCheck> tokenToCommentChecks =
078            HashMultimap.create();
079
080    /** Registered ordinary checks, that don't use comment nodes. */
081    private final Set<AbstractCheck> ordinaryChecks = new HashSet<>();
082
083    /** Registered comment checks. */
084    private final Set<AbstractCheck> commentChecks = new HashSet<>();
085
086    /** The ast filters. */
087    private final Set<TreeWalkerFilter> filters = new HashSet<>();
088
089    /** The sorted set of messages. */
090    private final SortedSet<LocalizedMessage> messages = new TreeSet<>();
091
092    /** The distance between tab stops. */
093    private int tabWidth = DEFAULT_TAB_WIDTH;
094
095    /** Class loader to resolve classes with. **/
096    private ClassLoader classLoader;
097
098    /** Context of child components. */
099    private Context childContext;
100
101    /** A factory for creating submodules (i.e. the Checks) */
102    private ModuleFactory moduleFactory;
103
104    /**
105     * Creates a new {@code TreeWalker} instance.
106     */
107    public TreeWalker() {
108        setFileExtensions("java");
109    }
110
111    /**
112     * Sets tab width.
113     * @param tabWidth the distance between tab stops
114     */
115    public void setTabWidth(int tabWidth) {
116        this.tabWidth = tabWidth;
117    }
118
119    /**
120     * Sets cache file.
121     * @deprecated Use {@link Checker#setCacheFile} instead. It does not do anything now. We just
122     *             keep the setter for transition period to the same option in Checker. The
123     *             method will be completely removed in Checkstyle 8.0. See
124     *             <a href="https://github.com/checkstyle/checkstyle/issues/2883">issue#2883</a>
125     * @param fileName the cache file
126     */
127    @Deprecated
128    public void setCacheFile(String fileName) {
129        // Deprecated
130    }
131
132    /**
133     * Sets classLoader to load class.
134     * @param classLoader class loader to resolve classes with.
135     */
136    public void setClassLoader(ClassLoader classLoader) {
137        this.classLoader = classLoader;
138    }
139
140    /**
141     * Sets the module factory for creating child modules (Checks).
142     * @param moduleFactory the factory
143     */
144    public void setModuleFactory(ModuleFactory moduleFactory) {
145        this.moduleFactory = moduleFactory;
146    }
147
148    @Override
149    public void finishLocalSetup() {
150        final DefaultContext checkContext = new DefaultContext();
151        checkContext.add("classLoader", classLoader);
152        checkContext.add("severity", getSeverity());
153        checkContext.add("tabWidth", String.valueOf(tabWidth));
154
155        childContext = checkContext;
156    }
157
158    /**
159     * {@inheritDoc} Creates child module.
160     * @noinspection ChainOfInstanceofChecks
161     */
162    @Override
163    public void setupChild(Configuration childConf)
164            throws CheckstyleException {
165        final String name = childConf.getName();
166        final Object module = moduleFactory.createModule(name);
167        if (module instanceof AutomaticBean) {
168            final AutomaticBean bean = (AutomaticBean) module;
169            bean.contextualize(childContext);
170            bean.configure(childConf);
171        }
172        if (module instanceof AbstractCheck) {
173            final AbstractCheck check = (AbstractCheck) module;
174            check.init();
175            registerCheck(check);
176        }
177        else if (module instanceof TreeWalkerFilter) {
178            final TreeWalkerFilter filter = (TreeWalkerFilter) module;
179            filters.add(filter);
180        }
181        else {
182            throw new CheckstyleException(
183                "TreeWalker is not allowed as a parent of " + name
184                        + " Please review 'Parent Module' section for this Check in web"
185                        + " documentation if Check is standard.");
186        }
187    }
188
189    @Override
190    protected void processFiltered(File file, FileText fileText) throws CheckstyleException {
191        // check if already checked and passed the file
192        if (CommonUtils.matchesFileExtension(file, getFileExtensions())) {
193            final String msg = "%s occurred during the analysis of file %s.";
194            final String fileName = file.getPath();
195
196            try {
197                if (!ordinaryChecks.isEmpty()
198                        || !commentChecks.isEmpty()) {
199                    final FileContents contents = new FileContents(fileText);
200                    final DetailAST rootAST = parse(contents);
201
202                    if (!ordinaryChecks.isEmpty()) {
203                        walk(rootAST, contents, AstState.ORDINARY);
204                    }
205                    if (!commentChecks.isEmpty()) {
206                        final DetailAST astWithComments = appendHiddenCommentNodes(rootAST);
207
208                        walk(astWithComments, contents, AstState.WITH_COMMENTS);
209                    }
210                    if (filters.isEmpty()) {
211                        addMessages(messages);
212                    }
213                    else {
214                        final SortedSet<LocalizedMessage> filteredMessages =
215                            getFilteredMessages(fileName, contents, rootAST);
216                        addMessages(filteredMessages);
217                    }
218                    messages.clear();
219                }
220            }
221            catch (final TokenStreamRecognitionException tre) {
222                final String exceptionMsg = String.format(Locale.ROOT, msg,
223                        "TokenStreamRecognitionException", fileName);
224                throw new CheckstyleException(exceptionMsg, tre);
225            }
226            catch (RecognitionException | TokenStreamException ex) {
227                final String exceptionMsg = String.format(Locale.ROOT, msg,
228                        ex.getClass().getSimpleName(), fileName);
229                throw new CheckstyleException(exceptionMsg, ex);
230            }
231        }
232    }
233
234    /**
235     * Returns filtered set of {@link LocalizedMessage}.
236     * @param fileName path to the file
237     * @param fileContents the contents of the file
238     * @param rootAST root AST element {@link DetailAST} of the file
239     * @return filtered set of messages
240     */
241    private SortedSet<LocalizedMessage> getFilteredMessages(
242            String fileName, FileContents fileContents, DetailAST rootAST) {
243        final SortedSet<LocalizedMessage> result = new TreeSet<>(messages);
244        for (LocalizedMessage element : messages) {
245            final TreeWalkerAuditEvent event =
246                    new TreeWalkerAuditEvent(fileContents, fileName, element, rootAST);
247            for (TreeWalkerFilter filter : filters) {
248                if (!filter.accept(event)) {
249                    result.remove(element);
250                    break;
251                }
252            }
253        }
254        return result;
255    }
256
257    /**
258     * Register a check for a given configuration.
259     * @param check the check to register
260     * @throws CheckstyleException if an error occurs
261     */
262    private void registerCheck(AbstractCheck check)
263            throws CheckstyleException {
264        validateDefaultTokens(check);
265        final int[] tokens;
266        final Set<String> checkTokens = check.getTokenNames();
267        if (checkTokens.isEmpty()) {
268            tokens = check.getDefaultTokens();
269        }
270        else {
271            tokens = check.getRequiredTokens();
272
273            //register configured tokens
274            final int[] acceptableTokens = check.getAcceptableTokens();
275            Arrays.sort(acceptableTokens);
276            for (String token : checkTokens) {
277                final int tokenId = TokenUtils.getTokenId(token);
278                if (Arrays.binarySearch(acceptableTokens, tokenId) >= 0) {
279                    registerCheck(token, check);
280                }
281                else {
282                    final String message = String.format(Locale.ROOT, "Token \"%s\" was "
283                            + "not found in Acceptable tokens list in check %s",
284                            token, check.getClass().getName());
285                    throw new CheckstyleException(message);
286                }
287            }
288        }
289        for (int element : tokens) {
290            registerCheck(element, check);
291        }
292        if (check.isCommentNodesRequired()) {
293            commentChecks.add(check);
294        }
295        else {
296            ordinaryChecks.add(check);
297        }
298    }
299
300    /**
301     * Register a check for a specified token id.
302     * @param tokenId the id of the token
303     * @param check the check to register
304     * @throws CheckstyleException if Check is misconfigured
305     */
306    private void registerCheck(int tokenId, AbstractCheck check) throws CheckstyleException {
307        registerCheck(TokenUtils.getTokenName(tokenId), check);
308    }
309
310    /**
311     * Register a check for a specified token name.
312     * @param token the name of the token
313     * @param check the check to register
314     * @throws CheckstyleException if Check is misconfigured
315     */
316    private void registerCheck(String token, AbstractCheck check) throws CheckstyleException {
317        if (check.isCommentNodesRequired()) {
318            tokenToCommentChecks.put(token, check);
319        }
320        else if (TokenUtils.isCommentType(token)) {
321            final String message = String.format(Locale.ROOT, "Check '%s' waits for comment type "
322                    + "token ('%s') and should override 'isCommentNodesRequired()' "
323                    + "method to return 'true'", check.getClass().getName(), token);
324            throw new CheckstyleException(message);
325        }
326        else {
327            tokenToOrdinaryChecks.put(token, check);
328        }
329    }
330
331    /**
332     * Validates that check's required tokens are subset of default tokens.
333     * @param check to validate
334     * @throws CheckstyleException when validation of default tokens fails
335     */
336    private static void validateDefaultTokens(AbstractCheck check) throws CheckstyleException {
337        if (check.getRequiredTokens().length != 0) {
338            final int[] defaultTokens = check.getDefaultTokens();
339            Arrays.sort(defaultTokens);
340            for (final int token : check.getRequiredTokens()) {
341                if (Arrays.binarySearch(defaultTokens, token) < 0) {
342                    final String message = String.format(Locale.ROOT, "Token \"%s\" from required "
343                            + "tokens was not found in default tokens list in check %s",
344                            token, check.getClass().getName());
345                    throw new CheckstyleException(message);
346                }
347            }
348        }
349    }
350
351    /**
352     * Initiates the walk of an AST.
353     * @param ast the root AST
354     * @param contents the contents of the file the AST was generated from.
355     * @param astState state of AST.
356     */
357    private void walk(DetailAST ast, FileContents contents,
358            AstState astState) {
359        notifyBegin(ast, contents, astState);
360
361        // empty files are not flagged by javac, will yield ast == null
362        if (ast != null) {
363            processIter(ast, astState);
364        }
365        notifyEnd(ast, astState);
366    }
367
368    /**
369     * Notify checks that we are about to begin walking a tree.
370     * @param rootAST the root of the tree.
371     * @param contents the contents of the file the AST was generated from.
372     * @param astState state of AST.
373     */
374    private void notifyBegin(DetailAST rootAST, FileContents contents,
375            AstState astState) {
376        final Set<AbstractCheck> checks;
377
378        if (astState == AstState.WITH_COMMENTS) {
379            checks = commentChecks;
380        }
381        else {
382            checks = ordinaryChecks;
383        }
384
385        for (AbstractCheck check : checks) {
386            check.setFileContents(contents);
387            check.clearMessages();
388            check.beginTree(rootAST);
389        }
390    }
391
392    /**
393     * Notify checks that we have finished walking a tree.
394     * @param rootAST the root of the tree.
395     * @param astState state of AST.
396     */
397    private void notifyEnd(DetailAST rootAST, AstState astState) {
398        final Set<AbstractCheck> checks;
399
400        if (astState == AstState.WITH_COMMENTS) {
401            checks = commentChecks;
402        }
403        else {
404            checks = ordinaryChecks;
405        }
406
407        for (AbstractCheck check : checks) {
408            check.finishTree(rootAST);
409            messages.addAll(check.getMessages());
410        }
411    }
412
413    /**
414     * Notify checks that visiting a node.
415     * @param ast the node to notify for.
416     * @param astState state of AST.
417     */
418    private void notifyVisit(DetailAST ast, AstState astState) {
419        final Collection<AbstractCheck> visitors = getListOfChecks(ast, astState);
420
421        if (visitors != null) {
422            for (AbstractCheck check : visitors) {
423                check.visitToken(ast);
424            }
425        }
426    }
427
428    /**
429     * Notify checks that leaving a node.
430     * @param ast
431     *        the node to notify for
432     * @param astState state of AST.
433     */
434    private void notifyLeave(DetailAST ast, AstState astState) {
435        final Collection<AbstractCheck> visitors = getListOfChecks(ast, astState);
436
437        if (visitors != null) {
438            for (AbstractCheck check : visitors) {
439                check.leaveToken(ast);
440            }
441        }
442    }
443
444    /**
445     * Method returns list of checks.
446     *
447     * @param ast
448     *            the node to notify for
449     * @param astState
450     *            state of AST.
451     * @return list of visitors
452     */
453    private Collection<AbstractCheck> getListOfChecks(DetailAST ast, AstState astState) {
454        Collection<AbstractCheck> visitors = null;
455        final String tokenType = TokenUtils.getTokenName(ast.getType());
456
457        if (astState == AstState.WITH_COMMENTS) {
458            if (tokenToCommentChecks.containsKey(tokenType)) {
459                visitors = tokenToCommentChecks.get(tokenType);
460            }
461        }
462        else {
463            if (tokenToOrdinaryChecks.containsKey(tokenType)) {
464                visitors = tokenToOrdinaryChecks.get(tokenType);
465            }
466        }
467        return visitors;
468    }
469
470    /**
471     * Static helper method to parses a Java source file.
472     *
473     * @param contents
474     *                contains the contents of the file
475     * @return the root of the AST
476     * @throws TokenStreamException
477     *                 if lexing failed
478     * @throws RecognitionException
479     *                 if parsing failed
480     */
481    public static DetailAST parse(FileContents contents)
482            throws RecognitionException, TokenStreamException {
483        final String fullText = contents.getText().getFullText().toString();
484        final Reader reader = new StringReader(fullText);
485        final GeneratedJavaLexer lexer = new GeneratedJavaLexer(reader);
486        lexer.setCommentListener(contents);
487        lexer.setTokenObjectClass("antlr.CommonHiddenStreamToken");
488
489        final TokenStreamHiddenTokenFilter filter =
490                new TokenStreamHiddenTokenFilter(lexer);
491        filter.hide(TokenTypes.SINGLE_LINE_COMMENT);
492        filter.hide(TokenTypes.BLOCK_COMMENT_BEGIN);
493
494        final GeneratedJavaRecognizer parser =
495            new GeneratedJavaRecognizer(filter);
496        parser.setFilename(contents.getFileName());
497        parser.setASTNodeClass(DetailAST.class.getName());
498        parser.compilationUnit();
499
500        return (DetailAST) parser.getAST();
501    }
502
503    /**
504     * Parses Java source file. Result AST contains comment nodes.
505     * @param contents source file content
506     * @return DetailAST tree
507     * @throws RecognitionException if parser failed
508     * @throws TokenStreamException if lexer failed
509     */
510    public static DetailAST parseWithComments(FileContents contents)
511            throws RecognitionException, TokenStreamException {
512        return appendHiddenCommentNodes(parse(contents));
513    }
514
515    @Override
516    public void destroy() {
517        ordinaryChecks.forEach(AbstractCheck::destroy);
518        commentChecks.forEach(AbstractCheck::destroy);
519        super.destroy();
520    }
521
522    @Override
523    public Set<String> getExternalResourceLocations() {
524        final Set<String> ordinaryChecksResources =
525                getExternalResourceLocationsOfChecks(ordinaryChecks);
526        final Set<String> commentChecksResources =
527                getExternalResourceLocationsOfChecks(commentChecks);
528        final Set<String> filtersResources =
529                getExternalResourceLocationsOfFilters();
530        final int resultListSize = commentChecksResources.size()
531                + ordinaryChecksResources.size()
532                + filtersResources.size();
533        final Set<String> resourceLocations = new HashSet<>(resultListSize);
534        resourceLocations.addAll(ordinaryChecksResources);
535        resourceLocations.addAll(commentChecksResources);
536        resourceLocations.addAll(filtersResources);
537        return resourceLocations;
538    }
539
540    /**
541     * Returns a set of external configuration resource locations which are used by the filters set.
542     * @return a set of external configuration resource locations which are used by the filters set.
543     */
544    private Set<String> getExternalResourceLocationsOfFilters() {
545        final Set<String> externalConfigurationResources = new HashSet<>();
546        filters.stream().filter(filter -> filter instanceof ExternalResourceHolder)
547                .forEach(filter -> {
548                    final Set<String> checkExternalResources =
549                        ((ExternalResourceHolder) filter).getExternalResourceLocations();
550                    externalConfigurationResources.addAll(checkExternalResources);
551                });
552        return externalConfigurationResources;
553    }
554
555    /**
556     * Returns a set of external configuration resource locations which are used by the checks set.
557     * @param checks a set of checks.
558     * @return a set of external configuration resource locations which are used by the checks set.
559     */
560    private static Set<String> getExternalResourceLocationsOfChecks(Set<AbstractCheck> checks) {
561        final Set<String> externalConfigurationResources = new HashSet<>();
562        checks.stream().filter(check -> check instanceof ExternalResourceHolder).forEach(check -> {
563            final Set<String> checkExternalResources =
564                ((ExternalResourceHolder) check).getExternalResourceLocations();
565            externalConfigurationResources.addAll(checkExternalResources);
566        });
567        return externalConfigurationResources;
568    }
569
570    /**
571     * Processes a node calling interested checks at each node.
572     * Uses iterative algorithm.
573     * @param root the root of tree for process
574     * @param astState state of AST.
575     */
576    private void processIter(DetailAST root, AstState astState) {
577        DetailAST curNode = root;
578        while (curNode != null) {
579            notifyVisit(curNode, astState);
580            DetailAST toVisit = curNode.getFirstChild();
581            while (curNode != null && toVisit == null) {
582                notifyLeave(curNode, astState);
583                toVisit = curNode.getNextSibling();
584                if (toVisit == null) {
585                    curNode = curNode.getParent();
586                }
587            }
588            curNode = toVisit;
589        }
590    }
591
592    /**
593     * Appends comment nodes to existing AST.
594     * It traverses each node in AST, looks for hidden comment tokens
595     * and appends found comment tokens as nodes in AST.
596     * @param root
597     *        root of AST.
598     * @return root of AST with comment nodes.
599     */
600    private static DetailAST appendHiddenCommentNodes(DetailAST root) {
601        DetailAST result = root;
602        DetailAST curNode = root;
603        DetailAST lastNode = root;
604
605        while (curNode != null) {
606            if (isPositionGreater(curNode, lastNode)) {
607                lastNode = curNode;
608            }
609
610            CommonHiddenStreamToken tokenBefore = curNode.getHiddenBefore();
611            DetailAST currentSibling = curNode;
612            while (tokenBefore != null) {
613                final DetailAST newCommentNode =
614                         createCommentAstFromToken(tokenBefore);
615
616                currentSibling.addPreviousSibling(newCommentNode);
617
618                if (currentSibling == result) {
619                    result = newCommentNode;
620                }
621
622                currentSibling = newCommentNode;
623                tokenBefore = tokenBefore.getHiddenBefore();
624            }
625
626            DetailAST toVisit = curNode.getFirstChild();
627            while (curNode != null && toVisit == null) {
628                toVisit = curNode.getNextSibling();
629                if (toVisit == null) {
630                    curNode = curNode.getParent();
631                }
632            }
633            curNode = toVisit;
634        }
635        if (lastNode != null) {
636            CommonHiddenStreamToken tokenAfter = lastNode.getHiddenAfter();
637            DetailAST currentSibling = lastNode;
638            while (tokenAfter != null) {
639                final DetailAST newCommentNode =
640                        createCommentAstFromToken(tokenAfter);
641
642                currentSibling.addNextSibling(newCommentNode);
643
644                currentSibling = newCommentNode;
645                tokenAfter = tokenAfter.getHiddenAfter();
646            }
647        }
648        return result;
649    }
650
651    /**
652     * Checks if position of first DetailAST is greater than position of
653     * second DetailAST. Position is line number and column number in source
654     * file.
655     * @param ast1
656     *        first DetailAST node.
657     * @param ast2
658     *        second DetailAST node.
659     * @return true if position of ast1 is greater than position of ast2.
660     */
661    private static boolean isPositionGreater(DetailAST ast1, DetailAST ast2) {
662        boolean isGreater = ast1.getLineNo() > ast2.getLineNo();
663        if (!isGreater && ast1.getLineNo() == ast2.getLineNo()) {
664            isGreater = ast1.getColumnNo() > ast2.getColumnNo();
665        }
666        return isGreater;
667    }
668
669    /**
670     * Create comment AST from token. Depending on token type
671     * SINGLE_LINE_COMMENT or BLOCK_COMMENT_BEGIN is created.
672     * @param token
673     *        Token object.
674     * @return DetailAST of comment node.
675     */
676    private static DetailAST createCommentAstFromToken(Token token) {
677        final DetailAST commentAst;
678        if (token.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
679            commentAst = createSlCommentNode(token);
680        }
681        else {
682            commentAst = CommonUtils.createBlockCommentNode(token);
683        }
684        return commentAst;
685    }
686
687    /**
688     * Create single-line comment from token.
689     * @param token
690     *        Token object.
691     * @return DetailAST with SINGLE_LINE_COMMENT type.
692     */
693    private static DetailAST createSlCommentNode(Token token) {
694        final DetailAST slComment = new DetailAST();
695        slComment.setType(TokenTypes.SINGLE_LINE_COMMENT);
696        slComment.setText("//");
697
698        // column counting begins from 0
699        slComment.setColumnNo(token.getColumn() - 1);
700        slComment.setLineNo(token.getLine());
701
702        final DetailAST slCommentContent = new DetailAST();
703        slCommentContent.setType(TokenTypes.COMMENT_CONTENT);
704
705        // column counting begins from 0
706        // plus length of '//'
707        slCommentContent.setColumnNo(token.getColumn() - 1 + 2);
708        slCommentContent.setLineNo(token.getLine());
709        slCommentContent.setText(token.getText());
710
711        slComment.addChild(slCommentContent);
712        return slComment;
713    }
714
715    /**
716     * State of AST.
717     * Indicates whether tree contains certain nodes.
718     */
719    private enum AstState {
720        /**
721         * Ordinary tree.
722         */
723        ORDINARY,
724
725        /**
726         * AST contains comment nodes.
727         */
728        WITH_COMMENTS
729    }
730}