001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * Sonar is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 3 of the License, or (at your option) any later version.
010     *
011     * Sonar is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public
017     * License along with Sonar; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019     */
020    package org.sonar.java.ast.visitor;
021    
022    import java.util.*;
023    
024    import org.sonar.java.signature.JvmJavaType;
025    import org.sonar.java.signature.MethodSignature;
026    import org.sonar.java.signature.MethodSignaturePrinter;
027    import org.sonar.java.signature.Parameter;
028    import org.sonar.squid.api.SourceMethod;
029    import org.sonar.squid.measures.Metric;
030    
031    import antlr.collections.AST;
032    
033    import com.puppycrawl.tools.checkstyle.api.DetailAST;
034    import com.puppycrawl.tools.checkstyle.api.TokenTypes;
035    
036    public class MethodVisitor extends JavaAstVisitor {
037    
038      private static final String CONSTRUCTOR = "<init>";
039    
040      public static final List<Integer> WANTED_TOKENS = Arrays.asList(TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF);
041      private static final Map<Integer, JvmJavaType> TOKEN_JAVA_TYPE_MAPPING = new HashMap<Integer, JvmJavaType>();
042    
043      static {
044        TOKEN_JAVA_TYPE_MAPPING.put(TokenTypes.LITERAL_BYTE, JvmJavaType.B);
045        TOKEN_JAVA_TYPE_MAPPING.put(TokenTypes.LITERAL_CHAR, JvmJavaType.C);
046        TOKEN_JAVA_TYPE_MAPPING.put(TokenTypes.LITERAL_SHORT, JvmJavaType.S);
047        TOKEN_JAVA_TYPE_MAPPING.put(TokenTypes.LITERAL_INT, JvmJavaType.I);
048        TOKEN_JAVA_TYPE_MAPPING.put(TokenTypes.LITERAL_LONG, JvmJavaType.J);
049        TOKEN_JAVA_TYPE_MAPPING.put(TokenTypes.LITERAL_BOOLEAN, JvmJavaType.Z);
050        TOKEN_JAVA_TYPE_MAPPING.put(TokenTypes.LITERAL_FLOAT, JvmJavaType.F);
051        TOKEN_JAVA_TYPE_MAPPING.put(TokenTypes.LITERAL_DOUBLE, JvmJavaType.D);
052        TOKEN_JAVA_TYPE_MAPPING.put(TokenTypes.LITERAL_VOID, JvmJavaType.V);
053      }
054    
055      @Override
056      public List<Integer> getWantedTokens() {
057        return WANTED_TOKENS;
058      }
059    
060      @Override
061      public void visitToken(DetailAST ast) {
062        boolean isConstructor = isConstructor(ast);
063        String methodName = buildMethodSignature(ast, isConstructor);
064        SourceMethod sourceMethod = new SourceMethod(peekParentClass(), methodName, ast.getLineNo());
065        sourceMethod.setMeasure(Metric.METHODS, 1);
066        if (isConstructor) {
067          sourceMethod.setMeasure(Metric.CONSTRUCTORS, 1);
068        }
069        sourceMethod.setSuppressWarnings(SuppressWarningsAnnotationUtils.isSuppressAllWarnings(ast));
070        addSourceCode(sourceMethod);
071      }
072    
073      private boolean isConstructor(DetailAST ast) {
074        return ast.getType() == TokenTypes.CTOR_DEF;
075      }
076    
077      @Override
078      public void leaveToken(DetailAST ast) {
079        popSourceCode();
080      }
081    
082      private String buildMethodSignature(DetailAST ast, boolean isConstructor) {
083        String methodName = extractMethodName(ast, isConstructor);
084        Parameter returnType = extractMethodReturnType(ast, isConstructor);
085        List<Parameter> argumentTypes = extractMethodArgumentTypes(ast);
086        MethodSignature signature = new MethodSignature(methodName, returnType, argumentTypes);
087        return MethodSignaturePrinter.print(signature);
088      }
089    
090      private List<Parameter> extractMethodArgumentTypes(DetailAST ast) {
091        List<Parameter> argumentTypes = new ArrayList<Parameter>();
092        DetailAST child = ast.findFirstToken(TokenTypes.PARAMETERS).findFirstToken(TokenTypes.PARAMETER_DEF);
093        while (child != null) {
094          if (child.getType() == TokenTypes.PARAMETER_DEF) {
095            Parameter argumentType = extractArgumentAndReturnType(child.findFirstToken(TokenTypes.TYPE));
096            argumentTypes.add(new Parameter(argumentType));
097          }
098          child = (DetailAST) child.getNextSibling();
099        }
100        return argumentTypes;
101      }
102    
103      private String extractMethodName(DetailAST ast, boolean isConstructor) {
104        if (isConstructor) {
105          return CONSTRUCTOR;
106        }
107        return ast.findFirstToken(TokenTypes.IDENT).getText();
108      }
109    
110      private Parameter extractMethodReturnType(DetailAST ast, boolean isConstructor) {
111        if (isConstructor) {
112          return new Parameter(JvmJavaType.V, false);
113        }
114        Parameter returnType = extractArgumentAndReturnType(ast.findFirstToken(TokenTypes.TYPE));
115        return new Parameter(returnType);
116      }
117    
118      private Parameter extractArgumentAndReturnType(DetailAST ast) {
119        boolean isArray = isArrayType(ast);
120        for (Integer tokenType : TOKEN_JAVA_TYPE_MAPPING.keySet()) {
121          if (ast.branchContains(tokenType)) {
122            return new Parameter(TOKEN_JAVA_TYPE_MAPPING.get(tokenType), isArray);
123          }
124        }
125        if (isObjectType(ast)) {
126          String className;
127          if (isArray) {
128            className = extractClassName(drilldownToLastArray(ast));
129          } else {
130            className = extractClassName(ast);
131          }
132          return new Parameter(className, isArray);
133        }
134        throw new IllegalStateException("No Known TokenType has been found at line " + ast.getLineNo() + " of file "
135            + getFileContents().getFilename());
136      }
137    
138      private DetailAST drilldownToLastArray(DetailAST ast) {
139        while (ast.findFirstToken(TokenTypes.ARRAY_DECLARATOR) != null) {
140          ast = ast.findFirstToken(TokenTypes.ARRAY_DECLARATOR);
141        }
142        return ast;
143      }
144    
145      private String extractClassName(DetailAST ast) {
146        if (ast.findFirstToken(TokenTypes.DOT) != null) {
147          return findLastToken(ast.findFirstToken(TokenTypes.DOT), TokenTypes.IDENT).getText();
148        } else {
149          return findLastToken(ast, TokenTypes.IDENT).getText();
150        }
151      }
152    
153      private DetailAST findLastToken(DetailAST astNode, int tokenType) {
154        DetailAST retVal = null;
155        for (AST child = astNode.getFirstChild(); child != null; child = child.getNextSibling()) {
156          if (child.getType() == tokenType) {
157            retVal = (DetailAST) child;
158          }
159        }
160        return retVal;
161      }
162    
163      private boolean isObjectType(DetailAST ast) {
164        return ast.branchContains(TokenTypes.IDENT);
165      }
166    
167      private boolean isArrayType(DetailAST ast) {
168        return (ast.findFirstToken(TokenTypes.IDENT) == null) && (ast.branchContains(TokenTypes.ARRAY_DECLARATOR));
169      }
170    }