protected void writeHeaders(
     OutputStream stream, PojoAttributeMapping attributeMapping, String div) throws IOException {
   for (PojoCsvAttribute pojoCsvAttribute : attributeMapping.getPojoAttributes()) {
     final PojoAttributeMapping childAttributeMapping = pojoCsvAttribute.getAttributeMapping();
     final String csvHeaderName = pojoCsvAttribute.getCsvHeaderName();
     if (csvHeaderName != null) {
       writeRaw(stream, div);
       writeQuoted(stream, csvHeaderName);
     } else {
       writeHeaders(stream, childAttributeMapping, div);
     }
     div = VALUE_SEPARATOR;
   }
 }
  protected void prepareGetters(
      Class objectClass, final PojoAttributeMapping pojoAttributeMapping) {
    // skip collections - methods are for the elements of the collection, not the collections,
    // but we don't know the class of the elements in the collection - will figure getters later,
    // when actually
    // processing the elements
    if (objectClass.getComponentType() != null || Collection.class.isAssignableFrom(objectClass)) {
      return;
    }

    for (PojoCsvAttribute pojoCsvAttribute : pojoAttributeMapping.getPojoAttributes()) {
      String[] attributeNames = pojoCsvAttribute.getAttributeName();
      LinkedList<Object> getters = new LinkedList<Object>();
      for (String attributeName : attributeNames) {
        attributeName =
            attributeName.substring(0, 1).toUpperCase()
                + (attributeName.length() == 1 ? "" : attributeName.substring(1));

        Object getter;
        try {
          getter = objectClass.getMethod("get" + attributeName);
        } catch (NoSuchMethodException e) {
          try {
            getter = objectClass.getMethod("is" + attributeName);
          } catch (NoSuchMethodException ex) {
            // throw new IllegalArgumentException("No getter (is/get) found for " + attributeName +
            // " on " + objectClass.getSimpleName());
            getter = attributeName;
          }
        }

        getters.add(getter);

        final PojoAttributeMapping childAttributeMapping = pojoCsvAttribute.getAttributeMapping();
        if ((getter instanceof Method) && childAttributeMapping != null) {
          prepareGetters(((Method) getter).getReturnType(), childAttributeMapping);
        }
      }

      Object[] getterAr = new Object[getters.size()];
      pojoCsvAttribute.setGetter(getters.toArray(getterAr));
    }
  }
  /**
   * 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);
          }
        }
      }
    }
  }