protected void doMarshal(
      final Object source,
      final HierarchicalStreamWriter writer,
      final MarshallingContext context) {
    final Set seenFields = new HashSet();
    final Set seenAsAttributes = new HashSet();

    // Attributes might be preferred to child elements ...
    reflectionProvider.visitSerializableFields(
        source,
        new ReflectionProvider.Visitor() {
          public void visit(String fieldName, Class type, Class definedIn, Object value) {
            SingleValueConverter converter =
                mapper.getConverterFromItemType(fieldName, type, definedIn);
            if (converter == null) converter = mapper.getConverterFromItemType(fieldName, type);
            if (converter == null) converter = mapper.getConverterFromItemType(type);
            if (converter != null) {
              if (value != null) {
                final String str = converter.toString(value);
                if (str != null) {
                  writer.addAttribute(mapper.aliasForAttribute(fieldName), str);
                }
              }
              seenAsAttributes.add(fieldName);
            }
          }
        });

    // Child elements not covered already processed as attributes ...
    reflectionProvider.visitSerializableFields(
        source,
        new ReflectionProvider.Visitor() {
          public void visit(String fieldName, Class fieldType, Class definedIn, Object newObj) {
            if (!seenAsAttributes.contains(fieldName) && newObj != null) {
              Mapper.ImplicitCollectionMapping mapping =
                  mapper.getImplicitCollectionDefForFieldName(source.getClass(), fieldName);
              if (mapping != null) {
                if (mapping.getItemFieldName() != null) {
                  Collection list = (Collection) newObj;
                  for (Iterator iter = list.iterator(); iter.hasNext(); ) {
                    Object obj = iter.next();
                    writeField(
                        fieldName,
                        mapping.getItemFieldName(),
                        mapping.getItemType(),
                        definedIn,
                        obj);
                  }
                } else {
                  context.convertAnother(newObj);
                }
              } else {
                writeField(fieldName, fieldName, fieldType, definedIn, newObj);
                seenFields.add(fieldName);
              }
            }
          }

          private void writeField(
              String fieldName, String aliasName, Class fieldType, Class definedIn, Object newObj) {
            try {
              if (!mapper.shouldSerializeMember(definedIn, aliasName)) {
                return;
              }
              ExtendedHierarchicalStreamWriterHelper.startNode(
                  writer, mapper.serializedMember(definedIn, aliasName), fieldType);

              Class actualType = newObj.getClass();

              Class defaultType = mapper.defaultImplementationOf(fieldType);
              if (!actualType.equals(defaultType)) {
                String serializedClassName = mapper.serializedClass(actualType);
                if (!serializedClassName.equals(mapper.serializedClass(defaultType))) {
                  writer.addAttribute(mapper.aliasForSystemAttribute("class"), serializedClassName);
                }
              }

              if (seenFields.contains(aliasName)) {
                writer.addAttribute(
                    mapper.aliasForAttribute("defined-in"), mapper.serializedClass(definedIn));
              }

              Field field = reflectionProvider.getField(definedIn, fieldName);
              marshallField(context, newObj, field);
              writer.endNode();
            } catch (RuntimeException e) {
              // intercept an exception so that the stack trace shows how we end up marshalling the
              // object in question
              throw new RuntimeException(
                  "Failed to serialize "
                      + definedIn.getName()
                      + "#"
                      + fieldName
                      + " for "
                      + source.getClass(),
                  e);
            }
          }
        });
  }