text: package org.testng.eclipse.util.param;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  
  import org.eclipse.jdt.core.IMethod;
  import org.eclipse.jdt.core.IType;
  import org.eclipse.jdt.core.dom.ASTVisitor;
  import org.eclipse.jdt.core.dom.ArrayInitializer;
  import org.eclipse.jdt.core.dom.Expression;
  import org.eclipse.jdt.core.dom.MarkerAnnotation;
  import org.eclipse.jdt.core.dom.MemberValuePair;
  import org.eclipse.jdt.core.dom.MethodDeclaration;
  import org.eclipse.jdt.core.dom.NormalAnnotation;
  import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
  import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
  import org.eclipse.jdt.core.dom.StringLiteral;
  import org.eclipse.jdt.core.dom.TypeDeclaration;
  
  import com.google.common.base.Strings;
  
  /**
   * Visitor for TestNG methods.
   */
  public class TestNGMethodParameterVisitor extends ASTVisitor {
    private static final String ANNOTATION_PACKAGE = "org.testng.annotations.";
    private static final String PARAMETER_ANNOTATION = "Parameters";
    private static final String PARAMETER_ANNOTATION_FQN =  ANNOTATION_PACKAGE + PARAMETER_ANNOTATION;
    private static final String CONFIGURATION_ANNOTATION = "Configuration";
    private static final String CONFIGURATION_ANNOTATION_FQN = ANNOTATION_PACKAGE + CONFIGURATION_ANNOTATION;
    private static final String TEST_ANNOTATION = "Test";
    private static final String TEST_ANNOTATION_FQN = ANNOTATION_PACKAGE + TEST_ANNOTATION;
    private static final String DEFAULT_PARAM_VALUE = "param-val-not-found";
  
    private Map<MethodDeclaration, List<String>> m_parameters = new HashMap<>();
    private IType m_typeFilter;
    private IMethod m_methodFilter;
  
    public TestNGMethodParameterVisitor() {
    }
  
    public TestNGMethodParameterVisitor(IMethod methodOnly) {
      m_methodFilter= methodOnly;
    }
  
    public TestNGMethodParameterVisitor(IType typeOnly) {
      m_typeFilter= typeOnly;
    }
  
    @Override
    public boolean visit(TypeDeclaration node) {
      if(null != m_typeFilter) {
        return node.getName().getIdentifier().equals(m_typeFilter.getElementName());
      }
      else {
        return true;
      }
    }
  
    @Override
    public boolean visit(MethodDeclaration node) {
      if(null != m_methodFilter) {
        return node.getName().getIdentifier().equals(m_methodFilter.getElementName())
            && node.parameters().size() == m_methodFilter.getNumberOfParameters(); 
      }
      else {
        return true;
      }
    }
  
    /**
     * No MarkerAnnotated method can have parameters. Ignore.
     * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.MarkerAnnotation)
     */
    @Override
    public boolean visit(MarkerAnnotation annotation) {
      return false;
    }
  
    @Override
    public boolean visit(NormalAnnotation annotation) {
      if(!isKnownAnnotation(annotation.getTypeName().getFullyQualifiedName())) {
        return false;
      }
  
      List<?> values= annotation.values();
  
      if(null != values && !values.isEmpty()) {
        for(int i= 0; i < values.size(); i++) {
          MemberValuePair pair= (MemberValuePair) values.get(i);
          if("parameters".equals(pair.getName().toString())) {
            Expression paramAttr= pair.getValue();
            if(paramAttr instanceof ArrayInitializer) {
              record((MethodDeclaration) annotation.getParent(), (ArrayInitializer) paramAttr);
            } else if (paramAttr instanceof StringLiteral) {
              record((MethodDeclaration) annotation.getParent(), (StringLiteral) paramAttr);
            }
          }
        }
      }
  
      return false;
    }
  
    /**
     * Only Parameters annotation can be used to describe the parameters.
     * 
     * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.SingleMemberAnnotation)
     */
    @Override
    public boolean visit(SingleMemberAnnotation annotation) {
      if(isParameterAnnotation(annotation.getTypeName().getFullyQualifiedName())) {
        Expression paramValues= annotation.getValue();
        if (paramValues instanceof ArrayInitializer) {
          record((MethodDeclaration) annotation.getParent(), (ArrayInitializer) paramValues);
        } else if (paramValues instanceof StringLiteral) {
          record((MethodDeclaration) annotation.getParent(), (StringLiteral) paramValues);
        }
      }
  
      return false;
    }
  
    public boolean hasParameters() {
      return !m_parameters.isEmpty();
    }
  
    private void record(MethodDeclaration method, List<String> paramNames) {
      m_parameters.put(method,  paramNames);
    }
  
    protected void record(MethodDeclaration method, StringLiteral value){
      if (! Strings.isNullOrEmpty(value.getLiteralValue())) {
        List<String> paramNames = new ArrayList<String>();
        paramNames.add(value.getLiteralValue());
        record(method, paramNames);
      }
    }
  
    /**
     * 
     * @param method the known TestNG annotation with required parameters
     * @param values array initializer containing StringLiterals for the parameter names
     */
    protected void record(MethodDeclaration method, ArrayInitializer values) {
      List<?> literals = values.expressions();
      List<String> paramNames = new ArrayList<String>(literals.size());
      for(int i= 0; i < literals.size(); i++) {
        StringLiteral str = (StringLiteral) literals.get(i);
        paramNames.add(str.getLiteralValue());
      }
  
      record(method, paramNames);
    }
  
    protected boolean isParameterAnnotation(String annotationType) {
      return PARAMETER_ANNOTATION.equals(annotationType) || PARAMETER_ANNOTATION_FQN.equals(annotationType);
    }
  
    protected boolean isKnownAnnotation(String annotationType) {
      return CONFIGURATION_ANNOTATION.equals(annotationType)
             || CONFIGURATION_ANNOTATION_FQN.equals(annotationType)
             || TEST_ANNOTATION.equals(annotationType)
             || TEST_ANNOTATION_FQN.equals(annotationType);
    }
  
    /**
     * @return the non-null parameter name/value map
     */
    public Map<String, String> getParametersMap() {
      Map<String, String> parameterMap = new HashMap<>();
      for (Entry<MethodDeclaration, List<String>> paramEntry : m_parameters.entrySet()) {
        // get all '@Optional' value, as the default value for the parameters
        List<String> optionals = new ArrayList<>();
        for (Object p : paramEntry.getKey().parameters()) {
          SingleVariableDeclaration paramDecl = (SingleVariableDeclaration) p;
          for (Object pm : paramDecl.modifiers()) {
            if (pm instanceof SingleMemberAnnotation) {
              SingleMemberAnnotation paramAnn = (SingleMemberAnnotation) pm;
              if ("Optional".equals(paramAnn.getTypeName().toString())) {
                optionals.add(paramAnn.getValue().toString());
              }
            }
          };
        };
  
        List<String> paramNames = paramEntry.getValue();
        for(int i = 0; i < paramNames.size(); i++) {
          String val = DEFAULT_PARAM_VALUE;
          // the user is responsible for making sure the order of the @Optional parameters follow the order in @Parameters
          if (i < optionals.size() && optionals.get(i) != null) {
            val = optionals.get(i);
          }
  
          String paramName = paramNames.get(i);
  
          if (parameterMap.containsKey(paramName) && !DEFAULT_PARAM_VALUE.equals(parameterMap.get(paramName))) {
            continue;
          }
  
          parameterMap.put(paramName, val);
        }
      }
  
      return parameterMap;
    }
  }
  