001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2009 SonarSource SA
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.ArrayList;
023    import java.util.Arrays;
024    import java.util.HashMap;
025    import java.util.List;
026    import java.util.Map;
027    
028    import org.sonar.java.language.ArgumentAndReturnType;
029    import org.sonar.java.language.ArgumentType;
030    import org.sonar.java.language.JavaMethodSignature;
031    import org.sonar.java.language.JavaType;
032    import org.sonar.java.language.ReturnType;
033    import org.sonar.squid.api.SourceMethod;
034    import org.sonar.squid.measures.Metric;
035    
036    import com.puppycrawl.tools.checkstyle.api.DetailAST;
037    import com.puppycrawl.tools.checkstyle.api.TokenTypes;
038    
039    public class MethodVisitor extends AstVisitor {
040    
041      private static final String CONSTRUCTOR = "<init>";
042    
043      private static final List<Integer> wantedTokens = Arrays.asList(TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF);
044      private static final Map<Integer, JavaType> tokenJavaTypeMapping = new HashMap<Integer, JavaType>();
045    
046      static {
047        tokenJavaTypeMapping.put(TokenTypes.LITERAL_BYTE, JavaType.BYTE);
048        tokenJavaTypeMapping.put(TokenTypes.LITERAL_CHAR, JavaType.CHAR);
049        tokenJavaTypeMapping.put(TokenTypes.LITERAL_SHORT, JavaType.SHORT);
050        tokenJavaTypeMapping.put(TokenTypes.LITERAL_INT, JavaType.INT);
051        tokenJavaTypeMapping.put(TokenTypes.LITERAL_LONG, JavaType.LONG);
052        tokenJavaTypeMapping.put(TokenTypes.LITERAL_BOOLEAN, JavaType.BOOLEAN);
053        tokenJavaTypeMapping.put(TokenTypes.LITERAL_FLOAT, JavaType.FLOAT);
054        tokenJavaTypeMapping.put(TokenTypes.LITERAL_DOUBLE, JavaType.DOUBLE);
055        tokenJavaTypeMapping.put(TokenTypes.LITERAL_VOID, JavaType.VOID);
056      }
057    
058      @Override
059      public List<Integer> getWantedTokens() {
060        return wantedTokens;
061      }
062    
063      @Override
064      public void visitToken(DetailAST ast) {
065        boolean isConstructor = isConstructor(ast);
066        String methodName = buildMethodSignature(ast, isConstructor);
067        SourceMethod sourceMethod = new SourceMethod(peekParentClass(), methodName, ast.getLineNo());
068        sourceMethod.setMeasure(Metric.METHODS, 1);
069        if (isConstructor) {
070          sourceMethod.setMeasure(Metric.CONSTRUCTORS, 1);
071        }
072        addResource(sourceMethod);
073      }
074    
075      private boolean isConstructor(DetailAST ast) {
076        return ast.getType() == TokenTypes.CTOR_DEF;
077      }
078    
079      @Override
080      public void leaveToken(DetailAST ast) {
081        popResource();
082      }
083    
084      private String buildMethodSignature(DetailAST ast, boolean isConstructor) {
085        String methodName = extractMethodName(ast, isConstructor);
086        ReturnType returnType = extractMethodReturnType(ast, isConstructor);
087        List<ArgumentType> argumentTypes = extractMethodArgumentTypes(ast);
088        JavaMethodSignature signature = new JavaMethodSignature(methodName, returnType, argumentTypes);
089        return signature.dumpMethodSignature();
090      }
091    
092      private List<ArgumentType> extractMethodArgumentTypes(DetailAST ast) {
093        List<ArgumentType> argumentTypes = new ArrayList<ArgumentType>();
094        DetailAST child = ast.findFirstToken(TokenTypes.PARAMETERS).findFirstToken(TokenTypes.PARAMETER_DEF);
095        while (child != null) {
096          if (child.getType() == TokenTypes.PARAMETER_DEF) {
097            ArgumentAndReturnType argumentType = extractArgumentAndRreturnType(child.findFirstToken(TokenTypes.TYPE));
098            argumentTypes.add(new ArgumentType(argumentType));
099          }
100          child = (DetailAST) child.getNextSibling();
101        }
102        return argumentTypes;
103      }
104    
105      private String extractMethodName(DetailAST ast, boolean isConstructor) {
106        if (isConstructor) {
107          return CONSTRUCTOR;
108        }
109        return ast.findFirstToken(TokenTypes.IDENT).getText();
110      }
111    
112      private ReturnType extractMethodReturnType(DetailAST ast, boolean isConstructor) {
113        if (isConstructor) {
114          return null;
115        }
116        ArgumentAndReturnType returnType = extractArgumentAndRreturnType(ast.findFirstToken(TokenTypes.TYPE));
117        return new ReturnType(returnType);
118      }
119    
120      private ArgumentAndReturnType extractArgumentAndRreturnType(DetailAST ast) {
121        boolean isArray = isArrayType(ast);
122        for (Integer tokenType : tokenJavaTypeMapping.keySet()) {
123          if (ast.branchContains(tokenType)) {
124            return new ArgumentAndReturnType(tokenJavaTypeMapping.get(tokenType), isArray);
125          }
126        }
127        if (isObjectType(ast)) {
128          String className;
129          if (isArray) {
130            className = extractClassName(drilldownToLastArray(ast));
131          } else {
132            className = extractClassName(ast);
133          }
134          return new ArgumentAndReturnType(className, isArray);
135        }
136        throw new IllegalStateException("No Known TokenType has been found at line " + ast.getLineNo() + " of file "
137            + getFileContents().getFilename());
138      }
139    
140      private DetailAST drilldownToLastArray(DetailAST ast) {
141        while (ast.findFirstToken(TokenTypes.ARRAY_DECLARATOR) != null) {
142          ast = ast.findFirstToken(TokenTypes.ARRAY_DECLARATOR);
143        }
144        return ast;
145      }
146    
147      private String extractClassName(DetailAST ast) {
148        if (ast.findFirstToken(TokenTypes.DOT) != null) {
149          return ast.findFirstToken(TokenTypes.DOT).getLastChild().getText();
150        } else {
151          return ast.findFirstToken(TokenTypes.IDENT).getText();
152        }
153      }
154    
155      private boolean isObjectType(DetailAST ast) {
156        return ast.branchContains(TokenTypes.IDENT);
157      }
158    
159      private boolean isArrayType(DetailAST ast) {
160        return (ast.findFirstToken(TokenTypes.IDENT)==null) && (ast.branchContains(TokenTypes.ARRAY_DECLARATOR));
161      }
162    }