/**
   * overrides the visitor to find abstract methods that override concrete ones
   *
   * @param obj the context object of the currently parsed method
   */
  @Override
  public void visitMethod(Method obj) {
    if (!obj.isAbstract()) return;

    String methodName = obj.getName();
    String methodSig = obj.getSignature();
    outer:
    for (JavaClass cls : superClasses) {
      Method[] methods = cls.getMethods();
      for (Method m : methods) {
        if (m.isPrivate() || m.isAbstract()) continue;
        if (methodName.equals(m.getName()) && methodSig.equals(m.getSignature())) {
          BugInstance bug =
              new BugInstance(this, BugType.AOM_ABSTRACT_OVERRIDDEN_METHOD.name(), NORMAL_PRIORITY)
                  .addClass(this)
                  .addMethod(this);

          Code code = obj.getCode();
          if (code != null) bug.addSourceLineRange(clsContext, this, 0, code.getLength() - 1);
          bugReporter.reportBug(bug);

          break outer;
        }
      }
    }
  }
    /** new style encoding is targetClassName ('!' | '?') methodName '.' methodSign */
    boolean decode(String encodedBinding, ClassGen cg) {
      int sepPos = encodedBinding.indexOf('!');
      if (sepPos != -1) { // static method:
        invokeKind = INVOKESTATIC;
      } else {
        sepPos = encodedBinding.indexOf('?');
        if (sepPos != -1) {
          invokeKind = INVOKEVIRTUAL;
        } else {
          return false; // old style
        }
      }
      targetClass = encodedBinding.substring(0, sepPos);
      int sigPos = encodedBinding.indexOf('(', sepPos);
      methodName = encodedBinding.substring(sepPos + 1, sigPos);
      methodSign = encodedBinding.substring(sigPos);

      returnType = Type.getReturnType(methodSign);
      args = Type.getArgumentTypes(methodSign);

      accessorName = "_OT$decaps$" + methodName;
      existsAlready = cg.containsMethod(accessorName, methodSign) != null;
      if (invokeKind == INVOKEVIRTUAL) {
        Method existing = cg.containsMethod(methodName, methodSign);
        if (existing != null && existing.isPrivate())
          invokeKind = INVOKESPECIAL; // accessing private
      }

      return true;
    }
  public void addAllDefinitions(JavaClass obj) {
    String className2 = obj.getClassName();

    defined.add(className2);
    for (Method m : obj.getMethods())
      if (!m.isPrivate()) {
        String name = getMemberName(obj, className2, m.getNameIndex(), m.getSignatureIndex());
        defined.add(name);
      }
    for (Field f : obj.getFields())
      if (!f.isPrivate()) {
        String name = getMemberName(obj, className2, f.getNameIndex(), f.getSignatureIndex());
        defined.add(name);
      }
  }
  @Override
  public void visit(Method obj) {

    int accessFlags = obj.getAccessFlags();
    boolean isSynchronized = (accessFlags & ACC_SYNCHRONIZED) != 0;
    if (getMethodName().equals("<init>")
        && getMethodSig().equals("()V")
        && (accessFlags & ACC_PUBLIC) != 0) hasPublicVoidConstructor = true;
    if (!getMethodName().equals("<init>") && isSynthetic(obj)) foundSynthetic = true;
    // System.out.println(methodName + isSynchronized);

    if (getMethodName().equals("readExternal")
        && getMethodSig().equals("(Ljava/io/ObjectInput;)V")) {
      sawReadExternal = true;
      if (DEBUG && !obj.isPrivate())
        System.out.println("Non-private readExternal method in: " + getDottedClassName());
    } else if (getMethodName().equals("writeExternal")
        && getMethodSig().equals("(Ljava/io/Objectoutput;)V")) {
      sawWriteExternal = true;
      if (DEBUG && !obj.isPrivate())
        System.out.println("Non-private writeExternal method in: " + getDottedClassName());
    } else if (getMethodName().equals("readResolve")
        && getMethodSig().startsWith("()")
        && isSerializable) {
      sawReadResolve = true;
      if (!getMethodSig().equals("()Ljava/lang/Object;"))
        bugReporter.reportBug(
            new BugInstance(this, "SE_READ_RESOLVE_MUST_RETURN_OBJECT", HIGH_PRIORITY)
                .addClassAndMethod(this));
      else if (obj.isStatic())
        bugReporter.reportBug(
            new BugInstance(this, "SE_READ_RESOLVE_IS_STATIC", HIGH_PRIORITY)
                .addClassAndMethod(this));
      else if (obj.isPrivate())
        try {
          Set<ClassDescriptor> subtypes =
              AnalysisContext.currentAnalysisContext()
                  .getSubtypes2()
                  .getSubtypes(getClassDescriptor());
          if (subtypes.size() > 1) {
            BugInstance bug =
                new BugInstance(this, "SE_PRIVATE_READ_RESOLVE_NOT_INHERITED", NORMAL_PRIORITY)
                    .addClassAndMethod(this);
            boolean nasty = false;
            for (ClassDescriptor subclass : subtypes)
              if (!subclass.equals(getClassDescriptor())) {

                XClass xSub = AnalysisContext.currentXFactory().getXClass(subclass);
                if (xSub != null
                    && xSub.findMethod("readResolve", "()Ljava/lang/Object;", false) == null
                    && xSub.findMethod("writeReplace", "()Ljava/lang/Object;", false) == null) {
                  bug.addClass(subclass).describe(ClassAnnotation.SUBCLASS_ROLE);
                  nasty = true;
                }
              }
            if (nasty) bug.setPriority(HIGH_PRIORITY);
            else if (!getThisClass().isPublic()) bug.setPriority(LOW_PRIORITY);
            bugReporter.reportBug(bug);
          }

        } catch (ClassNotFoundException e) {
          bugReporter.reportMissingClass(e);
        }

    } else if (getMethodName().equals("readObject")
        && getMethodSig().equals("(Ljava/io/ObjectInputStream;)V")
        && isSerializable) {
      sawReadObject = true;
      if (!obj.isPrivate())
        bugReporter.reportBug(
            new BugInstance(this, "SE_METHOD_MUST_BE_PRIVATE", HIGH_PRIORITY)
                .addClassAndMethod(this));

    } else if (getMethodName().equals("readObjectNoData")
        && getMethodSig().equals("()V")
        && isSerializable) {

      if (!obj.isPrivate())
        bugReporter.reportBug(
            new BugInstance(this, "SE_METHOD_MUST_BE_PRIVATE", HIGH_PRIORITY)
                .addClassAndMethod(this));

    } else if (getMethodName().equals("writeObject")
        && getMethodSig().equals("(Ljava/io/ObjectOutputStream;)V")
        && isSerializable) {
      sawWriteObject = true;
      if (!obj.isPrivate())
        bugReporter.reportBug(
            new BugInstance(this, "SE_METHOD_MUST_BE_PRIVATE", HIGH_PRIORITY)
                .addClassAndMethod(this));
    }

    if (isSynchronized) {
      if (getMethodName().equals("readObject")
          && getMethodSig().equals("(Ljava/io/ObjectInputStream;)V")
          && isSerializable)
        bugReporter.reportBug(
            new BugInstance(this, "RS_READOBJECT_SYNC", NORMAL_PRIORITY).addClass(this));
      else if (getMethodName().equals("writeObject")
          && getMethodSig().equals("(Ljava/io/ObjectOutputStream;)V")
          && isSerializable) writeObjectIsSynchronized = true;
      else foundSynchronizedMethods = true;
    }
    super.visit(obj);
  }