/**
  * Method called when a field of the class is visited.
  *
  * @param access Access type
  * @param name Name of the field
  * @param desc Descriptor of the field
  * @param signature Signature of the field
  * @param value Value of the field
  * @return FieldVisitor
  */
 public FieldVisitor visitField(
     int access, String name, String desc, String signature, Object value) {
   if (name.equals(enhancer.getNamer().getSerialVersionUidFieldName())) {
     // Has serialVersionUID field for use in serialisation
     hasSerialVersionUID = true;
   } else if (name.equals(enhancer.getNamer().getDetachedStateFieldName())) {
     // Has xxxDetachedState field
     hasDetachedState = true;
   }
   return super.visitField(access, name, desc, signature, value);
 }
  /**
   * Method called to visit the header of the class.
   *
   * @param version Version of this class
   * @param access Access for the class
   * @param name name of the class
   * @param signature Signature of the class
   * @param superName Superclass name (if any)
   * @param interfaces Interface(s) implemented
   */
  public void visit(
      int version,
      int access,
      String name,
      String signature,
      String superName,
      String[] interfaces) {
    if (enhancer.getClassMetaData().getPersistenceModifier()
        == ClassPersistenceModifier.PERSISTENCE_CAPABLE) {
      // Check if the class already implements required interfaces
      boolean alreadyPersistable = false;
      boolean alreadyDetachable = false;
      boolean needsPersistable = false;
      boolean needsDetachable = false;
      int numInterfaces = 0;
      if (interfaces != null && interfaces.length > 0) {
        numInterfaces = interfaces.length;
        for (int i = 0; i < interfaces.length; i++) {
          if (interfaces[i].equals(enhancer.getNamer().getDetachableAsmClassName())) {
            alreadyDetachable = true;
          }
          if (interfaces[i].equals(enhancer.getNamer().getPersistableAsmClassName())) {
            alreadyPersistable = true;
          }
        }
      }
      if (!alreadyDetachable && enhancer.getClassMetaData().isDetachable()) {
        numInterfaces++;
        needsDetachable = true;
      }
      if (!alreadyPersistable) {
        numInterfaces++;
        needsPersistable = true;
      }

      String[] intfs = interfaces;
      if (needsDetachable || needsPersistable) {
        // Allocate updated array of interfaces
        intfs = new String[numInterfaces];
        int position = 0;
        if (interfaces != null && interfaces.length > 0) {
          for (int i = 0; i < interfaces.length; i++) {
            intfs[position++] = interfaces[i];
          }
        }

        if (needsDetachable) {
          intfs[position++] = enhancer.getNamer().getDetachableAsmClassName();
          if (DataNucleusEnhancer.LOGGER.isDebugEnabled()) {
            DataNucleusEnhancer.LOGGER.debug(
                Localiser.msg("005022", enhancer.getNamer().getDetachableClass().getName()));
          }
        }
        if (needsPersistable) {
          intfs[position++] = enhancer.getNamer().getPersistableAsmClassName();
          if (DataNucleusEnhancer.LOGGER.isDebugEnabled()) {
            DataNucleusEnhancer.LOGGER.debug(
                Localiser.msg("005022", enhancer.getNamer().getPersistableClass().getName()));
          }
        }
      }
      cv.visit(version, access, name, signature, superName, intfs);
    } else {
      cv.visit(version, access, name, signature, superName, interfaces);
    }
  }
  /** Method called at the end of the class. */
  public void visitEnd() {
    AbstractClassMetaData cmd = enhancer.getClassMetaData();
    if (cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_CAPABLE) {
      // Add any new fields
      List fields = enhancer.getFieldsList();
      Iterator fieldsIter = fields.iterator();
      while (fieldsIter.hasNext()) {
        ClassField field = (ClassField) fieldsIter.next();
        if (field.getName().equals(enhancer.getNamer().getDetachedStateFieldName())
            && hasDetachedState) {
          // No need to add this field since exists
          continue;
        }

        if (DataNucleusEnhancer.LOGGER.isDebugEnabled()) {
          DataNucleusEnhancer.LOGGER.debug(
              Localiser.msg("005021", ((Class) field.getType()).getName() + " " + field.getName()));
        }
        cv.visitField(
            field.getAccess(),
            field.getName(),
            Type.getDescriptor((Class) field.getType()),
            null,
            null);
      }

      if (!hasStaticInitialisation) {
        // Add a static initialisation block for the class since nothing added yet
        InitClass method = InitClass.getInstance(enhancer);
        method.initialise(cv);
        method.execute();
        method.close();
      }

      if (!hasDefaultConstructor
          && enhancer.hasOption(ClassEnhancer.OPTION_GENERATE_DEFAULT_CONSTRUCTOR)) {
        // Add a default constructor
        DefaultConstructor ctr = DefaultConstructor.getInstance(enhancer);
        ctr.initialise(cv);
        ctr.execute();
        ctr.close();
      }

      // Add any new methods
      List methods = enhancer.getMethodsList();
      Iterator<ClassMethod> methodsIter = methods.iterator();
      while (methodsIter.hasNext()) {
        ClassMethod method = methodsIter.next();
        method.initialise(cv);
        method.execute();
        method.close();
      }

      if (Serializable.class.isAssignableFrom(enhancer.getClassBeingEnhanced())) {
        // Class is Serializable
        if (!hasSerialVersionUID) {
          // Needs "serialVersionUID" field
          Long uid = null;
          try {
            uid =
                (Long)
                    AccessController.doPrivileged(
                        new PrivilegedAction() {
                          public Object run() {
                            return Long.valueOf(
                                ObjectStreamClass.lookup(enhancer.getClassBeingEnhanced())
                                    .getSerialVersionUID());
                          }
                        });
          } catch (Throwable e) {
            DataNucleusEnhancer.LOGGER.warn(StringUtils.getStringFromStackTrace(e));
          }
          ClassField cf =
              new ClassField(
                  enhancer,
                  enhancer.getNamer().getSerialVersionUidFieldName(),
                  Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL,
                  long.class,
                  uid);
          if (DataNucleusEnhancer.LOGGER.isDebugEnabled()) {
            DataNucleusEnhancer.LOGGER.debug(
                Localiser.msg("005021", ((Class) cf.getType()).getName() + " " + cf.getName()));
          }
          cv.visitField(
              cf.getAccess(),
              cf.getName(),
              Type.getDescriptor((Class) cf.getType()),
              null,
              cf.getInitialValue());
        }

        // The dnPreSerialize method need be called only once for a persistent instance. The
        // writeObject method in the least-derived
        // pc class that implements Serializable in the inheritance hierarchy needs to be modified
        // or generated to call it.
        if (cmd.getSuperAbstractClassMetaData() == null && !hasWriteObject) {
          // User hasn't provided their own writeObject, so provide the default but with a call to
          // dnPreSerialize first
          ClassMethod method = WriteObject.getInstance(enhancer);
          method.initialise(cv);
          method.execute();
          method.close();
        }
      }

      // Add dnGetXXX, dnSetXXX for each of the (managed) fields/properties
      AbstractMemberMetaData[] fmds = cmd.getManagedMembers();
      for (int i = 0; i < fmds.length; i++) {
        if (fmds[i].getPersistenceModifier() == FieldPersistenceModifier.NONE) {
          // Field/Property is not persistent so ignore
          continue;
        }

        byte persistenceFlags = fmds[i].getPersistenceFlags();
        ClassMethod getMethod = null;
        ClassMethod setMethod = null;
        if (fmds[i] instanceof PropertyMetaData) {
          // dnGetXXX, dnSetXXX for property are generated when processing existing getXXX, setXXX
          // methods
        } else {
          // Generate dnGetXXX, dnSetXXX for field
          if ((persistenceFlags & Persistable.MEDIATE_READ) == Persistable.MEDIATE_READ) {
            getMethod = new GetViaMediate(enhancer, fmds[i]);
          } else if ((persistenceFlags & Persistable.CHECK_READ) == Persistable.CHECK_READ) {
            getMethod = new GetViaCheck(enhancer, fmds[i]);
          } else {
            getMethod = new GetNormal(enhancer, fmds[i]);
          }

          if ((persistenceFlags & Persistable.MEDIATE_WRITE) == Persistable.MEDIATE_WRITE) {
            setMethod = new SetViaMediate(enhancer, fmds[i]);
          } else if ((persistenceFlags & Persistable.CHECK_WRITE) == Persistable.CHECK_WRITE) {
            setMethod = new SetViaCheck(enhancer, fmds[i]);
          } else {
            setMethod = new SetNormal(enhancer, fmds[i]);
          }
        }

        if (getMethod != null) {
          getMethod.initialise(cv);
          getMethod.execute();
          getMethod.close();
        }
        if (setMethod != null) {
          setMethod.initialise(cv);
          setMethod.execute();
          setMethod.close();
        }
      }
    }
    cv.visitEnd();
  }