001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 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 */
020package org.sonar.java.ast.visitor;
021
022import java.util.*;
023
024import org.sonar.java.signature.JvmJavaType;
025import org.sonar.java.signature.MethodSignature;
026import org.sonar.java.signature.MethodSignaturePrinter;
027import org.sonar.java.signature.Parameter;
028import org.sonar.squid.api.SourceMethod;
029import org.sonar.squid.measures.Metric;
030
031import antlr.collections.AST;
032
033import com.puppycrawl.tools.checkstyle.api.DetailAST;
034import com.puppycrawl.tools.checkstyle.api.TokenTypes;
035
036public 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 = 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}