001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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.checks;
021
022import java.util.Deque;
023import java.util.Map;
024import java.util.Queue;
025import java.util.Set;
026
027import com.google.common.collect.Lists;
028import com.google.common.collect.Maps;
029import com.google.common.collect.Sets;
030import com.puppycrawl.tools.checkstyle.api.Check;
031import com.puppycrawl.tools.checkstyle.api.DetailAST;
032import com.puppycrawl.tools.checkstyle.api.TokenTypes;
033import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
034
035/**
036 * Abstract class for checks which need to collect information about
037 * declared members/parameters/variables.
038 *
039 * @author o_sukhodolsky
040 */
041public abstract class AbstractDeclarationCollector extends Check {
042    /**
043     * Tree of all the parsed frames.
044     */
045    private Map<DetailAST, LexicalFrame> frames;
046
047    /**
048     * Frame for the currently processed AST.
049     */
050    private LexicalFrame current;
051
052    @Override
053    public void beginTree(DetailAST rootAST) {
054        final Deque<LexicalFrame> frameStack = Lists.newLinkedList();
055        frameStack.add(new GlobalFrame());
056
057        frames = Maps.newHashMap();
058
059        DetailAST curNode = rootAST;
060        while (curNode != null) {
061            collectDeclarations(frameStack, curNode);
062            DetailAST toVisit = curNode.getFirstChild();
063            while (curNode != null && toVisit == null) {
064                endCollectingDeclarations(frameStack, curNode);
065                toVisit = curNode.getNextSibling();
066                if (toVisit == null) {
067                    curNode = curNode.getParent();
068                }
069            }
070            curNode = toVisit;
071        }
072    }
073
074    @Override
075    public void visitToken(DetailAST ast) {
076        switch (ast.getType()) {
077            case TokenTypes.CLASS_DEF :
078            case TokenTypes.INTERFACE_DEF :
079            case TokenTypes.ENUM_DEF :
080            case TokenTypes.ANNOTATION_DEF :
081            case TokenTypes.SLIST :
082            case TokenTypes.METHOD_DEF :
083            case TokenTypes.CTOR_DEF :
084                current = frames.get(ast);
085                break;
086            default :
087                // do nothing
088        }
089    }
090
091    /**
092     * Parse the next AST for declarations.
093     *
094     * @param frameStack Stack containing the FrameTree being built
095     * @param ast AST to parse
096     */
097    private static void collectDeclarations(Deque<LexicalFrame> frameStack,
098        DetailAST ast) {
099        final LexicalFrame frame = frameStack.peek();
100        switch (ast.getType()) {
101            case TokenTypes.VARIABLE_DEF :
102                collectVariableDeclarations(ast, frame);
103                break;
104            case TokenTypes.PARAMETER_DEF :
105                final DetailAST parameterAST = ast.findFirstToken(TokenTypes.IDENT);
106                frame.addName(parameterAST.getText());
107                break;
108            case TokenTypes.CLASS_DEF :
109            case TokenTypes.INTERFACE_DEF :
110            case TokenTypes.ENUM_DEF :
111            case TokenTypes.ANNOTATION_DEF :
112                final DetailAST classAST = ast.findFirstToken(TokenTypes.IDENT);
113                frame.addName(classAST.getText());
114                frameStack.addFirst(new ClassFrame(frame));
115                break;
116            case TokenTypes.SLIST :
117                frameStack.addFirst(new BlockFrame(frame));
118                break;
119            case TokenTypes.METHOD_DEF :
120                final String name = ast.findFirstToken(TokenTypes.IDENT).getText();
121                if (frame instanceof ClassFrame) {
122                    final DetailAST mods =
123                            ast.findFirstToken(TokenTypes.MODIFIERS);
124                    if (mods.branchContains(TokenTypes.LITERAL_STATIC)) {
125                        ((ClassFrame) frame).addStaticMethod(name);
126                    }
127                    else {
128                        ((ClassFrame) frame).addInstanceMethod(name);
129                    }
130                }
131                frameStack.addFirst(new MethodFrame(frame));
132                break;
133            case TokenTypes.CTOR_DEF :
134                frameStack.addFirst(new MethodFrame(frame));
135                break;
136            default:
137                // do nothing
138        }
139    }
140
141    /**
142     * Collect Variable Declarations.
143     * @param ast variable token
144     * @param frame current frame
145     */
146    private static void collectVariableDeclarations(DetailAST ast, LexicalFrame frame) {
147        final String name =
148                ast.findFirstToken(TokenTypes.IDENT).getText();
149        if (frame instanceof ClassFrame) {
150            final DetailAST mods =
151                    ast.findFirstToken(TokenTypes.MODIFIERS);
152            if (ScopeUtils.isInInterfaceBlock(ast)
153                    || mods.branchContains(TokenTypes.LITERAL_STATIC)) {
154                ((ClassFrame) frame).addStaticMember(name);
155            }
156            else {
157                ((ClassFrame) frame).addInstanceMember(name);
158            }
159        }
160        else {
161            frame.addName(name);
162        }
163    }
164
165    /**
166     * End parsing of the AST for declarations.
167     *
168     * @param frameStack Stack containing the FrameTree being built
169     * @param ast AST that was parsed
170     */
171    private void endCollectingDeclarations(Queue<LexicalFrame> frameStack,
172        DetailAST ast) {
173        switch (ast.getType()) {
174            case TokenTypes.CLASS_DEF :
175            case TokenTypes.INTERFACE_DEF :
176            case TokenTypes.ENUM_DEF :
177            case TokenTypes.ANNOTATION_DEF :
178            case TokenTypes.SLIST :
179            case TokenTypes.METHOD_DEF :
180            case TokenTypes.CTOR_DEF :
181                frames.put(ast, frameStack.poll());
182                break;
183            default :
184                // do nothing
185        }
186    }
187
188    /**
189     * Check if given name is a name for class field in current environment.
190     * @param name a name to check
191     * @return true is the given name is name of member.
192     */
193    protected final boolean isClassField(String name) {
194        final LexicalFrame frame = findFrame(name);
195        return frame instanceof ClassFrame
196                && ((ClassFrame) frame).hasInstanceMember(name);
197    }
198
199    /**
200     * Check if given name is a name for class method in current environment.
201     * @param name a name to check
202     * @return true is the given name is name of method.
203     */
204    protected final boolean isClassMethod(String name) {
205        final LexicalFrame frame = findFrame(name);
206        return frame instanceof ClassFrame
207                && ((ClassFrame) frame).hasInstanceMethod(name);
208    }
209
210    /**
211     * Find frame containing declaration.
212     * @param name name of the declaration to find
213     * @return LexicalFrame containing declaration or null
214     */
215    private LexicalFrame findFrame(String name) {
216        if (current != null) {
217            return current.getIfContains(name);
218        }
219        else {
220            return null;
221        }
222    }
223
224    /**
225     * A declaration frame.
226     * @author Stephen Bloch
227     */
228    private static class LexicalFrame {
229        /** Set of name of variables declared in this frame. */
230        private final Set<String> varNames;
231        /**
232         * Parent frame.
233         */
234        private final LexicalFrame parent;
235
236        /**
237         * Constructor -- invokable only via super() from subclasses.
238         *
239         * @param parent parent frame
240         */
241        protected LexicalFrame(LexicalFrame parent) {
242            this.parent = parent;
243            varNames = Sets.newHashSet();
244        }
245
246        /** Add a name to the frame.
247         * @param nameToAdd the name we're adding
248         */
249        void addName(String nameToAdd) {
250            varNames.add(nameToAdd);
251        }
252
253        /** Check whether the frame contains a given name.
254         * @param nameToFind the name we're looking for
255         * @return whether it was found
256         */
257        boolean contains(String nameToFind) {
258            return varNames.contains(nameToFind);
259        }
260
261        /** Check whether the frame contains a given name.
262         * @param nameToFind the name we're looking for
263         * @return whether it was found
264         */
265        LexicalFrame getIfContains(String nameToFind) {
266            LexicalFrame frame = null;
267
268            if (contains(nameToFind)) {
269                frame = this;
270            }
271            else if (parent != null) {
272                frame = parent.getIfContains(nameToFind);
273            }
274            return frame;
275        }
276    }
277
278    /**
279     * The global frame; should hold only class names.
280     * @author Stephen Bloch
281     */
282    private static class GlobalFrame extends LexicalFrame {
283
284        /**
285         * Constructor for the root of the FrameTree.
286         */
287        protected GlobalFrame() {
288            super(null);
289        }
290    }
291
292    /**
293     * A frame initiated at method definition; holds parameter names.
294     * @author Stephen Bloch
295     */
296    private static class MethodFrame extends LexicalFrame {
297        /**
298         * Creates method frame.
299         * @param parent parent frame
300         */
301        protected MethodFrame(LexicalFrame parent) {
302            super(parent);
303        }
304    }
305
306    /**
307     * A frame initiated at class definition; holds instance variable
308     * names.  For the present, I'm not worried about other class names,
309     * method names, etc.
310     * @author Stephen Bloch
311     */
312    private static class ClassFrame extends LexicalFrame {
313        /** Set of name of instance members declared in this frame. */
314        private final Set<String> instanceMembers;
315        /** Set of name of instance methods declared in this frame. */
316        private final Set<String> instanceMethods;
317        /** Set of name of variables declared in this frame. */
318        private final Set<String> staticMembers;
319        /** Set of name of static methods declared in this frame. */
320        private final Set<String> staticMethods;
321
322        /**
323         * Creates new instance of ClassFrame.
324         * @param parent parent frame
325         */
326        ClassFrame(LexicalFrame parent) {
327            super(parent);
328            instanceMembers = Sets.newHashSet();
329            instanceMethods = Sets.newHashSet();
330            staticMembers = Sets.newHashSet();
331            staticMethods = Sets.newHashSet();
332        }
333
334        /**
335         * Adds static member's name.
336         * @param name a name of static member of the class
337         */
338        public void addStaticMember(final String name) {
339            staticMembers.add(name);
340        }
341
342        /**
343         * Adds static method's name.
344         * @param name a name of static method of the class
345         */
346        public void addStaticMethod(final String name) {
347            staticMethods.add(name);
348        }
349
350        /**
351         * Adds instance member's name.
352         * @param name a name of instance member of the class
353         */
354        public void addInstanceMember(final String name) {
355            instanceMembers.add(name);
356        }
357
358        /**
359         * Adds instance method's name.
360         * @param name a name of instance method of the class
361         */
362        public void addInstanceMethod(final String name) {
363            instanceMethods.add(name);
364        }
365
366        /**
367         * Checks if a given name is a known instance member of the class.
368         * @param name a name to check
369         * @return true is the given name is a name of a known
370         *         instance member of the class
371         */
372        public boolean hasInstanceMember(final String name) {
373            return instanceMembers.contains(name);
374        }
375
376        /**
377         * Checks if a given name is a known instance method of the class.
378         * @param name a name to check
379         * @return true is the given name is a name of a known
380         *         instance method of the class
381         */
382        public boolean hasInstanceMethod(final String name) {
383            return instanceMethods.contains(name);
384        }
385
386        @Override
387        boolean contains(String nameToFind) {
388            return super.contains(nameToFind)
389                    || instanceMembers.contains(nameToFind)
390                    || instanceMethods.contains(nameToFind)
391                    || staticMembers.contains(nameToFind)
392                    || staticMethods.contains(nameToFind);
393        }
394    }
395
396    /**
397     * A frame initiated on entering a statement list; holds local variable
398     * names.  For the present, I'm not worried about other class names,
399     * method names, etc.
400     * @author Stephen Bloch
401     */
402    private static class BlockFrame extends LexicalFrame {
403
404        /**
405         * Creates block frame.
406         * @param parent parent frame
407         */
408        protected BlockFrame(LexicalFrame parent) {
409            super(parent);
410        }
411    }
412}