问题
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