Avoid Empty Element Tag in Simple Framework output

岁酱吖の 提交于 2020-01-05 08:58:46

问题


What is the correct way to avoid "empty element tags" when serializing required elements?

Example:

@ElementList(name="rpc", required=true)
public ArrayList<FunctionRequest> getRequestedFunctions() {
    return requestedFunctions;
}

An empty list will result in a single XML element in the referred "empty element tag" style:

<rpc/>

What I actually need is the following representation:

<rpc></rpc>

Edit: I need a solution for Lists of different types! For example, there might also be List<String>, List<int>, List<WhateverClass>, ... PLUS different name attributes of the annotation which is "rpc" in the example.

Thanks for your help.

Solutions

I will first present the solution I've actually implemented and that I'm using in my project. After that I will present a proposal of changes which could be added directly to the Simple code.

My solution I actually use:

Since I did not want to define the XML elements by hand and create a Converter for each List I've got in my program, I decided to use the existing annotations of my classes, as well as the name attributes of the annotations. My solution will work for classes that shall be serialized if they use getters and setters! It is currently bound to classes that use the Attribute and Text annotations, only!

To be more flexible I created a "base" class RequestListConverter. It has two protected methods prepareMethodList and writeRequest. prepareMethodList will go through all methods of a given class using reflection and creates a method-annotation-map. writeRequest will then write a single object of the type given to method prepareMethodList to the OutputNode of Simple that is given in the write method of the Converter interface.

public class RequestListConverter {
    private HashMap<Method, Object> curMethodAnnotationMap = new HashMap<Method, Object>(); 

    @SuppressWarnings("rawtypes")
    protected void prepareMethodList(Class targetClass) {
        /*
         * First, get the annotation information from the given class.
         * 
         * Since we use getters and setters, look for the "get" methods!
         */

        Method[] curMethods = targetClass.getMethods();
        for (Method curMethod : curMethods) {
            String curName = curMethod.getName();

            // We only want getter methods that return a String
            if (curName.startsWith("get") && (curMethod.getReturnType() == String.class)) {
                Attribute curAttrAnnotation = curMethod.getAnnotation(Attribute.class);
                Text curTextAnnotation = curMethod.getAnnotation(Text.class);

                if (curAttrAnnotation != null) {
                    curMethodAnnotationMap.put(curMethod, curAttrAnnotation);
                } else
                if (curTextAnnotation != null) {
                    curMethodAnnotationMap.put(curMethod, curTextAnnotation);
                }
            }
        }
    }

    protected void writeRequest(OutputNode curNode, Object curRequest) throws Exception {
        for (Map.Entry<Method, Object> curMapEntry : curMethodAnnotationMap
                .entrySet()) {
            if ((curMapEntry.getKey() == null)
                    || (curMapEntry.getValue() == null)) {
                continue;
            }

            Method curMethod = curMapEntry.getKey();

            Attribute curAttrAnnotation = null;
            Text curTextAnnotation = null;

            if (curMapEntry.getValue() instanceof Attribute) {
                curAttrAnnotation = (Attribute) curMapEntry.getValue();
            } else if (curMapEntry.getValue() instanceof Text) {
                curTextAnnotation = (Text) curMapEntry.getValue();
            } else {
                continue;
            }

            String curValue = null;
            try {
                // Try to invoke the getter
                curValue = (String) curMethod.invoke(curRequest);
            } catch (IllegalAccessException | IllegalArgumentException
                    | InvocationTargetException e) {

                // The getter method seems to need any argument, strange! Skip
                // this!
                continue;
            }

            // If the method has an Attribute annotation, then ...
            if (curAttrAnnotation != null) {
                boolean curAttrRequired = curAttrAnnotation.required();
                String curAttrName = curAttrAnnotation.name();

                /*
                 * IF the returned method value is NULL THEN IF if the attribute
                 * is required THEN throw a NullPointerException, ELSE skip the
                 * attribute
                 */
                if (curValue == null) {
                    if (curAttrRequired) {
                        throw new NullPointerException(
                                "The required attribute " + curAttrName
                                        + " returned NULL!");
                    } else {
                        continue;
                    }
                }

                // The attribute will be added as XML text now
                curNode.setAttribute(curAttrName, curValue);
            } else
            // If the method has a Text annotation, then ...
            if (curTextAnnotation != null) {
                // we only need to store it for later string creation
                curNode.setValue(curValue);
            }
        }

        curNode.commit();
    }
}

