@Nullable
  public static PsiElement getArrayKeyValueInsideSignaturePsi(
      PsiElement psiElementInsideClass, String callTo[], String methodName, String keyName) {
    PhpClass phpClass = PsiTreeUtil.getParentOfType(psiElementInsideClass, PhpClass.class);
    if (phpClass == null) {
      return null;
    }

    String className = phpClass.getPresentableFQN();
    if (className == null) {
      return null;
    }

    for (String s : callTo) {
      // @TODO: replace signature
      PsiElement arrayKeyValueInsideSignature =
          PhpElementsUtil.getArrayKeyValueInsideSignaturePsi(
              psiElementInsideClass.getProject(),
              "#M#C\\" + className + "." + s,
              methodName,
              keyName);
      if (arrayKeyValueInsideSignature != null) {
        return arrayKeyValueInsideSignature;
      }
    }

    return null;
  }
  private void parseTypes2(
      Map<String, Collection<String>> map, Iterable<ArrayHashElement> elements, String parent) {
    Collection<String> types = map.get(parent);
    if (types == null) {
      types = new ArrayList<String>();
      map.put(parent, types);
    }

    for (ArrayHashElement element : elements) {
      PhpPsiElement key = element.getKey();
      if (key instanceof StringLiteralExpression) {
        // key
        String keyName = ((StringLiteralExpression) key).getContents();
        types.add(keyName);

        String fullKeyName = parent.length() > 0 ? (parent + "." + keyName) : keyName;

        // value
        PhpPsiElement val = element.getValue();
        if (val instanceof ArrayCreationExpression) { // recursive
          Iterable<ArrayHashElement> subElements =
              ((ArrayCreationExpression) val).getHashElements();
          parseTypes2(map, subElements, fullKeyName);

        } else if (val instanceof FieldReference) { // reference to a field, where it's defined
          String classFqn = ((ClassReference) ((FieldReference) val).getClassReference()).getFQN();
          for (PhpClass phpClass :
              PhpIndex.getInstance(element.getProject()).getClassesByFQN(classFqn)) {
            Field field = phpClass.findFieldByName(((FieldReference) val).getNameCS(), false);
            if (field.getDefaultValue() instanceof ArrayCreationExpression) {
              Iterable<ArrayHashElement> subElements =
                  ((ArrayCreationExpression) field.getDefaultValue()).getHashElements();
              parseTypes2(map, subElements, fullKeyName);
            }
          }

        } else { // get value type
          parseValueType(val);

          // try annotation
          PsiElement el2 = element;
          while (el2 != null
              && (el2 instanceof LeafPsiElement
                      && ((LeafPsiElement) el2).getElementType() == PhpTokenTypes.opCOMMA
                  || el2 instanceof PsiWhiteSpace)) {
            el2 = el2.getNextSibling();
          }
          if (el2 instanceof PsiComment) {
            System.out.println("Comment for " + fullKeyName + ": " + el2.getText());
          }
        }
      }
    }
  }
  /**
   * Try to visit possible class name for PsiElements with text like "Foo\|Bar", "Foo|\Bar",
   * "\Foo|\Bar" Cursor must have position in PsiElement
   *
   * @param psiElement the element context, cursor should be in it
   * @param cursorOffset current cursor editor eg from completion context
   * @param visitor callback on matching class
   */
  public static void visitNamespaceClassForCompletion(
      PsiElement psiElement, int cursorOffset, ClassForCompletionVisitor visitor) {

    int cursorOffsetClean = cursorOffset - psiElement.getTextOffset();
    if (cursorOffsetClean < 1) {
      return;
    }

    String content = psiElement.getText();
    int length = content.length();
    if (!(length >= cursorOffsetClean)) {
      return;
    }

    String beforeCursor = content.substring(0, cursorOffsetClean);
    boolean isValid;

    // espend\|Container, espend\Cont|ainer <- fallback to last full namespace
    // espend|\Container <- only on known namespace "espend"
    String namespace = beforeCursor;

    // if no backslash or its equal in first position, fallback on namespace completion
    int lastSlash = beforeCursor.lastIndexOf("\\");
    if (lastSlash <= 0) {
      isValid = PhpIndexUtil.hasNamespace(psiElement.getProject(), beforeCursor);
    } else {
      isValid = true;
      namespace = beforeCursor.substring(0, lastSlash);
    }

    if (!isValid) {
      return;
    }

    // format namespaces and add prefix for fluent completion
    String prefix = "";
    if (namespace.startsWith("\\")) {
      prefix = "\\";
    } else {
      namespace = "\\" + namespace;
    }

    // search classes in current namespace and child namespaces
    for (PhpClass phpClass :
        PhpIndexUtil.getPhpClassInsideNamespace(psiElement.getProject(), namespace)) {
      String presentableFQN = phpClass.getPresentableFQN();
      if (presentableFQN != null
          && fr.adrienbrault.idea.symfony2plugin.util.StringUtils.startWithEqualClassname(
              presentableFQN, beforeCursor)) {
        visitor.visit(phpClass, presentableFQN, prefix);
      }
    }
  }
  @Nullable
  public static Method getClassMethod(
      @NotNull Project project, @NotNull String phpClassName, @NotNull String methodName) {

    // we need here an each; because eg Command is non unique because phar file
    for (PhpClass phpClass : PhpIndex.getInstance(project).getClassesByFQN(phpClassName)) {
      Method method = phpClass.findMethodByName(methodName);
      if (method != null) {
        return method;
      }
    }

    return null;
  }
  /**
   * Find a string return value of a method context "function() { return 'foo'}" First match wins
   */
  @Nullable
  public static String getMethodReturnAsString(
      @NotNull PhpClass phpClass, @NotNull String methodName) {

    Method method = phpClass.findMethodByName(methodName);
    if (method == null) {
      return null;
    }

    final Set<String> values = new HashSet<String>();
    method.acceptChildren(
        new PsiRecursiveElementWalkingVisitor() {
          @Override
          public void visitElement(PsiElement element) {

            if (PhpElementsUtil.getMethodReturnPattern().accepts(element)) {
              String value = PhpElementsUtil.getStringValue(element);
              if (value != null && StringUtils.isNotBlank(value)) {
                values.add(value);
              }
            }

            super.visitElement(element);
          }
        });

    if (values.size() == 0) {
      return null;
    }

    // we support only first item
    return values.iterator().next();
  }
  public static ArrayList<Method> getClassPublicMethod(PhpClass phpClass) {
    ArrayList<Method> methods = new ArrayList<Method>();

    for (Method method : phpClass.getMethods()) {
      if (method.getAccess().isPublic() && !method.getName().startsWith("__")) {
        methods.add(method);
      }
    }

    return methods;
  }
  private static ArrayList<Method> getImplementedMethods(
      @Nullable PhpClass phpClass, @NotNull Method method, ArrayList<Method> implementedMethods) {
    if (phpClass == null) {
      return implementedMethods;
    }

    Method[] methods = phpClass.getOwnMethods();
    for (Method ownMethod : methods) {
      if (PhpLangUtil.equalsMethodNames(ownMethod.getName(), method.getName())) {
        implementedMethods.add(ownMethod);
      }
    }

    for (PhpClass interfaceClass : phpClass.getImplementedInterfaces()) {
      getImplementedMethods(interfaceClass, method, implementedMethods);
    }

    getImplementedMethods(phpClass.getSuperClass(), method, implementedMethods);

    return implementedMethods;
  }
  public static boolean isTestClass(@NotNull PhpClass phpClass) {

    if (PhpUnitUtil.isTestClass(phpClass)) {
      return true;
    }

    String fqn = phpClass.getPresentableFQN();
    if (fqn == null) {
      return false;
    }

    return fqn.contains("\\Test\\") || fqn.contains("\\Tests\\");
  }
  public static boolean isEqualClassName(
      @Nullable PhpClass phpClass, @Nullable String compareClassName) {

    if (phpClass == null || compareClassName == null) {
      return false;
    }

    String phpClassName = phpClass.getPresentableFQN();
    if (phpClassName == null) {
      return false;
    }

    if (phpClassName.startsWith("\\")) {
      phpClassName = phpClassName.substring(1);
    }

    if (compareClassName.startsWith("\\")) {
      compareClassName = compareClassName.substring(1);
    }

    return phpClassName.equals(compareClassName);
  }
 public static boolean isEqualClassName(
     @NotNull PhpClass phpClass, @NotNull PhpClass compareClassName) {
   return isEqualClassName(phpClass, compareClassName.getPresentableFQN());
 }
 /** There is no need for this proxy method. We are api safe now */
 @Deprecated
 @Nullable
 public static Method getClassMethod(PhpClass phpClass, String methodName) {
   return phpClass.findMethodByName(methodName);
 }