content: package com.alibaba.fastjson2.writer;
  
  import com.alibaba.fastjson2.*;
  import com.alibaba.fastjson2.annotation.JSONField;
  import com.alibaba.fastjson2.codec.FieldInfo;
  import com.alibaba.fastjson2.util.*;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.io.Serializable;
  import java.lang.reflect.*;
  import java.math.BigDecimal;
  import java.text.DecimalFormat;
  import java.time.*;
  import java.util.*;
  import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
  import java.util.function.Function;
  import java.util.zip.GZIPOutputStream;
  
  import static com.alibaba.fastjson2.JSONWriter.Feature.*;
  import static com.alibaba.fastjson2.util.JDKUtils.UNSAFE;
  import static java.time.temporal.ChronoField.SECOND_OF_DAY;
  import static java.time.temporal.ChronoField.YEAR;
  
  public abstract class FieldWriter<T>
          implements Comparable {
      public final String fieldName;
      public final Type fieldType;
      public final Class fieldClass;
      public final long features;
      public final int ordinal;
      public final String format;
      public final Locale locale;
      public final DecimalFormat decimalFormat;
      public final String label;
      public final Field field;
      public final Method method;
      protected final long fieldOffset;
      protected final boolean primitive;
  
      final long hashCode;
      final byte[] nameWithColonUTF8;
      final char[] nameWithColonUTF16;
      final byte[] nameJSONB;
      long nameSymbolCache;
  
      final boolean fieldClassSerializable;
      final JSONWriter.Path rootParentPath;
  
      final boolean symbol;
      final boolean trim;
      final boolean raw;
      final boolean managedReference;
      final boolean backReference;
  
      transient JSONWriter.Path path;
      volatile ObjectWriter initObjectWriter;
      Object defaultValue;
  
      static final AtomicReferenceFieldUpdater<FieldWriter, ObjectWriter>
              initObjectWriterUpdater = AtomicReferenceFieldUpdater.newUpdater(
              FieldWriter.class,
              ObjectWriter.class,
              "initObjectWriter"
      );
  
      FieldWriter(
              String name,
              int ordinal,
              long features,
              String format,
              Locale locale,
              String label,
              Type fieldType,
              Class fieldClass,
              Field field,
              Method method
      ) {
          if ("string".equals(format) && fieldClass != String.class) {
              features |= WriteNonStringValueAsString.mask;
          }
  
          this.fieldName = name;
          this.ordinal = ordinal;
          this.format = format;
          this.locale = locale;
          this.label = label;
          this.hashCode = Fnv.hashCode64(name);
          this.features = features;
          this.fieldType = TypeUtils.intern(fieldType);
          this.fieldClass = fieldClass;
          this.fieldClassSerializable = fieldClass != null && (Serializable.class.isAssignableFrom(fieldClass) || !Modifier.isFinal(fieldClass.getModifiers()));
          this.field = field;
          this.method = method;
          this.primitive = fieldClass.isPrimitive();
  
          this.nameJSONB = JSONB.toBytes(fieldName);
  
          DecimalFormat decimalFormat = null;
          if (format != null
                  && (fieldClass == float.class
                  || fieldClass == float[].class
                  || fieldClass == Float.class
                  || fieldClass == Float[].class
                  || fieldClass == double.class
                  || fieldClass == double[].class
                  || fieldClass == Double.class
                  || fieldClass == Double[].class
                  || fieldClass == BigDecimal.class
                  || fieldClass == BigDecimal[].class)
          ) {
              decimalFormat = new DecimalFormat(format);
          }
          this.decimalFormat = decimalFormat;
  
          long fieldOffset = -1L;
          if (field != null) {
              fieldOffset = UNSAFE.objectFieldOffset(field);
          }
          this.fieldOffset = fieldOffset;
  
          this.symbol = "symbol".equals(format);
          this.trim = "trim".equals(format);
          this.raw = (features & FieldInfo.RAW_VALUE_MASK) != 0;
          this.managedReference = (features & ReferenceDetection.mask) != 0;
          this.backReference = (features & FieldInfo.BACKR_EFERENCE) != 0;
          this.rootParentPath = new JSONWriter.Path(JSONWriter.Path.ROOT, name);
  
          int nameLength = name.length();
          int utflen = nameLength + 3;
          for (int i = 0; i < nameLength; ++i) {
              char c = name.charAt(i);
              if ((c >= 0x0001) && (c <= 0x007F)) {
                  // skip
              } else if (c > 0x07FF) {
                  utflen += 2;
              } else {
                  utflen += 1;
              }
          }
  
          byte[] bytes = new byte[utflen];
          int off = 0;
          bytes[off++] = '"';
          for (int i = 0; i < nameLength; ++i) {
              char c = name.charAt(i);
              if ((c >= 0x0001) && (c <= 0x007F)) {
                  bytes[off++] = (byte) c;
              } else if (c > 0x07FF) {
                  // 2 bytes, 11 bits
                  bytes[off++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
                  bytes[off++] = (byte) (0x80 | ((c >> 6) & 0x3F));
                  bytes[off++] = (byte) (0x80 | ((c) & 0x3F));
              } else {
                  bytes[off++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
                  bytes[off++] = (byte) (0x80 | ((c) & 0x3F));
              }
          }
          bytes[off++] = '"';
          bytes[off] = ':';
          nameWithColonUTF8 = bytes;
  
          char[] chars = new char[nameLength + 3];
          chars[0] = '"';
          name.getChars(0, name.length(), chars, 1);
          chars[chars.length - 2] = '"';
          chars[chars.length - 1] = ':';
          nameWithColonUTF16 = chars;
      }
  
      public boolean isFieldClassSerializable() {
          return fieldClassSerializable;
      }
  
      public boolean isDateFormatMillis() {
          return false;
      }
  
      public boolean isDateFormatISO8601() {
          return false;
      }
  
      public int writeEnumValueJSONB(byte[] bytes, int off, Enum e, SymbolTable symbolTable, long features) {
          throw new UnsupportedOperationException();
      }
  
      public void writeEnumJSONB(JSONWriter jsonWriter, Enum e) {
          throw new UnsupportedOperationException();
      }
  
      public ObjectWriter getInitWriter() {
          return null;
      }
  
      public boolean unwrapped() {
          return false;
      }
  
      public final void writeFieldNameJSONB(JSONWriter jsonWriter) {
          SymbolTable symbolTable = jsonWriter.symbolTable;
          if (symbolTable != null && writeFieldNameSymbol(jsonWriter, symbolTable)) {
              return;
          }
          jsonWriter.writeNameRaw(nameJSONB, hashCode);
      }
  
      public final int writeFieldNameJSONB(byte[] bytes, int off) {
          System.arraycopy(nameJSONB, 0, bytes, off, nameJSONB.length);
          return off + nameJSONB.length;
      }
  
      public final int writeFieldNameJSONB(byte[] bytes, int off, JSONWriter jsonWriter) {
          return JSONB.IO.writeNameRaw(bytes, off, nameJSONB, hashCode, jsonWriter);
      }
  
      public final void writeFieldName(JSONWriter jsonWriter) {
          if (jsonWriter.jsonb) {
              SymbolTable symbolTable = jsonWriter.symbolTable;
              if (symbolTable != null && writeFieldNameSymbol(jsonWriter, symbolTable)) {
                  return;
              }
              jsonWriter.writeNameRaw(nameJSONB, hashCode);
              return;
          }
  
          if (!jsonWriter.useSingleQuote && (jsonWriter.context.getFeatures() & UnquoteFieldName.mask) == 0) {
              if (jsonWriter.utf8) {
                  jsonWriter.writeNameRaw(nameWithColonUTF8);
                  return;
              }
  
              if (jsonWriter.utf16) {
                  jsonWriter.writeNameRaw(nameWithColonUTF16);
                  return;
              }
          }
  
          jsonWriter.writeName(fieldName);
          jsonWriter.writeColon();
      }
  
      private boolean writeFieldNameSymbol(JSONWriter jsonWriter, SymbolTable symbolTable) {
          int symbolTableIdentity = System.identityHashCode(symbolTable);
  
          int symbol;
          if (nameSymbolCache == 0) {
              symbol = symbolTable.getOrdinalByHashCode(hashCode);
              nameSymbolCache = ((long) symbol << 32) | symbolTableIdentity;
          } else {
              if ((int) nameSymbolCache == symbolTableIdentity) {
                  symbol = (int) (nameSymbolCache >> 32);
              } else {
                  symbol = symbolTable.getOrdinalByHashCode(hashCode);
                  nameSymbolCache = ((long) symbol << 32) | symbolTableIdentity;
              }
          }
  
          if (symbol != -1) {
              jsonWriter.writeSymbol(-symbol);
              return true;
          }
          return false;
      }
  
      public int writeFieldNameSymbol(SymbolTable symbolTable) {
          int symbolTableIdentity = System.identityHashCode(symbolTable);
  
          int symbol;
          if (nameSymbolCache == 0) {
              symbol = symbolTable.getOrdinalByHashCode(hashCode);
              nameSymbolCache = ((long) symbol << 32) | symbolTableIdentity;
          } else {
              if ((int) nameSymbolCache == symbolTableIdentity) {
                  symbol = (int) (nameSymbolCache >> 32);
              } else {
                  symbol = symbolTable.getOrdinalByHashCode(hashCode);
                  nameSymbolCache = ((long) symbol << 32) | symbolTableIdentity;
              }
          }
          return symbol;
      }
  
      public boolean isRefDetect(Object object, long features) {
          features |= this.features;
          return (features & ReferenceDetection.mask) != 0
                  && (features & FieldInfo.DISABLE_REFERENCE_DETECT) == 0
                  && object != null
                  && !ObjectWriterProvider.isNotReferenceDetect(object.getClass());
      }
  
      public final JSONWriter.Path getRootParentPath() {
          return rootParentPath;
      }
  
      public final JSONWriter.Path getPath(JSONWriter.Path parent) {
          if (path == null) {
              return path = new JSONWriter.Path(parent, fieldName);
          }
  
          if (path.parent == parent) {
              return path;
          }
  
          return new JSONWriter.Path(parent, fieldName);
      }
  
      public Type getItemType() {
          return null;
      }
  
      public Class getItemClass() {
          return null;
      }
  
      @Override
      public String toString() {
          return fieldName;
      }
  
      void setDefaultValue(T object) {
          Object fieldValue = null;
  
          if (Iterable.class.isAssignableFrom(fieldClass)
                  || Map.class.isAssignableFrom(fieldClass)
          ) {
              return;
          }
  
          if (field != null && object != null) {
              try {
                  field.setAccessible(true);
                  fieldValue = field.get(object);
              } catch (Throwable ignored) {
                  // ignored
              }
          }
  
          if (fieldValue == null) {
              return;
          }
  
          if (fieldClass == boolean.class) {
              if (fieldValue.equals(Boolean.FALSE)) {
                  return;
              }
          } else if (fieldClass == byte.class
                  || fieldClass == short.class
                  || fieldClass == int.class
                  || fieldClass == long.class
                  || fieldClass == float.class
                  || fieldClass == double.class
          ) {
              if (((Number) fieldValue).doubleValue() == 0) {
                  return;
              }
          } else if (fieldClass == char.class) {
              if ((Character) fieldValue == '\0') {
                  return;
              }
          }
  
          defaultValue = fieldValue;
      }
  
      public Object getFieldValue(T object) {
          if (object == null) {
              throw new JSONException("field.get error, " + fieldName);
          }
  
          if (field != null) {
              try {
                  Object value;
                  if (fieldOffset != -1 && !primitive) {
                      value = UNSAFE.getObject(object, fieldOffset);
                  } else {
                      value = field.get(object);
                  }
                  return value;
              } catch (IllegalArgumentException | IllegalAccessException e) {
                  throw new JSONException("field.get error, " + fieldName, e);
              }
          }
  
          throw new UnsupportedOperationException();
      }
  
      @Override
      public int compareTo(Object o) {
          FieldWriter other = (FieldWriter) o;
  
          int thisOrdinal = this.ordinal;
          int otherOrdinal = other.ordinal;
          if (thisOrdinal < otherOrdinal) {
              return -1;
          }
          if (thisOrdinal > otherOrdinal) {
              return 1;
          }
  
          int nameCompare = this.fieldName.compareTo(other.fieldName);
  
          if (nameCompare != 0) {
              return nameCompare;
          }
  
          Member thisMember;
          Member otherMember;
          if (this.method == null || (this.field != null && Modifier.isPublic(this.field.getModifiers()))) {
              thisMember = this.field;
          } else {
              thisMember = this.method;
          }
          if (other.method == null || (other.field != null && Modifier.isPublic(other.field.getModifiers()))) {
              otherMember = other.field;
          } else {
              otherMember = other.method;
          }
  
          if (thisMember != null && otherMember != null) {
              Class otherDeclaringClass = otherMember.getDeclaringClass();
              Class thisDeclaringClass = thisMember.getDeclaringClass();
              if (thisDeclaringClass != otherDeclaringClass) {
                  if (thisDeclaringClass.isAssignableFrom(otherDeclaringClass)) {
                      return 1;
                  } else if (otherDeclaringClass.isAssignableFrom(thisDeclaringClass)) {
                      return -1;
                  }
              }
  
              JSONField thisField = null;
              JSONField otherField = null;
              if (thisMember instanceof Field) {
                  thisField = ((Field) thisMember).getAnnotation(JSONField.class);
              } else if (thisMember instanceof Method) {
                  thisField = ((Method) thisMember).getAnnotation(JSONField.class);
              }
              if (otherMember instanceof Field) {
                  otherField = ((Field) otherMember).getAnnotation(JSONField.class);
              } else if (thisMember instanceof Method) {
                  otherField = ((Method) otherMember).getAnnotation(JSONField.class);
              }
  
              if (thisField != null && otherField == null) {
                  return -1;
              }
              if (thisField == null && otherField != null) {
                  return 1;
              }
          }
  
          if (thisMember instanceof Field
                  && otherMember instanceof Method
                  && ((Field) thisMember).getType() == ((Method) otherMember).getReturnType()
          ) {
              return -1;
          }
  
          if (thisMember instanceof Method
                  && otherMember instanceof Field
                  && ((Method) thisMember).getReturnType() == ((Field) otherMember).getType()
          ) {
              return 1;
          }
  
          Class otherFieldClass = other.fieldClass;
          Class thisFieldClass = this.fieldClass;
          if (thisFieldClass != otherFieldClass && thisFieldClass != null && otherFieldClass != null) {
              if (thisFieldClass.isAssignableFrom(otherFieldClass)) {
                  return 1;
              } else if (otherFieldClass.isAssignableFrom(thisFieldClass)) {
                  return -1;
              }
          }
  
          if (thisFieldClass == boolean.class && otherFieldClass != boolean.class) {
              return 1;
          }
  
          if (thisFieldClass == Boolean.class
                  && otherFieldClass == Boolean.class
                  && thisMember instanceof Method
                  && otherMember instanceof Method
          ) {
              String thisMethodName = thisMember.getName();
              String otherMethodName = otherMember.getName();
              if (thisMethodName.startsWith("is") && otherMethodName.startsWith("get")) {
                  return 1;
              }
              if (thisMethodName.startsWith("get") && otherMethodName.startsWith("is")) {
                  return -1;
              }
          }
  
          if (thisMember instanceof Method && otherMember instanceof Method) {
              String thisMethodName = thisMember.getName();
              String otherMethodName = otherMember.getName();
              if (!thisMethodName.equals(otherMethodName)) {
                  String thisSetterName = BeanUtils.getterName(thisMethodName, null);
                  String otherSetterName = BeanUtils.getterName(otherMethodName, null);
                  if (this.fieldName.equals(thisSetterName) && !other.fieldName.equals(otherSetterName)) {
                      return 1;
                  }
                  if (this.fieldName.equals(otherSetterName) && !other.fieldName.equals(thisSetterName)) {
                      return -1;
                  }
              }
          }
  
          if (thisFieldClass.isPrimitive() && !otherFieldClass.isPrimitive()) {
              return -1;
          }
  
          if (!thisFieldClass.isPrimitive() && otherFieldClass.isPrimitive()) {
              return 1;
          }
  
          if (thisFieldClass.getName().startsWith("java.") && !otherFieldClass.getName().startsWith("java.")) {
              return -1;
          }
  
          if (!thisFieldClass.getName().startsWith("java.") && otherFieldClass.getName().startsWith("java.")) {
              return 1;
          }
  
          if (this.method != null && other.method == null) {
              return -1;
          }
  
          if (this.method == null && other.method != null) {
              return 1;
          }
  
          return nameCompare;
      }
  
      public void writeEnum(JSONWriter jsonWriter, Enum e) {
          writeFieldName(jsonWriter);
          jsonWriter.writeEnum(e);
      }
  
      public void writeBinary(JSONWriter jsonWriter, byte[] value) {
          if (value == null) {
              if (!jsonWriter.isWriteNulls()) {
                  return;
              }
              writeFieldName(jsonWriter);
              jsonWriter.writeArrayNull();
              return;
          }
  
          writeFieldName(jsonWriter);
          if ((features & WriteNonStringValueAsString.mask) != 0) {
              jsonWriter.writeString(value);
          } else if ("base64".equals(format)
                  || (format == null && (jsonWriter.getFeatures(this.features) & WriteByteArrayAsBase64.mask) != 0)
          ) {
              jsonWriter.writeBase64(value);
          } else if ("hex".equals(format)) {
              jsonWriter.writeHex(value);
          } else if ("gzip,base64".equals(format) || "gzip".equals(format)) {
              GZIPOutputStream gzipOut = null;
              try {
                  ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
                  if (value.length < 512) {
                      gzipOut = new GZIPOutputStream(byteOut, value.length);
                  } else {
                      gzipOut = new GZIPOutputStream(byteOut);
                  }
                  gzipOut.write(value);
                  gzipOut.finish();
                  value = byteOut.toByteArray();
              } catch (IOException ex) {
                  throw new JSONException("write gzipBytes error", ex);
              } finally {
                  IOUtils.close(gzipOut);
              }
  
              jsonWriter.writeBase64(value);
          } else {
              jsonWriter.writeBinary(value);
          }
      }
  
      public void writeInt16(JSONWriter jsonWriter, short[] value) {
          if (value == null && !jsonWriter.isWriteNulls()) {
              return;
          }
  
          writeFieldName(jsonWriter);
          if ((features & WriteNonStringValueAsString.mask) != 0) {
              jsonWriter.writeString(value);
          } else {
              jsonWriter.writeInt16(value);
          }
      }
  
      public void writeInt32(JSONWriter jsonWriter, int value) {
          writeFieldName(jsonWriter);
          jsonWriter.writeInt32(value);
      }
  
      public void writeInt64(JSONWriter jsonWriter, long value) {
          writeFieldName(jsonWriter);
          if ((features & WriteNonStringValueAsString.mask) != 0) {
              jsonWriter.writeString(Long.toString(value));
          } else {
              jsonWriter.writeInt64(value);
          }
      }
  
      public void writeString(JSONWriter jsonWriter, String value) {
          writeFieldName(jsonWriter);
  
          if (value == null && (features & (NullAsDefaultValue.mask | WriteNullStringAsEmpty.mask)) != 0) {
              jsonWriter.writeString("");
              return;
          }
  
          if (trim && value != null) {
              value = value.trim();
          }
  
          if (symbol && jsonWriter.jsonb) {
              jsonWriter.writeSymbol(value);
          } else {
              if (raw) {
                  jsonWriter.writeRaw(value);
              } else {
                  jsonWriter.writeString(value);
              }
          }
      }
  
      public void writeString(JSONWriter jsonWriter, char[] value) {
          if (value == null && !jsonWriter.isWriteNulls()) {
              return;
          }
  
          writeFieldName(jsonWriter);
          if (value == null) {
              jsonWriter.writeStringNull();
              return;
          }
  
          jsonWriter.writeString(value, 0, value.length);
      }
  
      public void writeFloat(JSONWriter jsonWriter, float value) {
          writeFieldName(jsonWriter);
          if (decimalFormat != null) {
              jsonWriter.writeFloat(value, decimalFormat);
          } else {
              if ((features & WriteNonStringValueAsString.mask) != 0) {
                  jsonWriter.writeString(Float.toString(value));
              } else {
                  jsonWriter.writeFloat(value);
              }
          }
      }
  
      public void writeDouble(JSONWriter jsonWriter, double value) {
          writeFieldName(jsonWriter);
          if (decimalFormat != null) {
              jsonWriter.writeDouble(value, decimalFormat);
          } else {
              boolean writeNonStringValueAsString = (features & JSONWriter.Feature.WriteNonStringValueAsString.mask) != 0;
              if (writeNonStringValueAsString) {
                  jsonWriter.writeString(Double.toString(value));
              } else {
                  jsonWriter.writeDouble(value);
              }
          }
      }
  
      public void writeBool(JSONWriter jsonWriter, boolean value) {
          throw new UnsupportedOperationException();
      }
  
      public void writeBool(JSONWriter jsonWriter, boolean[] value) {
          if (value == null && !jsonWriter.isWriteNulls()) {
              return;
          }
  
          writeFieldName(jsonWriter);
          if ((features & WriteNonStringValueAsString.mask) != 0) {
              jsonWriter.writeString(value);
          } else {
              jsonWriter.writeBool(value);
          }
      }
  
      public void writeFloat(JSONWriter jsonWriter, float[] value) {
          if (value == null && !jsonWriter.isWriteNulls()) {
              return;
          }
  
          writeFieldName(jsonWriter);
          if ((features & WriteNonStringValueAsString.mask) != 0) {
              jsonWriter.writeString(value);
          } else {
              jsonWriter.writeFloat(value);
          }
      }
  
      public void writeDouble(JSONWriter jsonWriter, double[] value) {
          if (value == null && !jsonWriter.isWriteNulls()) {
              return;
          }
  
          writeFieldName(jsonWriter);
          if ((features & WriteNonStringValueAsString.mask) != 0) {
              jsonWriter.writeString(value);
          } else {
              jsonWriter.writeDouble(value);
          }
      }
  
      public void writeDouble(JSONWriter jsonWriter, Double value) {
          if (value == null) {
              long features = jsonWriter.getFeatures(this.features);
              if ((features & JSONWriter.Feature.WriteNulls.mask) != 0
                      && (features & JSONWriter.Feature.NotWriteDefaultValue.mask) == 0
              ) {
                  writeFieldName(jsonWriter);
                  jsonWriter.writeNumberNull();
              }
              return;
          }
  
          writeFieldName(jsonWriter);
          jsonWriter.writeDouble(value);
      }
  
      public void writeDate(JSONWriter jsonWriter, boolean writeFieldName, Date value) {
          if (value == null) {
              if (writeFieldName) {
                  writeFieldName(jsonWriter);
              }
  
              jsonWriter.writeNull();
              return;
          }
  
          writeDate(jsonWriter, writeFieldName, value.getTime());
      }
  
      public void writeDate(JSONWriter jsonWriter, long millis) {
          writeDate(jsonWriter, true, millis);
      }
  
      public void writeDate(JSONWriter jsonWriter, boolean writeFieldName, long millis) {
          if (jsonWriter.jsonb) {
              jsonWriter.writeMillis(millis);
              return;
          }
  
          final long SECONDS_PER_DAY = 60 * 60 * 24;
  
          JSONWriter.Context ctx = jsonWriter.context;
          if (isDateFormatMillis() || ctx.isDateFormatMillis()) {
              if (writeFieldName) {
                  writeFieldName(jsonWriter);
              }
              jsonWriter.writeInt64(millis);
              return;
          }
  
          ZoneId zoneId = ctx.getZoneId();
  
          String dateFormat = ctx.getDateFormat();
          if (dateFormat == null) {
              Instant instant = Instant.ofEpochMilli(millis);
              long epochSecond = instant.getEpochSecond();
              ZoneOffset offset = zoneId
                      .getRules()
                      .getOffset(instant);
  
              long localSecond = epochSecond + offset.getTotalSeconds();
              long localEpochDay = Math.floorDiv(localSecond, SECONDS_PER_DAY);
              int secsOfDay = (int) Math.floorMod(localSecond, SECONDS_PER_DAY);
              int year, month, dayOfMonth;
              {
                  final int DAYS_PER_CYCLE = 146097;
                  final long DAYS_0000_TO_1970 = (DAYS_PER_CYCLE * 5L) - (30L * 365L + 7L);
  
                  long zeroDay = localEpochDay + DAYS_0000_TO_1970;
                  // find the march-based year
                  zeroDay -= 60;  // adjust to 0000-03-01 so leap day is at end of four year cycle
                  long adjust = 0;
                  if (zeroDay < 0) {
                      // adjust negative years to positive for calculation
                      long adjustCycles = (zeroDay + 1) / DAYS_PER_CYCLE - 1;
                      adjust = adjustCycles * 400;
                      zeroDay += -adjustCycles * DAYS_PER_CYCLE;
                  }
                  long yearEst = (400 * zeroDay + 591) / DAYS_PER_CYCLE;
                  long doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
                  if (doyEst < 0) {
                      // fix estimate
                      yearEst--;
                      doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
                  }
                  yearEst += adjust;  // reset any negative year
                  int marchDoy0 = (int) doyEst;
  
                  // convert march-based values back to january-based
                  int marchMonth0 = (marchDoy0 * 5 + 2) / 153;
                  month = (marchMonth0 + 2) % 12 + 1;
                  dayOfMonth = marchDoy0 - (marchMonth0 * 306 + 5) / 10 + 1;
                  yearEst += marchMonth0 / 10;
  
                  // check year now we are certain it is correct
                  year = YEAR.checkValidIntValue(yearEst);
              }
  
              int hour, minute, second;
              {
                  final int MINUTES_PER_HOUR = 60;
                  final int SECONDS_PER_MINUTE = 60;
                  final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
  
                  long secondOfDay = secsOfDay;
                  SECOND_OF_DAY.checkValidValue(secondOfDay);
                  int hours = (int) (secondOfDay / SECONDS_PER_HOUR);
                  secondOfDay -= hours * SECONDS_PER_HOUR;
                  int minutes = (int) (secondOfDay / SECONDS_PER_MINUTE);
                  secondOfDay -= minutes * SECONDS_PER_MINUTE;
  
                  hour = hours;
                  minute = minutes;
                  second = (int) secondOfDay;
              }
  
              if (writeFieldName) {
                  writeFieldName(jsonWriter);
              }
              jsonWriter.writeDateTime19(year, month, dayOfMonth, hour, minute, second);
          } else {
              ZonedDateTime zdt = ZonedDateTime
                      .ofInstant(
                              Instant.ofEpochMilli(millis), zoneId);
  
              if (isDateFormatISO8601() || ctx.isDateFormatISO8601()) {
                  int year = zdt.getYear();
                  int month = zdt.getMonthValue();
                  int dayOfMonth = zdt.getDayOfMonth();
                  int hour = zdt.getHour();
                  int minute = zdt.getMinute();
                  int second = zdt.getSecond();
                  int milliSeconds = zdt.getNano() / 1000_000;
                  int offsetSeconds = zdt.getOffset().getTotalSeconds();
                  jsonWriter.writeDateTimeISO8601(year, month, dayOfMonth, hour, minute, second, milliSeconds, offsetSeconds, true);
                  return;
              }
  
              String str = ctx.getDateFormatter().format(zdt);
  
              if (writeFieldName) {
                  writeFieldName(jsonWriter);
              }
              jsonWriter.writeString(str);
          }
      }
  
      public ObjectWriter getItemWriter(JSONWriter writer, Type itemType) {
          return writer
                  .getObjectWriter(itemType, null);
      }
  
      public abstract void writeValue(JSONWriter jsonWriter, T object);
  
      public abstract boolean write(JSONWriter jsonWriter, T o);
  
      public ObjectWriter getObjectWriter(JSONWriter jsonWriter, Class valueClass) {
          if (valueClass == Float[].class) {
              if (decimalFormat != null) {
                  return new ObjectWriterArrayFinal(Float.class, decimalFormat);
              } else {
                  return ObjectWriterArrayFinal.FLOAT_ARRAY;
              }
          }
  
          if (valueClass == Double[].class) {
              if (decimalFormat != null) {
                  return new ObjectWriterArrayFinal(Double.class, decimalFormat);
              } else {
                  return ObjectWriterArrayFinal.DOUBLE_ARRAY;
              }
          }
  
          if (valueClass == BigDecimal[].class) {
              if (decimalFormat != null) {
                  return new ObjectWriterArrayFinal(BigDecimal.class, decimalFormat);
              } else {
                  return ObjectWriterArrayFinal.DECIMAL_ARRAY;
              }
          }
  
          return jsonWriter.getObjectWriter(valueClass);
      }
  
      public void writeListValueJSONB(JSONWriter jsonWriter, List list) {
          throw new UnsupportedOperationException();
      }
  
      public void writeListValue(JSONWriter jsonWriter, List list) {
          throw new UnsupportedOperationException();
      }
  
      public void writeListJSONB(JSONWriter jsonWriter, List list) {
          throw new UnsupportedOperationException();
      }
  
      public void writeList(JSONWriter jsonWriter, List list) {
          throw new UnsupportedOperationException();
      }
  
      public void writeListStr(JSONWriter jsonWriter, boolean writeFieldName, List<String> list) {
          throw new UnsupportedOperationException();
      }
  
      static ObjectWriter getObjectWriter(
              Type fieldType,
              Class fieldClass,
              String format,
              Locale locale,
              Class valueClass
      ) {
          if (Map.class.isAssignableFrom(valueClass)) {
              if (fieldClass.isAssignableFrom(valueClass)) {
                  return ObjectWriterImplMap.of(fieldType, format, valueClass);
              } else {
                  return ObjectWriterImplMap.of(valueClass);
              }
          } else {
              if (Calendar.class.isAssignableFrom(valueClass)) {
                  if (format == null || format.isEmpty()) {
                      return ObjectWriterImplCalendar.INSTANCE;
                  }
                  return new ObjectWriterImplCalendar(format, locale);
              }
  
              if (ZonedDateTime.class.isAssignableFrom(valueClass)) {
                  if (format == null || format.isEmpty()) {
                      return ObjectWriterImplZonedDateTime.INSTANCE;
                  } else {
                      return new ObjectWriterImplZonedDateTime(format, locale);
                  }
              }
  
              if (OffsetDateTime.class.isAssignableFrom(valueClass)) {
                  if (format == null || format.isEmpty()) {
                      return ObjectWriterImplOffsetDateTime.INSTANCE;
                  } else {
                      return ObjectWriterImplOffsetDateTime.of(format, locale);
                  }
              }
  
              if (LocalDateTime.class.isAssignableFrom(valueClass)) {
                  ObjectWriter objectWriter = JSONFactory.getDefaultObjectWriterProvider().getObjectWriter(LocalDateTime.class);
                  if (objectWriter != null && objectWriter != ObjectWriterImplLocalDateTime.INSTANCE) {
                      return objectWriter;
                  }
  
                  if (format == null || format.isEmpty()) {
                      return ObjectWriterImplLocalDateTime.INSTANCE;
                  } else {
                      return new ObjectWriterImplLocalDateTime(format, locale);
                  }
              }
  
              if (LocalDate.class.isAssignableFrom(valueClass)) {
                  ObjectWriter objectWriter = JSONFactory.getDefaultObjectWriterProvider().getObjectWriter(LocalDate.class);
                  if (objectWriter != null && objectWriter != ObjectWriterImplLocalDate.INSTANCE) {
                      return objectWriter;
                  }
  
                  return ObjectWriterImplLocalDate.of(format, locale);
              }
  
              if (LocalTime.class.isAssignableFrom(valueClass)) {
                  ObjectWriter objectWriter = JSONFactory.getDefaultObjectWriterProvider().getObjectWriter(LocalTime.class);
                  if (objectWriter != null && objectWriter != ObjectWriterImplLocalTime.INSTANCE) {
                      return objectWriter;
                  }
  
                  if (format == null || format.isEmpty()) {
                      return ObjectWriterImplLocalTime.INSTANCE;
                  } else {
                      return new ObjectWriterImplLocalTime(format, locale);
                  }
              }
  
              if (Instant.class == valueClass) {
                  if (format == null || format.isEmpty()) {
                      return ObjectWriterImplInstant.INSTANCE;
                  } else {
                      return new ObjectWriterImplInstant(format, locale);
                  }
              }
  
              if (BigDecimal.class == valueClass) {
                  if (format == null || format.isEmpty()) {
                      return ObjectWriterImplBigDecimal.INSTANCE;
                  } else {
                      return new ObjectWriterImplBigDecimal(new DecimalFormat(format), null);
                  }
              }
  
              if (BigDecimal[].class == valueClass) {
                  if (format == null || format.isEmpty()) {
                      return new ObjectWriterArrayFinal(BigDecimal.class, null);
                  } else {
                      return new ObjectWriterArrayFinal(BigDecimal.class, new DecimalFormat(format));
                  }
              }
  
              if (Optional.class == valueClass) {
                  return ObjectWriterImplOptional.of(format, locale);
              }
  
              String className = valueClass.getName();
              switch (className) {
                  case "java.sql.Time":
                      return JdbcSupport.createTimeWriter(format);
                  case "java.sql.Date":
                      return new ObjectWriterImplDate(format, locale);
                  case "java.sql.Timestamp":
                      return JdbcSupport.createTimestampWriter(valueClass, format);
                  case "org.joda.time.LocalDate":
                      return JodaSupport.createLocalDateWriter(valueClass, format);
                  case "org.joda.time.LocalDateTime":
                      return JodaSupport.createLocalDateTimeWriter(valueClass, format);
                  default:
                      break;
              }
          }
  
          return null;
      }
  
      public Function getFunction() {
          return null;
      }
  
      protected final boolean writeFloatNull(JSONWriter jsonWriter) {
          long features = jsonWriter.getFeatures(this.features);
          if ((features & (JSONWriter.Feature.WriteNulls.mask | JSONWriter.Feature.NullAsDefaultValue.mask | JSONWriter.Feature.WriteNullNumberAsZero.mask)) == 0) {
              return false;
          }
          writeFieldName(jsonWriter);
          if ((features & NullAsDefaultValue.mask) != 0) {
              jsonWriter.writeFloat(0.0F);
          } else if ((features & WriteNullNumberAsZero.mask) != 0) {
              jsonWriter.writeInt32(0);
          } else {
              jsonWriter.writeNull();
          }
          return true;
      }
  
      protected final boolean writeIntNull(JSONWriter jsonWriter) {
          long features = this.features | jsonWriter.getFeatures();
          if ((features & (JSONWriter.Feature.WriteNulls.mask | JSONWriter.Feature.NullAsDefaultValue.mask | JSONWriter.Feature.WriteNullNumberAsZero.mask)) == 0) {
              return false;
          }
          writeFieldName(jsonWriter);
  
          if ((features & (JSONWriter.Feature.NullAsDefaultValue.mask | JSONWriter.Feature.WriteNullNumberAsZero.mask)) != 0) {
              jsonWriter.writeInt32(0);
          } else {
              jsonWriter.writeNull();
          }
          return true;
      }
  }
  