private void fieldToJava(
      ClassWrapper wrapper,
      StructClass cl,
      StructField fd,
      TextBuffer buffer,
      int indent,
      BytecodeMappingTracer tracer) {
    int start = buffer.length();
    boolean isInterface = cl.hasModifier(CodeConstants.ACC_INTERFACE);
    boolean isDeprecated = fd.getAttributes().containsKey("Deprecated");
    boolean isEnum =
        fd.hasModifier(CodeConstants.ACC_ENUM)
            && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM);

    if (isDeprecated) {
      appendDeprecation(buffer, indent);
    }

    if (interceptor != null) {
      String oldName =
          interceptor.getOldName(cl.qualifiedName + " " + fd.getName() + " " + fd.getDescriptor());
      appendRenameComment(buffer, oldName, MType.FIELD, indent);
    }

    if (fd.isSynthetic()) {
      appendComment(buffer, "synthetic field", indent);
    }

    appendAnnotations(buffer, fd, indent);

    buffer.appendIndent(indent);

    if (!isEnum) {
      appendModifiers(buffer, fd.getAccessFlags(), FIELD_ALLOWED, isInterface, FIELD_EXCLUDED);
    }

    VarType fieldType = new VarType(fd.getDescriptor(), false);

    GenericFieldDescriptor descriptor = null;
    if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES)) {
      StructGenericSignatureAttribute attr =
          (StructGenericSignatureAttribute) fd.getAttributes().getWithKey("Signature");
      if (attr != null) {
        descriptor = GenericMain.parseFieldSignature(attr.getSignature());
      }
    }

    if (!isEnum) {
      if (descriptor != null) {
        buffer.append(GenericMain.getGenericCastTypeName(descriptor.type));
      } else {
        buffer.append(ExprProcessor.getCastTypeName(fieldType));
      }
      buffer.append(' ');
    }

    buffer.append(fd.getName());

    tracer.incrementCurrentSourceLine(buffer.countLines(start));

    Exprent initializer;
    if (fd.hasModifier(CodeConstants.ACC_STATIC)) {
      initializer =
          wrapper
              .getStaticFieldInitializers()
              .getWithKey(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor()));
    } else {
      initializer =
          wrapper
              .getDynamicFieldInitializers()
              .getWithKey(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor()));
    }
    if (initializer != null) {
      if (isEnum && initializer.type == Exprent.EXPRENT_NEW) {
        NewExprent nexpr = (NewExprent) initializer;
        nexpr.setEnumConst(true);
        buffer.append(nexpr.toJava(indent, tracer));
      } else {
        buffer.append(" = ");
        // FIXME: special case field initializer. Can map to more than one method (constructor) and
        // bytecode intruction.
        buffer.append(initializer.toJava(indent, tracer));
      }
    } else if (fd.hasModifier(CodeConstants.ACC_FINAL)
        && fd.hasModifier(CodeConstants.ACC_STATIC)) {
      StructConstantValueAttribute attr =
          (StructConstantValueAttribute)
              fd.getAttributes().getWithKey(StructGeneralAttribute.ATTRIBUTE_CONSTANT_VALUE);
      if (attr != null) {
        PrimitiveConstant constant = cl.getPool().getPrimitiveConstant(attr.getIndex());
        buffer.append(" = ");
        buffer.append(new ConstExprent(fieldType, constant.value, null).toJava(indent, tracer));
      }
    }

    if (!isEnum) {
      buffer.append(";").appendLineSeparator();
      tracer.incrementCurrentSourceLine();
    }
  }
  private static String isClass14Invocation(
      Exprent exprent, ClassWrapper wrapper, MethodWrapper meth) {

    if (exprent.type == Exprent.EXPRENT_FUNCTION) {
      FunctionExprent fexpr = (FunctionExprent) exprent;
      if (fexpr.getFuncType() == FunctionExprent.FUNCTION_IIF) {
        if (fexpr.getLstOperands().get(0).type == Exprent.EXPRENT_FUNCTION) {
          FunctionExprent headexpr = (FunctionExprent) fexpr.getLstOperands().get(0);
          if (headexpr.getFuncType() == FunctionExprent.FUNCTION_EQ) {
            if (headexpr.getLstOperands().get(0).type == Exprent.EXPRENT_FIELD
                && headexpr.getLstOperands().get(1).type == Exprent.EXPRENT_CONST
                && ((ConstExprent) headexpr.getLstOperands().get(1))
                    .getConstType()
                    .equals(VarType.VARTYPE_NULL)) {

              FieldExprent field = (FieldExprent) headexpr.getLstOperands().get(0);
              ClassNode fieldnode =
                  DecompilerContext.getClassProcessor()
                      .getMapRootClasses()
                      .get(field.getClassname());

              if (fieldnode != null
                  && fieldnode.classStruct.qualifiedName.equals(
                      wrapper.getClassStruct().qualifiedName)) { // source class
                StructField fd =
                    wrapper
                        .getClassStruct()
                        .getField(
                            field.getName(),
                            field.getDescriptor().descriptorString); // FIXME: can be null! why??

                if (fd != null
                    && fd.hasModifier(CodeConstants.ACC_STATIC)
                    && (fd.isSynthetic()
                        || DecompilerContext.getOption(IFernflowerPreferences.SYNTHETIC_NOT_SET))) {

                  if (fexpr.getLstOperands().get(1).type == Exprent.EXPRENT_ASSIGNMENT
                      && fexpr.getLstOperands().get(2).equals(field)) {
                    AssignmentExprent asexpr = (AssignmentExprent) fexpr.getLstOperands().get(1);

                    if (asexpr.getLeft().equals(field)
                        && asexpr.getRight().type == Exprent.EXPRENT_INVOCATION) {
                      InvocationExprent invexpr = (InvocationExprent) asexpr.getRight();

                      if (invexpr.getClassname().equals(wrapper.getClassStruct().qualifiedName)
                          && invexpr.getName().equals(meth.methodStruct.getName())
                          && invexpr
                              .getStringDescriptor()
                              .equals(meth.methodStruct.getDescriptor())) {

                        if (invexpr.getLstParameters().get(0).type == Exprent.EXPRENT_CONST) {
                          wrapper
                              .getHiddenMembers()
                              .add(
                                  InterpreterUtil.makeUniqueKey(
                                      fd.getName(), fd.getDescriptor())); // hide synthetic field
                          return ((ConstExprent) invexpr.getLstParameters().get(0))
                              .getValue()
                              .toString();
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }

    return null;
  }
  public void classToJava(
      ClassNode node, TextBuffer buffer, int indent, BytecodeMappingTracer tracer) {
    ClassNode outerNode =
        (ClassNode) DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE);
    DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, node);

    int startLine = tracer != null ? tracer.getCurrentSourceLine() : 0;
    BytecodeMappingTracer dummy_tracer = new BytecodeMappingTracer(startLine);

    try {
      // last minute processing
      invokeProcessors(node);

      ClassWrapper wrapper = node.getWrapper();
      StructClass cl = wrapper.getClassStruct();

      DecompilerContext.getLogger().startWriteClass(cl.qualifiedName);

      // write class definition
      int start_class_def = buffer.length();
      writeClassDefinition(node, buffer, indent);

      //      // count lines in class definition the easiest way
      //      startLine = buffer.substring(start_class_def).toString().split(lineSeparator,
      // -1).length - 1;

      boolean hasContent = false;

      // fields
      boolean enumFields = false;

      dummy_tracer.incrementCurrentSourceLine(buffer.countLines(start_class_def));

      for (StructField fd : cl.getFields()) {
        boolean hide =
            fd.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC)
                || wrapper
                    .getHiddenMembers()
                    .contains(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor()));
        if (hide) continue;

        boolean isEnum =
            fd.hasModifier(CodeConstants.ACC_ENUM)
                && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM);
        if (isEnum) {
          if (enumFields) {
            buffer.append(',').appendLineSeparator();
            dummy_tracer.incrementCurrentSourceLine();
          }
          enumFields = true;
        } else if (enumFields) {
          buffer.append(';');
          buffer.appendLineSeparator();
          buffer.appendLineSeparator();
          dummy_tracer.incrementCurrentSourceLine(2);
          enumFields = false;
        }

        fieldToJava(wrapper, cl, fd, buffer, indent + 1, dummy_tracer); // FIXME: insert real tracer

        hasContent = true;
      }

      if (enumFields) {
        buffer.append(';').appendLineSeparator();
        dummy_tracer.incrementCurrentSourceLine();
      }

      // FIXME: fields don't matter at the moment
      startLine += buffer.countLines(start_class_def);

      // methods
      for (StructMethod mt : cl.getMethods()) {
        boolean hide =
            mt.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC)
                || mt.hasModifier(CodeConstants.ACC_BRIDGE)
                    && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_BRIDGE)
                || wrapper
                    .getHiddenMembers()
                    .contains(InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor()));
        if (hide) continue;

        int position = buffer.length();
        int storedLine = startLine;
        if (hasContent) {
          buffer.appendLineSeparator();
          startLine++;
        }
        BytecodeMappingTracer method_tracer = new BytecodeMappingTracer(startLine);
        boolean methodSkipped = !methodToJava(node, mt, buffer, indent + 1, method_tracer);
        if (!methodSkipped) {
          hasContent = true;
          addTracer(cl, mt, method_tracer);
          startLine = method_tracer.getCurrentSourceLine();
        } else {
          buffer.setLength(position);
          startLine = storedLine;
        }
      }

      // member classes
      for (ClassNode inner : node.nested) {
        if (inner.type == ClassNode.CLASS_MEMBER) {
          StructClass innerCl = inner.classStruct;
          boolean isSynthetic =
              (inner.access & CodeConstants.ACC_SYNTHETIC) != 0
                  || innerCl.isSynthetic()
                  || inner.namelessConstructorStub;
          boolean hide =
              isSynthetic && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC)
                  || wrapper.getHiddenMembers().contains(innerCl.qualifiedName);
          if (hide) continue;

          if (hasContent) {
            buffer.appendLineSeparator();
            startLine++;
          }
          BytecodeMappingTracer class_tracer = new BytecodeMappingTracer(startLine);
          classToJava(inner, buffer, indent + 1, class_tracer);
          startLine = buffer.countLines();

          hasContent = true;
        }
      }

      buffer.appendIndent(indent).append('}');

      if (node.type != ClassNode.CLASS_ANONYMOUS) {
        buffer.appendLineSeparator();
      }
    } finally {
      DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, outerNode);
    }

    DecompilerContext.getLogger().endWriteClass();
  }