@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);
  }
  private TypeQualifierValue(ClassDescriptor typeQualifier, @CheckForNull Object value) {
    this.typeQualifier = typeQualifier;
    this.value = value;
    boolean isStrict = false; // will be set to true if this is a strict
    // type qualifier value
    boolean isExclusive = false; // will be set to true if this is an
    // exclusive type qualifier value
    boolean isExhaustive = false; // will be set to true if this is an
    // exhaustive type qualifier value

    TypeQualifierValidator<A> validator = null;
    Class<A> qualifierClass = null;
    A proxy = null;
    try {
      XClass xclass = Global.getAnalysisCache().getClassAnalysis(XClass.class, typeQualifier);

      // Annotation elements appear as abstract methods in the annotation
      // class (interface).
      // So, if the type qualifier annotation has specified a default When
      // value,
      // it will appear as an abstract method called "when".
      XMethod whenMethod = xclass.findMethod("when", "()Ljavax/annotation/meta/When;", false);
      if (whenMethod == null) {
        isStrict = true;
      }
      for (XMethod xmethod : xclass.getXMethods()) {
        if (xmethod.getName().equals("value") && xmethod.getSignature().startsWith("()")) {
          isExhaustive = xmethod.getAnnotation(EXHAUSTIVE_ANNOTATION) != null;
          if (isExhaustive) {
            // exhaustive qualifiers are automatically exclusive
            isExclusive = true;
          } else {
            // see if there is an explicit @Exclusive annotation
            isExclusive = xmethod.getAnnotation(EXCLUSIVE_ANNOTATION) != null;
          }

          break;
        }
      }
    } catch (MissingClassException e) {
      AnalysisContext.currentAnalysisContext()
          .getLookupFailureCallback()
          .reportMissingClass(e.getClassNotFoundException());
    } catch (CheckedAnalysisException e) {
      AnalysisContext.logError(
          "Error looking up annotation class " + typeQualifier.toDottedClassName(), e);
    }
    this.isStrict = isStrict;
    this.isExclusive = isExclusive;
    this.isExhaustive = isExhaustive;
    ClassDescriptor checkerName =
        DescriptorFactory.createClassDescriptor(typeQualifier.getClassName() + "$Checker");
    try {
      Global.getAnalysisCache().getClassAnalysis(ClassData.class, checkerName);
      // found it.
      //            System.out.println(checkerName);
      SecurityManager m = System.getSecurityManager();
      if (m == null) System.setSecurityManager(new ValidationSecurityManager());
      Class<?> c = validatorLoader.loadClass(checkerName.getDottedClassName());
      if (TypeQualifierValidator.class.isAssignableFrom(c)) {
        Class<? extends TypeQualifierValidator> checkerClass =
            c.asSubclass(TypeQualifierValidator.class);
        validator = getValidator(checkerClass);
        qualifierClass = getQualifierClass(typeQualifier);

        InvocationHandler handler =
            new InvocationHandler() {

              public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
                if (arg1.getName() == "value") return TypeQualifierValue.this.value;
                throw new UnsupportedOperationException("Can't handle " + arg1);
              }
            };

        proxy =
            qualifierClass.cast(
                Proxy.newProxyInstance(validatorLoader, new Class[] {qualifierClass}, handler));
      }
    } catch (ClassNotFoundException e) {
      assert true; // ignore
    } catch (CheckedAnalysisException e) {
      assert true; // ignore
    } catch (Exception e) {
      AnalysisContext.logError("Unable to construct type qualifier checker " + checkerName, e);
    } catch (Throwable e) {
      AnalysisContext.logError(
          "Unable to construct type qualifier checker "
              + checkerName
              + " due to "
              + e.getClass().getSimpleName()
              + ":"
              + e.getMessage());
    }
    this.validator = validator;
    this.typeQualifierClass = qualifierClass;
    this.proxy = proxy;
  }