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;
021
022import java.io.File;
023import java.util.Arrays;
024import java.util.Collection;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.Locale;
028import java.util.Map;
029import java.util.Set;
030import java.util.SortedSet;
031import java.util.TreeSet;
032
033import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
034import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
035import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
036import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
037import com.puppycrawl.tools.checkstyle.api.Configuration;
038import com.puppycrawl.tools.checkstyle.api.Context;
039import com.puppycrawl.tools.checkstyle.api.DetailAST;
040import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
041import com.puppycrawl.tools.checkstyle.api.FileContents;
042import com.puppycrawl.tools.checkstyle.api.FileText;
043import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
044import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
045
046/**
047 * Responsible for walking an abstract syntax tree and notifying interested
048 * checks at each each node.
049 *
050 */
051@FileStatefulCheck
052public final class TreeWalker extends AbstractFileSetCheck implements ExternalResourceHolder {
053
054    /** Maps from token name to ordinary checks. */
055    private final Map<String, Set<AbstractCheck>> tokenToOrdinaryChecks =
056        new HashMap<>();
057
058    /** Maps from token name to comment checks. */
059    private final Map<String, Set<AbstractCheck>> tokenToCommentChecks =
060            new HashMap<>();
061
062    /** Registered ordinary checks, that don't use comment nodes. */
063    private final Set<AbstractCheck> ordinaryChecks = new HashSet<>();
064
065    /** Registered comment checks. */
066    private final Set<AbstractCheck> commentChecks = new HashSet<>();
067
068    /** The ast filters. */
069    private final Set<TreeWalkerFilter> filters = new HashSet<>();
070
071    /** The sorted set of messages. */
072    private final SortedSet<LocalizedMessage> messages = new TreeSet<>();
073
074    /** Context of child components. */
075    private Context childContext;
076
077    /** A factory for creating submodules (i.e. the Checks) */
078    private ModuleFactory moduleFactory;
079
080    /**
081     * Creates a new {@code TreeWalker} instance.
082     */
083    public TreeWalker() {
084        setFileExtensions("java");
085    }
086
087    /**
088     * Sets classLoader to load class.
089     * @param classLoader class loader to resolve classes with.
090     * @deprecated Checkstyle is not type aware tool and all class loading is potentially
091     *     unstable.
092     */
093    @Deprecated
094    public void setClassLoader(ClassLoader classLoader) {
095        // no code
096    }
097
098    /**
099     * Sets the module factory for creating child modules (Checks).
100     * @param moduleFactory the factory
101     */
102    public void setModuleFactory(ModuleFactory moduleFactory) {
103        this.moduleFactory = moduleFactory;
104    }
105
106    @Override
107    public void finishLocalSetup() {
108        final DefaultContext checkContext = new DefaultContext();
109        checkContext.add("severity", getSeverity());
110        checkContext.add("tabWidth", String.valueOf(getTabWidth()));
111
112        childContext = checkContext;
113    }
114
115    /**
116     * {@inheritDoc} Creates child module.
117     * @noinspection ChainOfInstanceofChecks
118     */
119    @Override
120    public void setupChild(Configuration childConf)
121            throws CheckstyleException {
122        final String name = childConf.getName();
123        final Object module;
124
125        try {
126            module = moduleFactory.createModule(name);
127            if (module instanceof AutomaticBean) {
128                final AutomaticBean bean = (AutomaticBean) module;
129                bean.contextualize(childContext);
130                bean.configure(childConf);
131            }
132        }
133        catch (final CheckstyleException ex) {
134            throw new CheckstyleException("cannot initialize module " + name
135                    + " - " + ex.getMessage(), ex);
136        }
137        if (module instanceof AbstractCheck) {
138            final AbstractCheck check = (AbstractCheck) module;
139            check.init();
140            registerCheck(check);
141        }
142        else if (module instanceof TreeWalkerFilter) {
143            final TreeWalkerFilter filter = (TreeWalkerFilter) module;
144            filters.add(filter);
145        }
146        else {
147            throw new CheckstyleException(
148                "TreeWalker is not allowed as a parent of " + name
149                        + " Please review 'Parent Module' section for this Check in web"
150                        + " documentation if Check is standard.");
151        }
152    }
153
154    @Override
155    protected void processFiltered(File file, FileText fileText) throws CheckstyleException {
156        // check if already checked and passed the file
157        if (!ordinaryChecks.isEmpty() || !commentChecks.isEmpty()) {
158            final FileContents contents = getFileContents();
159            final DetailAST rootAST = JavaParser.parse(contents);
160            if (!ordinaryChecks.isEmpty()) {
161                walk(rootAST, contents, AstState.ORDINARY);
162            }
163            if (!commentChecks.isEmpty()) {
164                final DetailAST astWithComments = JavaParser.appendHiddenCommentNodes(rootAST);
165                walk(astWithComments, contents, AstState.WITH_COMMENTS);
166            }
167            if (filters.isEmpty()) {
168                addMessages(messages);
169            }
170            else {
171                final SortedSet<LocalizedMessage> filteredMessages =
172                    getFilteredMessages(file.getAbsolutePath(), contents, rootAST);
173                addMessages(filteredMessages);
174            }
175            messages.clear();
176        }
177    }
178
179    /**
180     * Returns filtered set of {@link LocalizedMessage}.
181     * @param fileName path to the file
182     * @param fileContents the contents of the file
183     * @param rootAST root AST element {@link DetailAST} of the file
184     * @return filtered set of messages
185     */
186    private SortedSet<LocalizedMessage> getFilteredMessages(
187            String fileName, FileContents fileContents, DetailAST rootAST) {
188        final SortedSet<LocalizedMessage> result = new TreeSet<>(messages);
189        for (LocalizedMessage element : messages) {
190            final TreeWalkerAuditEvent event =
191                    new TreeWalkerAuditEvent(fileContents, fileName, element, rootAST);
192            for (TreeWalkerFilter filter : filters) {
193                if (!filter.accept(event)) {
194                    result.remove(element);
195                    break;
196                }
197            }
198        }
199        return result;
200    }
201
202    /**
203     * Register a check for a given configuration.
204     * @param check the check to register
205     * @throws CheckstyleException if an error occurs
206     */
207    private void registerCheck(AbstractCheck check) throws CheckstyleException {
208        final int[] tokens;
209        final Set<String> checkTokens = check.getTokenNames();
210        if (checkTokens.isEmpty()) {
211            tokens = check.getDefaultTokens();
212        }
213        else {
214            tokens = check.getRequiredTokens();
215
216            //register configured tokens
217            final int[] acceptableTokens = check.getAcceptableTokens();
218            Arrays.sort(acceptableTokens);
219            for (String token : checkTokens) {
220                final int tokenId = TokenUtil.getTokenId(token);
221                if (Arrays.binarySearch(acceptableTokens, tokenId) >= 0) {
222                    registerCheck(token, check);
223                }
224                else {
225                    final String message = String.format(Locale.ROOT, "Token \"%s\" was "
226                            + "not found in Acceptable tokens list in check %s",
227                            token, check.getClass().getName());
228                    throw new CheckstyleException(message);
229                }
230            }
231        }
232        for (int element : tokens) {
233            registerCheck(element, check);
234        }
235        if (check.isCommentNodesRequired()) {
236            commentChecks.add(check);
237        }
238        else {
239            ordinaryChecks.add(check);
240        }
241    }
242
243    /**
244     * Register a check for a specified token id.
245     * @param tokenId the id of the token
246     * @param check the check to register
247     * @throws CheckstyleException if Check is misconfigured
248     */
249    private void registerCheck(int tokenId, AbstractCheck check) throws CheckstyleException {
250        registerCheck(TokenUtil.getTokenName(tokenId), check);
251    }
252
253    /**
254     * Register a check for a specified token name.
255     * @param token the name of the token
256     * @param check the check to register
257     * @throws CheckstyleException if Check is misconfigured
258     */
259    private void registerCheck(String token, AbstractCheck check) throws CheckstyleException {
260        if (check.isCommentNodesRequired()) {
261            tokenToCommentChecks.computeIfAbsent(token, empty -> new HashSet<>()).add(check);
262        }
263        else if (TokenUtil.isCommentType(token)) {
264            final String message = String.format(Locale.ROOT, "Check '%s' waits for comment type "
265                    + "token ('%s') and should override 'isCommentNodesRequired()' "
266                    + "method to return 'true'", check.getClass().getName(), token);
267            throw new CheckstyleException(message);
268        }
269        else {
270            tokenToOrdinaryChecks.computeIfAbsent(token, empty -> new HashSet<>()).add(check);
271        }
272    }
273
274    /**
275     * Initiates the walk of an AST.
276     * @param ast the root AST
277     * @param contents the contents of the file the AST was generated from.
278     * @param astState state of AST.
279     */
280    private void walk(DetailAST ast, FileContents contents,
281            AstState astState) {
282        notifyBegin(ast, contents, astState);
283        processIter(ast, astState);
284        notifyEnd(ast, astState);
285    }
286
287    /**
288     * Notify checks that we are about to begin walking a tree.
289     * @param rootAST the root of the tree.
290     * @param contents the contents of the file the AST was generated from.
291     * @param astState state of AST.
292     */
293    private void notifyBegin(DetailAST rootAST, FileContents contents,
294            AstState astState) {
295        final Set<AbstractCheck> checks;
296
297        if (astState == AstState.WITH_COMMENTS) {
298            checks = commentChecks;
299        }
300        else {
301            checks = ordinaryChecks;
302        }
303
304        for (AbstractCheck check : checks) {
305            check.setFileContents(contents);
306            check.clearMessages();
307            check.beginTree(rootAST);
308        }
309    }
310
311    /**
312     * Notify checks that we have finished walking a tree.
313     * @param rootAST the root of the tree.
314     * @param astState state of AST.
315     */
316    private void notifyEnd(DetailAST rootAST, AstState astState) {
317        final Set<AbstractCheck> checks;
318
319        if (astState == AstState.WITH_COMMENTS) {
320            checks = commentChecks;
321        }
322        else {
323            checks = ordinaryChecks;
324        }
325
326        for (AbstractCheck check : checks) {
327            check.finishTree(rootAST);
328            messages.addAll(check.getMessages());
329        }
330    }
331
332    /**
333     * Notify checks that visiting a node.
334     * @param ast the node to notify for.
335     * @param astState state of AST.
336     */
337    private void notifyVisit(DetailAST ast, AstState astState) {
338        final Collection<AbstractCheck> visitors = getListOfChecks(ast, astState);
339
340        if (visitors != null) {
341            for (AbstractCheck check : visitors) {
342                check.visitToken(ast);
343            }
344        }
345    }
346
347    /**
348     * Notify checks that leaving a node.
349     * @param ast
350     *        the node to notify for
351     * @param astState state of AST.
352     */
353    private void notifyLeave(DetailAST ast, AstState astState) {
354        final Collection<AbstractCheck> visitors = getListOfChecks(ast, astState);
355
356        if (visitors != null) {
357            for (AbstractCheck check : visitors) {
358                check.leaveToken(ast);
359            }
360        }
361    }
362
363    /**
364     * Method returns list of checks.
365     *
366     * @param ast
367     *            the node to notify for
368     * @param astState
369     *            state of AST.
370     * @return list of visitors
371     */
372    private Collection<AbstractCheck> getListOfChecks(DetailAST ast, AstState astState) {
373        final Collection<AbstractCheck> visitors;
374        final String tokenType = TokenUtil.getTokenName(ast.getType());
375
376        if (astState == AstState.WITH_COMMENTS) {
377            visitors = tokenToCommentChecks.get(tokenType);
378        }
379        else {
380            visitors = tokenToOrdinaryChecks.get(tokenType);
381        }
382        return visitors;
383    }
384
385    @Override
386    public void destroy() {
387        ordinaryChecks.forEach(AbstractCheck::destroy);
388        commentChecks.forEach(AbstractCheck::destroy);
389        super.destroy();
390    }
391
392    @Override
393    public Set<String> getExternalResourceLocations() {
394        final Set<String> ordinaryChecksResources =
395                getExternalResourceLocationsOfChecks(ordinaryChecks);
396        final Set<String> commentChecksResources =
397                getExternalResourceLocationsOfChecks(commentChecks);
398        final Set<String> filtersResources =
399                getExternalResourceLocationsOfFilters();
400        final int resultListSize = commentChecksResources.size()
401                + ordinaryChecksResources.size()
402                + filtersResources.size();
403        final Set<String> resourceLocations = new HashSet<>(resultListSize);
404        resourceLocations.addAll(ordinaryChecksResources);
405        resourceLocations.addAll(commentChecksResources);
406        resourceLocations.addAll(filtersResources);
407        return resourceLocations;
408    }
409
410    /**
411     * Returns a set of external configuration resource locations which are used by the filters set.
412     * @return a set of external configuration resource locations which are used by the filters set.
413     */
414    private Set<String> getExternalResourceLocationsOfFilters() {
415        final Set<String> externalConfigurationResources = new HashSet<>();
416        filters.stream().filter(filter -> filter instanceof ExternalResourceHolder)
417                .forEach(filter -> {
418                    final Set<String> checkExternalResources =
419                        ((ExternalResourceHolder) filter).getExternalResourceLocations();
420                    externalConfigurationResources.addAll(checkExternalResources);
421                });
422        return externalConfigurationResources;
423    }
424
425    /**
426     * Returns a set of external configuration resource locations which are used by the checks set.
427     * @param checks a set of checks.
428     * @return a set of external configuration resource locations which are used by the checks set.
429     */
430    private static Set<String> getExternalResourceLocationsOfChecks(Set<AbstractCheck> checks) {
431        final Set<String> externalConfigurationResources = new HashSet<>();
432        checks.stream().filter(check -> check instanceof ExternalResourceHolder).forEach(check -> {
433            final Set<String> checkExternalResources =
434                ((ExternalResourceHolder) check).getExternalResourceLocations();
435            externalConfigurationResources.addAll(checkExternalResources);
436        });
437        return externalConfigurationResources;
438    }
439
440    /**
441     * Processes a node calling interested checks at each node.
442     * Uses iterative algorithm.
443     * @param root the root of tree for process
444     * @param astState state of AST.
445     */
446    private void processIter(DetailAST root, AstState astState) {
447        DetailAST curNode = root;
448        while (curNode != null) {
449            notifyVisit(curNode, astState);
450            DetailAST toVisit = curNode.getFirstChild();
451            while (curNode != null && toVisit == null) {
452                notifyLeave(curNode, astState);
453                toVisit = curNode.getNextSibling();
454                curNode = curNode.getParent();
455            }
456            curNode = toVisit;
457        }
458    }
459
460    /**
461     * State of AST.
462     * Indicates whether tree contains certain nodes.
463     */
464    private enum AstState {
465
466        /**
467         * Ordinary tree.
468         */
469        ORDINARY,
470
471        /**
472         * AST contains comment nodes.
473         */
474        WITH_COMMENTS,
475
476    }
477
478}