必要な要素をシリアル化するときに「空の要素タグ」を回避する正しい方法は何ですか?
例:
@ElementList(name="rpc", required=true)
public ArrayList<FunctionRequest> getRequestedFunctions() {
return requestedFunctions;
}
空のリストは、参照される「空の要素タグ」スタイルの単一のXML要素になります。
<rpc/>
私が実際に必要としているのは、次の表現です。
<rpc></rpc>
編集:さまざまなタイプのリストのソリューションが必要です!たとえば、例では「rpc」である注釈の、、、、...PLUS異なる名前属性List<String>
もList<int>
存在する可能性があります。List<WhateverClass>
ご協力いただきありがとうございます。
ソリューション
最初に、実際に実装し、プロジェクトで使用しているソリューションを紹介します。その後、Simpleコードに直接追加できる変更の提案を提示します。
私が実際に使用しているソリューション:
XML要素を手動で定義して、プログラムにあるリストごとにConverterを作成したくなかったので、クラスの既存のアノテーションと、アノテーションの名前属性を使用することにしました。私のソリューションは、ゲッターとセッターを使用する場合にシリアル化されるクラスで機能します。現在、アノテーションAttribute
とText
アノテーションのみを使用するクラスにバインドされています。
より柔軟にするために、「基本」クラスを作成しましたRequestListConverter
。2つのprotected
方法prepareMethodList
とwriteRequest
。prepareMethodList
リフレクションを使用して特定のクラスのすべてのメソッドを調べ、method-annotation-mapを作成します。writeRequest
次に、メソッドに指定されたタイプの単一のオブジェクトを、インターフェイスのメソッドでprepareMethodList
指定されたSimpleのOutputNodeに書き込みます。write
Converter
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();
}
}
このクラスに基づいて、たとえば、クラスを作成しましたSetRequestListConverter
。SimpleのConverter
インターフェースを実装しているため、read
実装されていないメソッドとwrite
、要素を含む可能性のあるリストまたは空の可能性のあるリストを取得するメソッドを提供します。
この例は、クラスのConverterの実装を示していますSetRequestList
。以前に導入された基本RequestConverter
クラスを拡張し、Converter
型付きをに実装し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);
}
}
}
使用されるのSetRequestList
は、を保持する単純なクラスですArrayList<SetRequest>
。これは、これが実際にはArrayListであるという事実を隠すために必要です。
@Root
@Convert(SetRequestListConverter.class)
public abstract class SetRequestList {
protected ArrayList<SetRequest> requests = new ArrayList<SetRequest>();
public void add(T newRequest) {
requests.add(newRequest);
}
}
このクラスは、次のように使用できます。
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;
}
}
要素を含む生成されたXMLは次のSetRequestList
ようになります。
<get>
<set someAttribute="text" anotherAttribute="bla">Some Text</set>
...
</get>
SetRequestList
空の生成されたXMLは次のようになります。
<get></get>
ええ、まさに私が必要としていたものに加えて、他のSetRequest
クラスやその他のクラスで注釈を使用できるという事実もあります。XML構造を再度(再)定義する必要はありません!
Simpleのコード提案
注意:これは解決策の提案であり、テストされていません。
Simpleのソースコードを調べたところ、Formatter
クラスが実際に開始タグと終了タグ、および空の要素タグを書き込んでいることがわかりました。オブジェクトを渡すことで作成されFormat
ます。SimpleのJavadocは、Format
クラスを次のように説明しています。
Formatオブジェクトは、生成されたXMLドキュメントの構造に関する情報を提供するために使用されます。
したがって、空の要素タグを作成する必要があるかどうかについての情報で拡張できます。このために、プライベート変数useEmptyEndTag
と適切なgetterメソッドとsetterメソッドを追加しました。変数はtrue
コンストラクター内で初期化されます。Format
空の終了タグスタイルが作成すべきものでない場合は、オブジェクトの作成後にを使用して設定できますmyFormat.setUseEmptyEndTag(false)
。
クラスは、適切なコード位置で設定されたパラメーターにアクセスできるように、指定されたオブジェクトをFormatter
保持する新しいプライベート変数によって拡張されます。Format
空の終了タグは。内に書き込まれwriteEnd
ます。公式のソースコードを見て、元のコードを確認してください。空の要素タグを避けるための私の提案は次のとおりです。
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;
}
この提案は、私の目にはより良い解決策であり、Simpleのソースコードに追加されることを願っています。