/**
  * if out == null => automatically create/reuse a bytebuffer
  *
  * @param out
  */
 public void resetForReUse(OutputStream out) {
   if (closed) throw new RuntimeException("Can't reuse closed stream");
   codec.reset();
   if (out != null) {
     codec.setOutstream(out);
   }
   objects.clearForWrite();
 }
 // write identical to other version, but take field values from hashmap (because of annoying
 // putField/getField feature)
 private void writeCompatibleObjectFields(
     Object toWrite, Map fields, FSTClazzInfo.FSTFieldInfo[] fieldInfo) throws IOException {
   int booleanMask = 0;
   int boolcount = 0;
   for (int i = 0; i < fieldInfo.length; i++) {
     try {
       FSTClazzInfo.FSTFieldInfo subInfo = fieldInfo[i];
       boolean isarr = subInfo.isArray();
       Class subInfType = subInfo.getType();
       if (subInfType != boolean.class || isarr) {
         if (boolcount > 0) {
           codec.writeFByte(booleanMask << (8 - boolcount));
           boolcount = 0;
           booleanMask = 0;
         }
       }
       if (subInfo.isIntegral() && !isarr) {
         if (subInfType == boolean.class) {
           if (boolcount == 8) {
             codec.writeFByte(booleanMask << (8 - boolcount));
             boolcount = 0;
             booleanMask = 0;
           }
           boolean booleanValue =
               ((Boolean) fields.get(subInfo.getField().getName())).booleanValue();
           booleanMask = booleanMask << 1;
           booleanMask = (booleanMask | (booleanValue ? 1 : 0));
           boolcount++;
         } else if (subInfType == int.class) {
           codec.writeFInt(((Number) fields.get(subInfo.getField().getName())).intValue());
         } else if (subInfType == long.class) {
           codec.writeFLong(((Number) fields.get(subInfo.getField().getName())).longValue());
         } else if (subInfType == byte.class) {
           codec.writeFByte(((Number) fields.get(subInfo.getField().getName())).byteValue());
         } else if (subInfType == char.class) {
           codec.writeFChar((char) ((Number) fields.get(subInfo.getField().getName())).intValue());
         } else if (subInfType == short.class) {
           codec.writeFShort(((Number) fields.get(subInfo.getField().getName())).shortValue());
         } else if (subInfType == float.class) {
           codec.writeFFloat(((Number) fields.get(subInfo.getField().getName())).floatValue());
         } else if (subInfType == double.class) {
           codec.writeFDouble(((Number) fields.get(subInfo.getField().getName())).doubleValue());
         }
       } else {
         // object
         Object subObject = fields.get(subInfo.getField().getName());
         writeObjectWithContext(subInfo, subObject);
       }
     } catch (Exception ex) {
       throw FSTUtil.rethrow(ex);
     }
   }
   if (boolcount > 0) {
     codec.writeFByte(booleanMask << (8 - boolcount));
   }
 }
 @Override
 public void close() throws IOException {
   flush();
   closed = true;
   codec.close();
   resetAndClearRefs();
   conf.returnObject(objects);
 }
 public void defaultWriteObject(Object toWrite, FSTClazzInfo serializationInfo)
     throws IOException {
   if (serializationInfo.isExternalizable()) {
     codec.ensureFree(writeExternalWriteAhead);
     ((Externalizable) toWrite).writeExternal(this);
   } else {
     FSTClazzInfo.FSTFieldInfo[] fieldInfo = serializationInfo.getFieldInfo();
     writeObjectFields(toWrite, serializationInfo, fieldInfo, 0, 0);
   }
 }
 /**
  * @param clsInfo
  * @param referencee
  * @param toWrite
  * @return true if header already wrote object
  * @throws IOException
  */
 protected boolean writeObjectHeader(
     final FSTClazzInfo clsInfo, final FSTClazzInfo.FSTFieldInfo referencee, final Object toWrite)
     throws IOException {
   if (toWrite.getClass() == referencee.getType() && !clsInfo.useCompatibleMode()) {
     return codec.writeTag(TYPED, clsInfo, 0, toWrite);
   } else {
     final Class[] possibleClasses = referencee.getPossibleClasses();
     if (possibleClasses == null) {
       if (!codec.writeTag(OBJECT, clsInfo, 0, toWrite)) {
         codec.writeClass(clsInfo);
         return false;
       } else {
         return true;
       }
     } else {
       final int length = possibleClasses.length;
       for (int j = 0; j < length; j++) {
         final Class possibleClass = possibleClasses[j];
         if (possibleClass == toWrite.getClass()) {
           codec.writeFByte(j + 1);
           return false;
         }
       }
       if (!codec.writeTag(OBJECT, clsInfo, 0, toWrite)) {
         codec.writeClass(clsInfo);
         return false;
       } else {
         return true;
       }
     }
   }
 }
 public void writeObject(Object obj, Class... possibles) throws IOException {
   curDepth++;
   if (conf.isCrossPlatform()) {
     writeObjectInternal(obj, null); // not supported cross platform
   }
   if (possibles != null && possibles.length > 1) {
     for (int i = 0; i < possibles.length; i++) {
       Class possible = possibles[i];
       codec.registerClass(possible);
     }
   }
   writeObjectInternal(obj, possibles);
 }
  /**
   * Creates a new FSTObjectOutput stream to write data to the specified underlying output stream.
   * The counter <code>written</code> is set to zero. Don't create a FSTConfiguration with each
   * stream, just create one global static configuration and reuse it. FSTConfiguration is
   * threadsafe.
   *
   * @param out the underlying output stream, to be saved for later use.
   */
  public FSTObjectOutput(OutputStream out, FSTConfiguration conf) {
    this.conf = conf;
    codec = conf.createStreamEncoder();
    codec.setOutstream(out);

    objects = (FSTObjectRegistry) conf.getCachedObject(FSTObjectRegistry.class);
    if (objects == null) {
      objects = new FSTObjectRegistry(conf);
      objects.disabled = !conf.isShareReferences();
    } else {
      objects.clearForWrite();
    }
  }
 // incoming array is already registered
 protected void writeArray(FSTClazzInfo.FSTFieldInfo referencee, Object array) throws IOException {
   if (array == null) {
     codec.writeClass(Object.class);
     codec.writeFInt(-1);
     return;
   }
   final int len = Array.getLength(array);
   Class<?> componentType = array.getClass().getComponentType();
   codec.writeClass(array.getClass());
   codec.writeFInt(len);
   if (!componentType.isArray()) {
     if (codec.isPrimitiveArray(array, componentType)) {
       codec.writePrimitiveArray(array, 0, len);
     } else { // objects
       Object arr[] = (Object[]) array;
       for (int i = 0; i < len; i++) {
         Object toWrite = arr[i];
         writeObjectWithContext(referencee, toWrite);
       }
     }
   } else { // multidim array. FIXME shared refs to subarrays are not tested !!!
     Object[] arr = (Object[]) array;
     FSTClazzInfo.FSTFieldInfo ref1 =
         new FSTClazzInfo.FSTFieldInfo(
             referencee.getPossibleClasses(),
             null,
             conf.getCLInfoRegistry().isIgnoreAnnotations());
     for (int i = 0; i < len; i++) {
       Object subArr = arr[i];
       boolean needsWrite = true;
       if (codec.isTagMultiDimSubArrays()) {
         if (subArr == null) {
           needsWrite = !codec.writeTag(NULL, null, 0, null);
         } else {
           needsWrite = !codec.writeTag(ARRAY, subArr, 0, subArr);
         }
       }
       if (needsWrite) writeArray(ref1, subArr);
     }
   }
 }
 @Override
 public void writeFloat(float v) throws IOException {
   codec.writeFFloat(v);
 }
 @Override
 public void write(byte[] b) throws IOException {
   codec.writePrimitiveArray(b, 0, b.length);
 }
 public void ensureFree(int bytes) throws IOException {
   codec.ensureFree(bytes);
 }
 @Override
 public void write(byte[] b, int off, int len) throws IOException {
   codec.writePrimitiveArray(b, off, len);
 }
 private void writeObjectFields(
     Object toWrite,
     FSTClazzInfo serializationInfo,
     FSTClazzInfo.FSTFieldInfo[] fieldInfo,
     int startIndex,
     int version)
     throws IOException {
   try {
     int booleanMask = 0;
     int boolcount = 0;
     final int length = fieldInfo.length;
     int j = startIndex;
     if (!codec.isWritingAttributes()) {
       for (; ; j++) {
         if (j == length || fieldInfo[j].getVersion() != version) {
           if (boolcount > 0) {
             codec.writeFByte(booleanMask << (8 - boolcount));
           }
           break;
         }
         final FSTClazzInfo.FSTFieldInfo subInfo = fieldInfo[j];
         if (subInfo.getIntegralType() != subInfo.BOOL) {
           if (boolcount > 0) {
             codec.writeFByte(booleanMask << (8 - boolcount));
           }
           break;
         } else {
           if (boolcount == 8) {
             codec.writeFByte(booleanMask << (8 - boolcount));
             boolcount = 0;
             booleanMask = 0;
           }
           boolean booleanValue = subInfo.getBooleanValue(toWrite);
           booleanMask = booleanMask << 1;
           booleanMask = (booleanMask | (booleanValue ? 1 : 0));
           boolcount++;
         }
       }
     }
     for (int i = j; i < length; i++) {
       final FSTClazzInfo.FSTFieldInfo subInfo = fieldInfo[i];
       if (subInfo.getVersion() != version) {
         codec.writeVersionTag(subInfo.getVersion());
         writeObjectFields(toWrite, serializationInfo, fieldInfo, i, subInfo.getVersion());
         return;
       }
       codec.writeAttributeName(subInfo);
       if (subInfo.isPrimitive()) {
         // speed safe
         int integralType = subInfo.getIntegralType();
         switch (integralType) {
           case FSTClazzInfo.FSTFieldInfo.BOOL:
             codec.writeFByte(subInfo.getBooleanValue(toWrite) ? 1 : 0);
             break;
           case FSTClazzInfo.FSTFieldInfo.BYTE:
             codec.writeFByte(subInfo.getByteValue(toWrite));
             break;
           case FSTClazzInfo.FSTFieldInfo.CHAR:
             codec.writeFChar((char) subInfo.getCharValue(toWrite));
             break;
           case FSTClazzInfo.FSTFieldInfo.SHORT:
             codec.writeFShort((short) subInfo.getShortValue(toWrite));
             break;
           case FSTClazzInfo.FSTFieldInfo.INT:
             codec.writeFInt(subInfo.getIntValue(toWrite));
             break;
           case FSTClazzInfo.FSTFieldInfo.LONG:
             codec.writeFLong(subInfo.getLongValue(toWrite));
             break;
           case FSTClazzInfo.FSTFieldInfo.FLOAT:
             codec.writeFFloat(subInfo.getFloatValue(toWrite));
             break;
           case FSTClazzInfo.FSTFieldInfo.DOUBLE:
             codec.writeFDouble(subInfo.getDoubleValue(toWrite));
             break;
         }
       } else if (subInfo.isConditional()) {
         final int conditional = codec.getWritten();
         codec.skip(4);
         // object
         Object subObject = subInfo.getObjectValue(toWrite);
         if (subObject == null) {
           codec.writeTag(NULL, null, 0, toWrite);
         } else {
           writeObjectWithContext(subInfo, subObject);
         }
         int v = codec.getWritten();
         codec.writeInt32At(conditional, v);
       } else {
         // object
         Object subObject = subInfo.getObjectValue(toWrite);
         if (subObject == null) {
           codec.writeTag(NULL, null, 0, toWrite);
         } else {
           writeObjectWithContext(subInfo, subObject);
         }
       }
     }
     codec.writeVersionTag((byte) 0);
   } catch (IllegalAccessException ex) {
     throw FSTUtil.rethrow(ex);
   }
 }
 @Override
 public void writeBoolean(boolean v) throws IOException {
   codec.writeFByte(v ? 1 : 0);
 }
  // splitting this slows down ...
  protected void writeObjectWithContext(FSTClazzInfo.FSTFieldInfo referencee, Object toWrite)
      throws IOException {
    int startPosition = codec.getWritten();
    boolean dontShare = objects.disabled;
    objectWillBeWritten(toWrite, startPosition);

    try {
      if (toWrite == null) {
        codec.writeTag(NULL, null, 0, toWrite);
        return;
      }
      final Class clazz = toWrite.getClass();
      if (clazz == String.class) {
        String[] oneOf = referencee.getOneOf();
        if (oneOf != null) {
          for (int i = 0; i < oneOf.length; i++) {
            String s = oneOf[i];
            if (s.equals(toWrite)) {
              codec.writeTag(ONE_OF, oneOf, i, toWrite);
              codec.writeFByte(i);
              return;
            }
          }
        }
        if (dontShare) {
          codec.writeTag(STRING, toWrite, 0, toWrite);
          codec.writeStringUTF((String) toWrite);
          return;
        }
      } else if (clazz == Integer.class) {
        codec.writeTag(BIG_INT, null, 0, toWrite);
        codec.writeFInt(((Integer) toWrite).intValue());
        return;
      } else if (clazz == Long.class) {
        codec.writeTag(BIG_LONG, null, 0, toWrite);
        codec.writeFLong(((Long) toWrite).longValue());
        return;
      } else if (clazz == Boolean.class) {
        codec.writeTag(
            ((Boolean) toWrite).booleanValue() ? BIG_BOOLEAN_TRUE : BIG_BOOLEAN_FALSE,
            null,
            0,
            toWrite);
        return;
      } else if ((referencee.getType() != null && referencee.getType().isEnum())
          || toWrite instanceof Enum) {
        if (!codec.writeTag(ENUM, toWrite, 0, toWrite)) {
          boolean isEnumClass = toWrite.getClass().isEnum();
          if (!isEnumClass) {
            // weird stuff ..
            Class c = toWrite.getClass();
            while (c != null && !c.isEnum()) {
              c = toWrite.getClass().getEnclosingClass();
            }
            if (c == null) {
              throw new RuntimeException("Can't handle this enum: " + toWrite.getClass());
            }
            codec.writeClass(c);
          } else {
            codec.writeClass(getFstClazzInfo(referencee, toWrite.getClass()));
          }
          codec.writeFInt(((Enum) toWrite).ordinal());
        }
        return;
      }

      FSTClazzInfo serializationInfo = getFstClazzInfo(referencee, clazz);
      // check for identical / equal objects
      FSTObjectSerializer ser = serializationInfo.getSer();
      if (!dontShare
          && !referencee.isFlat()
          && !serializationInfo.isFlat()
          && (ser == null || !ser.alwaysCopy())) {
        int handle =
            objects.registerObjectForWrite(toWrite, codec.getWritten(), serializationInfo, tmp);
        // determine class header
        if (handle >= 0) {
          final boolean isIdentical =
              tmp[0] == 0; // objects.getReadRegisteredObject(handle) == toWrite;
          if (isIdentical) {
            //                        System.out.println("POK writeHandle"+handle+"
            // "+toWrite.getClass().getName());
            if (!codec.writeTag(HANDLE, null, handle, toWrite)) codec.writeFInt(handle);
            return;
          }
        }
      }
      if (clazz.isArray()) {
        if (codec.writeTag(ARRAY, toWrite, 0, toWrite))
          return; // some codecs handle primitive arrays like an primitive type
        writeArray(referencee, toWrite);
      } else if (ser == null) {
        // default write object wihtout custom serializer
        // handle write replace
        if (!dontShare) {
          if (serializationInfo.getWriteReplaceMethod() != null) {
            Object replaced = null;
            try {
              replaced = serializationInfo.getWriteReplaceMethod().invoke(toWrite);
            } catch (Exception e) {
              throw FSTUtil.rethrow(e);
            }
            if (replaced != toWrite) {
              toWrite = replaced;
              serializationInfo = getClassInfoRegistry().getCLInfo(toWrite.getClass());
              // fixme: update object map ?
            }
          }
          // clazz uses some JDK special stuff (frequently slow)
          if (serializationInfo.useCompatibleMode() && !serializationInfo.isExternalizable()) {
            writeObjectCompatible(referencee, toWrite, serializationInfo);
            return;
          }
        }
        if (!writeObjectHeader(
            serializationInfo,
            referencee,
            toWrite)) { // skip in case codec can write object as primitive
          defaultWriteObject(toWrite, serializationInfo);
          if (serializationInfo.isExternalizable()) codec.externalEnd(serializationInfo);
        }
      } else { // object has custom serializer
        // Object header (nothing written till here)
        int pos = codec.getWritten();
        if (!writeObjectHeader(
            serializationInfo,
            referencee,
            toWrite)) { // skip in case code can write object as primitive
          // write object depending on type (custom, externalizable, serializable/java, default)
          ser.writeObject(this, toWrite, serializationInfo, referencee, pos);
          codec.externalEnd(serializationInfo);
        }
      }
    } finally {
      objectHasBeenWritten(toWrite, startPosition, codec.getWritten());
    }
  }
 /**
  * Flushes this data output stream. This forces any buffered output bytes to be written out to the
  * stream.
  *
  * <p>The <code>flush</code> method of <code>DataOutputStream</code> calls the <code>flush</code>
  * method of its underlying output stream.
  *
  * @throws java.io.IOException if an I/O error occurs.
  * @see java.io.FilterOutputStream#out
  * @see java.io.OutputStream#flush()
  */
 @Override
 public void flush() throws IOException {
   codec.flush();
   resetAndClearRefs();
 }
 @Override
 public void writeUTF(String s) throws IOException {
   codec.writeStringUTF(s);
 }
 void resetAndClearRefs() {
   codec.reset();
   objects.clearForWrite();
 }
 public void resetForReUse(byte[] out) {
   if (closed) throw new RuntimeException("Can't reuse closed stream");
   codec.reset();
   codec.reset(out);
   objects.clearForWrite();
 }
 @Override
 public void writeByte(int v) throws IOException {
   codec.writeFByte(v);
 }
 @Override
 public void writeLong(long v) throws IOException {
   codec.writeFLong(v);
 }
 @Override
 public void writeInt(int v) throws IOException {
   codec.writeFInt(v);
 }
 @Override
 public void writeChar(int v) throws IOException {
   codec.writeFChar((char) v);
 }
 @Override
 public void writeShort(int v) throws IOException {
   codec.writeFShort((short) v);
 }
 public void writeStringUTF(String str) throws IOException {
   codec.writeStringUTF(str);
 }
 @Override
 public void writeDouble(double v) throws IOException {
   codec.writeFDouble(v);
 }
 @Override
 public void writeBytes(String s) throws IOException {
   byte[] bytes = s.getBytes();
   codec.writePrimitiveArray(bytes, 0, bytes.length);
 }
 @Override
 public void writeChars(String s) throws IOException {
   char[] chars = s.toCharArray();
   codec.writePrimitiveArray(chars, 0, chars.length);
 }
 /**
  * serialize without an underlying stream, the resulting byte array of writing to this
  * FSTObjectOutput can be accessed using getBuffer(), the size using getWritten().
  *
  * <p>Don't create a FSTConfiguration with each stream, just create one global static
  * configuration and reuse it. FSTConfiguration is threadsafe.
  *
  * @param conf
  * @throws IOException
  */
 public FSTObjectOutput(FSTConfiguration conf) {
   this(null, conf);
   codec.setOutstream(null);
 }
 /**
  * serialize without an underlying stream, the resulting byte array of writing to this
  * FSTObjectOutput can be accessed using getBuffer(), the size using getWritten(). Note once you
  * call close or flush, the tmp byte array is lost. (grab array before flushing/closing)
  *
  * <p>uses default configuration singleton
  *
  * @throws IOException
  */
 public FSTObjectOutput() {
   this(null, FSTConfiguration.getDefaultConfiguration());
   codec.setOutstream(null);
 }