Search code examples
javaxmlserializationsimple-framework

Howto extend org.simpleframework.xml with an annotation that will write comments into the resulting XML


I am using org.simpleframework.xml (http://simple.sourceforge.net/) to serialize Java Objects to XML.

What I would like to add is to add a comments area in the resulting XML, based on Annotations in the Java object.

So for example I would like to write some Java Object like:

@Root(name = "myclass")
public class MyClass {
  @Element(required=true)
  @Version(revision=1.1)
  @Comment(text=This Element is new since, version 1.1, it is a MD5 encrypted value)
  private String activateHash;
}

And the resulting xml would look like:

<myclass version="1.1">
  <!-- This Element is new since, version 1.1, it is a MD5 encrypted value -->
  <activateHash>129831923131s3jjs3s3jjk93jk1</activateHash>
</myclass>

There is an example in their docs on howto write a Visitor that will write a comments in the xml: http://simple.sourceforge.net/download/stream/doc/tutorial/tutorial.php#intercept

However: How can I attach a Visitor to a Strategy at all?

And further the Visitor concept of simpleframework does not allow access to the raw parsing class. In the Visitor there is only a method to overwrite:

public void write(Type type, NodeMap<OutputNode> node) { ... }

=> OutputNode does not give me a chance to read the Annotation of the Element that I am parsing. So how should one access the Annotations of the attribute.

Thanks!

Sebastian


Solution

  • Update as of 2012-11-05:

    Answer by the author of org.simpleframework.xml: This works

    https://simple.svn.sourceforge.net/svnroot/simple/trunk/download/stream/src/test/java/org/simpleframework/xml/strategy/CommentTest.java

    package org.simpleframework.xml.strategy;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    import org.simpleframework.xml.Default;
    import org.simpleframework.xml.Root;
    import org.simpleframework.xml.ValidationTestCase;
    import org.simpleframework.xml.core.Persister;
    import org.simpleframework.xml.stream.InputNode;
    import org.simpleframework.xml.stream.NodeMap;
    import org.simpleframework.xml.stream.OutputNode;
    
    public class CommentTest extends ValidationTestCase {
    
       @Retention(RetentionPolicy.RUNTIME)
       private static @interface Comment {
          public String value();
       }
    
       @Root
       @Default
       private static class CommentExample {
          @Comment("This represents the name value")
          private String name;
          @Comment("This is a value to be used")
          private String value;
          @Comment("Yet another comment")
          private Double price;
       }
    
       private static class CommentVisitor implements Visitor {
          public void read(Type type, NodeMap<InputNode> node) throws Exception {}
          public void write(Type type, NodeMap<OutputNode> node) throws Exception {
             if(!node.getNode().isRoot()) {
                Comment comment = type.getAnnotation(Comment.class);
                if(comment != null) {
                   node.getNode().setComment(comment.value());
                }
             }
          }
       }
    
       public void testComment() throws Exception {
          Visitor visitor = new CommentVisitor();
          Strategy strategy = new VisitorStrategy(visitor);
          Persister persister = new Persister(strategy);
          CommentExample example = new CommentExample();
    
          example.name = "Some Name";
          example.value = "A value to use";
          example.price = 9.99;
    
          persister.write(example, System.out);
       }
    
    }
    

    Update as of 2012-11-01 20:16

    this is the workaround that seems to get the desired effect - the necessary FieldHelper is described in (Get the value of a field, given the hierarchical path)

        /**
         * write according to this visitor
         */
        public void write(Type type, NodeMap<OutputNode> node) {
            OutputNode element = node.getNode();
            Class ctype = type.getType();
    
            String comment = ctype.getName();
            if (!element.isRoot()) {
                FieldHelper fh = new FieldHelper();
                element.setComment(comment);
                try {
                    if (type.getClass().getSimpleName().startsWith("Override")) {
                        type = (Type) fh.getFieldValue(type, "type");
                    }
                    if (type.getClass().getSimpleName().startsWith("Field")) {
                        Field field = (Field) fh.getFieldValue(type, "field");
                        System.out.println(field.getName());
                        Comment commentAnnotation = field.getAnnotation(Comment.class);
                        if (commentAnnotation != null) {
                            element.setComment(commentAnnotation.value());
                        }
                    }
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    

    Here is how far I got with this. Unfortunately it does not work as expected. I have written an E-Mail to the author of the Simpleframwork for XML.

      /**
      * write according to this visitor
      */
        public void write(Type type, NodeMap<OutputNode> node) {
            OutputNode element = node.getNode();
            Class ctype = type.getType();
    
            String comment = ctype.getName();
            if (!element.isRoot()) {
                Comment commentAnnotation = type.getAnnotation(Comment.class);
                if (commentAnnotation!=null)
                    element.setComment(commentAnnotation.value());
                else
                    element.setComment(comment);
            }
        }
    
        @Override
        public void read(Type type, NodeMap<InputNode> nodeMap) throws Exception {
    
        }
    
    }
    

    I declared the Comment annotation like this:

    package com.bitplan.storage.simplexml;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.lang.annotation.ElementType;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Comment {
    String value();
    }
    

    which is then usable like this:

    @Comment("this is the unique identifier")
    private long id;
    

    adding the Visitor was possible like this:

    /**
     * get Serializer
     * 
     * @return
     */
    public Serializer getSerializer() {
        Serializer serializer = null;
        Strategy strategy=null;
        VisitorStrategy vstrategy=null;
        if ((idname != null) && (refname != null)) {
            strategy = new CycleStrategy(idname, refname);
        }
        CommentVisitor cv=new CommentVisitor();
        if (strategy==null) {
            vstrategy=new VisitorStrategy(cv);
        } else {
            vstrategy=new VisitorStrategy(cv,strategy);
        }       
        serializer = new Persister(vstrategy);
        return serializer;
    }