protected void writeHeaders(Object obj, OutputStream stream) throws IOException {
    while (obj instanceof Iterable) {
      Iterator itr = ((Iterable) obj).iterator();
      if (!itr.hasNext()) {
        return;
      }
      obj = itr.next();
    }

    while (obj != null && obj.getClass().isArray()) {
      if (Array.getLength(obj) == 0) {
        return;
      }
      obj = Array.get(obj, 0);
    }

    if (obj == null) {
      return;
    }

    Class objClass = obj.getClass();

    final PojoAttributeMapping attributeMapping = _config.getAttributeMapping(objClass);
    if (attributeMapping != null) {
      if (_config.isWriteHeaders()) {
        writeHeaders(stream, attributeMapping, "");
        writeRaw(stream, NEWLINE);
      }

      prepareGetters(objClass, _config.getAttributeMapping(objClass));
    }
  }
  public void serialize(Object obj, OutputStream stream) throws IOException {
    if (_config.isWriteHeaders()) {
      writeHeaders(obj, stream);
    }

    if (obj != null) {
      final PojoAttributeMapping pojoAttributeMapping = _config.getAttributeMapping(obj.getClass());

      writeContent(obj, stream, true, pojoAttributeMapping, true);
    }
  }
  /**
   * Serializes the given object into the stream based on the {@link PojoAttributeMapping}
   * configuration.
   *
   * @param obj object to be serialized into the stream
   * @param stream the stream to be used to output the serialized object
   * @param processCollections if elements of the collection/array should produce a new row or not
   * @param pojoAttributeMapping attribute mapping (serialization instruction), if any, to be used
   *     to serialize the object
   * @param needQuotes if surround serialized text with the quotes or not
   * @throws IOException
   */
  protected void writeContent(
      Object obj,
      OutputStream stream,
      boolean processCollections,
      PojoAttributeMapping pojoAttributeMapping,
      boolean needQuotes)
      throws IOException {
    if (obj == null) {
      // don't write any value - keep it empty but do write separators
      // if value that is missing corresponds to multiple columns
      List<PojoCsvAttribute> pojoAttributes = pojoAttributeMapping.getPojoAttributes();
      if (pojoAttributes != null && !pojoAttributes.isEmpty()) {
        for (int i = pojoAttributes.size() - 1;
            i > 0;
            i--) { // write one separator less than the size
          writeRaw(stream, VALUE_SEPARATOR);
        }
      }
      return;
    }

    final Class<?> objClass = obj.getClass();

    if (objClass.isArray()) {
      obj = new IterableArray(obj);
    }

    if (obj instanceof Iterable) {
      if (!processCollections) {
        if (needQuotes) {
          writeRaw(stream, QUOTE);
        }
        String div = "";
        for (Object item : (Iterable) obj) {
          writeRaw(stream, div);
          final PojoAttributeMapping mapping =
              (pojoAttributeMapping == null
                  ? _config.getAttributeMapping(item.getClass())
                  : pojoAttributeMapping);
          writeContent(item, stream, false, mapping, false);
          div =
              SPACED_VALUE_SEPARATOR; // otherwise things like "9780073371856,9780077474034" will be
          // split into 2 columns, even comma is inside the quotes
        }
        if (needQuotes) {
          writeRaw(stream, QUOTE);
        }
      } else {
        for (Object item : (Iterable) obj) {
          writeContent(item, stream, false, _config.getAttributeMapping(item.getClass()), true);
          writeRaw(stream, NEWLINE);
        }
      }
    } else if (obj instanceof Boolean || obj instanceof Number || obj instanceof String) {
      if (needQuotes) {
        if (obj instanceof String) {
          writeQuoted(stream, obj);
        } else {
          write(stream, obj);
        }
      } else {
        writeRaw(stream, obj);
      }
    } else {
      final boolean needToString =
          (pojoAttributeMapping == null || _config.needsToString(objClass));
      if (needToString) {
        // write as toString - it's an object but according to config it should be processed as a
        // String
        if (needQuotes) {
          writeQuoted(stream, obj);
        } else {
          writeRaw(stream, obj);
        }
      } else {
        String div = "";

        for (PojoCsvAttribute pojoCsvAttribute : pojoAttributeMapping.getPojoAttributes()) {
          Object[] getters = pojoCsvAttribute.getGetter();

          if (getters
              == null) { // this is probably element of the collection - getter are not prepared for
            // those yet
            prepareGetters(obj.getClass(), pojoAttributeMapping);
            getters = pojoCsvAttribute.getGetter();
          }

          try {
            writeRaw(stream, div);
            boolean needQuotesCopy = needQuotes;
            if (getters.length > 1) {
              needQuotes = false;
              writeRaw(stream, QUOTE);
            }
            for (Object getter : getters) {
              Object value = (getter instanceof Method) ? ((Method) getter).invoke(obj) : getter;
              PojoAttributeMapping attributeMapping = pojoCsvAttribute.getAttributeMapping();
              writeContent(value, stream, false, attributeMapping, needQuotes);
            }
            if (getters.length > 1) {
              writeRaw(stream, QUOTE);
            }

            needQuotes = needQuotesCopy;

            div = VALUE_SEPARATOR;
          } catch (IllegalAccessException e) {
            //                            logger.error("Exception", e);
          } catch (InvocationTargetException e) {
            //                            logger.error("Exception", e);
          }
        }
      }
    }
  }