Based upon this class, I created - for example - the class SetRequestListConverter. It implements Simple's Converter interface, so it provides the methods read which is not implemented and write which gets the List that might have elements or might be empty.

The example shows the implementation of a Converter of the class SetRequestList. It extends the previously introduced base RequestConverter class and implements the Converter typed to the SetRequestList.

public class SetRequestListConverter extends RequestListConverter implements Converter<SetRequestList> {

    @Override
    public SetRequestList read(InputNode newNode) throws Exception {
        return null;
    }

    @Override
    public void write(OutputNode newNode, SetRequestList newValue) throws Exception {
        if (newValue.requests.isEmpty()) {
            newNode.setValue("");
            return;
        }

        this.prepareMethodList(SetRequest.class);

        /*
         * Now we can go through all SetRequests and call the methods
         * to build the XML attributes and to get the element value (i.e. parameters) 
         */

        for (SetRequest curRequest : newValue.requests) {
            OutputNode curNode = newNode.getChild("set");

            this.writeRequest(curNode, curRequest);
        }
    }
}

The used SetRequestList is a simple class that holds an ArrayList<SetRequest>. This is needed to hide the fact that this actually is an ArrayList.

@Root
@Convert(SetRequestListConverter.class)
public abstract class SetRequestList {
    protected ArrayList<SetRequest> requests = new ArrayList<SetRequest>();

    public void add(T newRequest) {
        requests.add(newRequest);
    }
}

This class can then be used like this:

public class ClassToSerialize {
    private SetRequestList requestedSets = new SetRequestList();

    @Element(name="get", required=true)
    public SetRequestList getRequestedSets() {
        return requestedSets;
    }

    @Element(name="get", required=true)
    public void setRequestedSets(SetRequestList newRequestedSets) {
        requestedSets = newRequestedSets;
    }
}

The generated XML of SetRequestList containing elements will look like that:

<get>
    <set someAttribute="text" anotherAttribute="bla">Some Text</set>
    ...
</get>

The generated XML of SetRequestList being empty will look like that:

<get></get>

Yay, exactly what I needed PLUS the fact that I can go on and use annotations in the SetRequest or whatever classes! No need to do (re)define the XML structure again!

Code proposal for Simple

Attention: This only is a solution proposal and has not been tested!

I looked through the source code of Simple and found that the Formatter class is actually writing start and end tags, as well as the empty element tag. It is created by handing over a Format object. Simple's Javadoc describes the Format class as follows:

The Format object is used to provide information on how a generated XML document should be structured.

So it could be extended with the information if empty element tags should be created or not. For this I added the private variable useEmptyEndTag and the appropriate getter and setter methods. The variable will be initialized with true inside the constructor. If the empty end tag style is not what should be created, you can set it after creating a Format object by using myFormat.setUseEmptyEndTag(false).

The Formatter class is enhanced by a new private variable holding the given Format object to be able to access the set parameters at the appropriate code location. The empty end tag is written inside writeEnd. Have look at the official source code to see the original code. Here is what my proposal is to avoid empty element tags:

public void writeEnd(String name, String prefix) throws Exception {
    String text = indenter.pop();

    // This will act like the element contains text
    if ((last == Tag.START) && (!format.isUseEmptyEndTag())) {
        write('>');
        last = Tag.TEXT;
    }

    if (last == Tag.START) {
        write('/');
        write('>');
    } else {
        if (last != Tag.TEXT) {
            write(text);
        }
        if (last != Tag.START) {
            write('<');
            write('/');
            write(name, prefix);
            write('>');
        }
    }
    last = Tag.END;
}

This proposal is the better solution - in my eyes - and I hope it will be added to the Simple's sourcecode.


回答1:


As baraky has written before, you can solve the part with the <rpc></rpc> tags with a Converter like posted here: Prevent inclusion of empty tag for an empty ElementList in an ElementListUnion.
There's a comment on the ExampleConverter where this special part is done.

Moreover to get the name attribute see here: How do you access field annotations from a custom Converter with Simple?

So what you need is a Converter-implementation for your class. For types (int, String etc.) check out Transformer-class.



来源:https://stackoverflow.com/questions/14955902/avoid-empty-element-tag-in-simple-framework-output

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!