@Override
  public void visitAfter(JavaClass obj) {
    if (isEnum) return;
    if (DEBUG) {
      System.out.println(getDottedClassName());
      System.out.println("  hasPublicVoidConstructor: " + hasPublicVoidConstructor);
      System.out.println("  superClassHasVoidConstructor: " + superClassHasVoidConstructor);
      System.out.println("  isExternalizable: " + isExternalizable);
      System.out.println("  isSerializable: " + isSerializable);
      System.out.println("  isAbstract: " + isAbstract);
      System.out.println("  superClassImplementsSerializable: " + superClassImplementsSerializable);
      System.out.println("  isGUIClass: " + isGUIClass);
      System.out.println("  isEjbImplClass: " + isEjbImplClass);
    }
    if (isSerializable
        && !sawReadObject
        && !sawReadResolve
        && seenTransientField
        && !superClassHasReadObject) {
      for (Map.Entry<XField, Integer> e : transientFieldsUpdates.entrySet()) {

        XField fieldX = e.getKey();
        int priority = NORMAL_PRIORITY;
        if (transientFieldsSetInConstructor.contains(e.getKey())) priority--;

        if (isGUIClass) priority++;
        if (isEjbImplClass) priority++;
        if (e.getValue() < 3) priority++;
        if (transientFieldsSetToDefaultValueInConstructor.contains(e.getKey())) priority++;
        if (obj.isAbstract()) {
          priority++;
          if (priority < Priorities.LOW_PRIORITY) priority = Priorities.LOW_PRIORITY;
        }

        try {
          double isSerializable = DeepSubtypeAnalysis.isDeepSerializable(fieldX.getSignature());
          if (isSerializable < 0.6) priority++;
        } catch (ClassNotFoundException e1) {
          // ignore it
        }

        bugReporter.reportBug(
            new BugInstance(this, "SE_TRANSIENT_FIELD_NOT_RESTORED", priority)
                .addClass(getThisClass())
                .addField(fieldX));
      }
    }
    if (isSerializable
        && !isExternalizable
        && !superClassHasVoidConstructor
        && !superClassImplementsSerializable) {
      int priority =
          implementsSerializableDirectly || seenTransientField
              ? HIGH_PRIORITY
              : (sawSerialVersionUID ? NORMAL_PRIORITY : LOW_PRIORITY);
      if (isGUIClass) priority++;
      if (isEjbImplClass) priority++;
      bugReporter.reportBug(
          new BugInstance(this, "SE_NO_SUITABLE_CONSTRUCTOR", priority)
              .addClass(getThisClass().getClassName()));
    }
    // Downgrade class-level warnings if it's a GUI or EJB-implementation class.
    int priority = (isGUIClass || isEjbImplClass) ? LOW_PRIORITY : NORMAL_PRIORITY;
    if (obj.getClassName().endsWith("_Stub")) priority++;

    if (isExternalizable && !hasPublicVoidConstructor && !isAbstract)
      bugReporter.reportBug(
          new BugInstance(
                  this,
                  "SE_NO_SUITABLE_CONSTRUCTOR_FOR_EXTERNALIZATION",
                  directlyImplementsExternalizable ? HIGH_PRIORITY : NORMAL_PRIORITY)
              .addClass(getThisClass().getClassName()));
    if (!foundSynthetic) priority++;
    if (seenTransientField) priority--;
    if (!isAnonymousInnerClass
        && !isExternalizable
        && !isGUIClass
        && !obj.isAbstract()
        && isSerializable
        && !isAbstract
        && !sawSerialVersionUID
        && !isEjbImplClass)
      bugReporter.reportBug(
          new BugInstance(this, "SE_NO_SERIALVERSIONID", priority).addClass(this));

    if (writeObjectIsSynchronized && !foundSynchronizedMethods)
      bugReporter.reportBug(
          new BugInstance(this, "WS_WRITEOBJECT_SYNC", LOW_PRIORITY).addClass(this));
  }
  public static double isDeepSerializable(JavaClass x) throws ClassNotFoundException {
    if (storedException != null) throw storedException;

    if (x.getClassName().equals("java.lang.Object")) return 0.4;

    if (DEBUG) {
      System.out.println("checking " + x.getClassName());
    }

    double result = Analyze.deepInstanceOf(x, serializable);
    if (result >= 0.9) {
      if (DEBUG) {
        System.out.println("Direct high serializable result: " + result);
      }
      return result;
    }

    if (x.isFinal()) return result;

    double collectionResult = Analyze.deepInstanceOf(x, collection);
    double mapResult = Analyze.deepInstanceOf(x, map);

    if (x.isInterface() || x.isAbstract()) {
      result = Math.max(result, Math.max(mapResult, collectionResult) * 0.95);
      if (result >= 0.9) {
        return result;
      }
    }
    ClassDescriptor classDescriptor = DescriptorFactory.createClassDescriptor(x);

    Subtypes2 subtypes2 = AnalysisContext.currentAnalysisContext().getSubtypes2();

    Set<ClassDescriptor> directSubtypes = subtypes2.getDirectSubtypes(classDescriptor);
    directSubtypes.remove(classDescriptor);

    double confidence = 0.6;
    if (x.isAbstract() || x.isInterface()) {
      confidence = 0.8;
      result = Math.max(result, 0.4);
    } else if (directSubtypes.isEmpty()) confidence = 0.2;

    double confidence2 = (1 + confidence) / 2;
    result = Math.max(result, confidence2 * collectionResult);
    if (result >= 0.9) {
      if (DEBUG) {
        System.out.println("High collection result: " + result);
      }
      return result;
    }
    result = Math.max(result, confidence2 * mapResult);
    if (result >= 0.9) {
      if (DEBUG) {
        System.out.println("High map result: " + result);
      }
      return result;
    }
    result = Math.max(result, confidence2 * 0.5 * Analyze.deepInstanceOf(x, comparator));
    if (result >= 0.9) {
      if (DEBUG) {
        System.out.println("High comparator result: " + result);
      }
      return result;
    }

    for (ClassDescriptor subtype : directSubtypes) {
      JavaClass subJavaClass = Repository.lookupClass(subtype.getDottedClassName());
      result = Math.max(result, confidence * Analyze.deepInstanceOf(subJavaClass, serializable));

      // result = Math.max(result, confidence * isDeepSerializable(subJavaClass));
      if (result >= 0.9) {
        return result;
      }
    }

    if (DEBUG) {
      System.out.println("No high results; max: " + result);
    }
    return result;
  }
 private static boolean containsConcreteClasses(Set<JavaClass> s) {
   for (JavaClass c : s) if (!c.isInterface() && !c.isAbstract()) return true;
   return false;
 }
  /**
   * overrides the visitor to report on classes without toStrings that have fields
   *
   * @param classContext the context object of the currently parsed class
   */
  @Override
  public void visitClassContext(ClassContext classContext) {
    JavaClass cls = classContext.getJavaClass();

    if (cls.getPackageName().isEmpty()) {
      bugReporter.reportBug(
          new BugInstance(this, BugType.IMC_IMMATURE_CLASS_NO_PACKAGE.name(), LOW_PRIORITY)
              .addClass(cls));
    }

    if ((!cls.isAbstract())
        && (!cls.isEnum())
        && !cls.getClassName().contains("$")
        && !isTestClass(cls)) {

      try {
        boolean clsHasRuntimeAnnotation = classHasRuntimeVisibleAnnotation(cls);
        HEStatus heStatus = HEStatus.UNKNOWN;

        checkIDEGeneratedParmNames(cls);

        for (Field f : cls.getFields()) {
          if (!f.isStatic() && !f.isSynthetic()) {

            boolean fieldHasRuntimeAnnotation = fieldHasRuntimeVisibleAnnotation(f);
            if (!fieldHasRuntimeAnnotation) {
              /* only report one of these, so as not to flood the report */
              if (!hasMethodInHierarchy(cls, "toString", "()Ljava/lang/String;")) {
                bugReporter.reportBug(
                    new BugInstance(
                            this, BugType.IMC_IMMATURE_CLASS_NO_TOSTRING.name(), LOW_PRIORITY)
                        .addClass(cls));
                return;
              }
              if (heStatus != HEStatus.NOT_NEEDED) {
                String fieldSig = f.getSignature();
                if (fieldSig.startsWith("L")) {
                  if (!fieldSig.startsWith("Ljava")) {
                    JavaClass fieldClass =
                        Repository.lookupClass(fieldSig.substring(1, fieldSig.length() - 1));
                    if (!hasMethodInHierarchy(fieldClass, "equals", "(Ljava/lang/Object)Z")) {
                      heStatus = HEStatus.NOT_NEEDED;
                    }
                  } else if (!fieldSig.startsWith("Ljava/lang/")
                      && !fieldSig.startsWith("Ljava/util/")) {
                    heStatus = HEStatus.NOT_NEEDED;
                  }
                } else if (!fieldSig.startsWith("[")) {
                  heStatus = HEStatus.NEEDED;
                }
              }
            } else {
              heStatus = HEStatus.NOT_NEEDED;
            }
          }
        }

        if (!clsHasRuntimeAnnotation && (heStatus == HEStatus.NEEDED)) {
          if (!hasMethodInHierarchy(cls, "equals", "(Ljava/lang/Object;)Z")) {
            bugReporter.reportBug(
                new BugInstance(this, BugType.IMC_IMMATURE_CLASS_NO_EQUALS.name(), LOW_PRIORITY)
                    .addClass(cls));
          } else if (!hasMethodInHierarchy(cls, "hashCode", "()I")) {
            bugReporter.reportBug(
                new BugInstance(this, BugType.IMC_IMMATURE_CLASS_NO_HASHCODE.name(), LOW_PRIORITY)
                    .addClass(cls));
          }
        }

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