1

序列化所需元素时避免“空元素标签”的正确方法是什么?

例子:

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

一个空列表将产生一个引用的“空元素标记”样式中的单个 XML 元素:

<rpc/>

我真正需要的是以下表示:

<rpc></rpc>

编辑:我需要不同类型列表的解决方案!例如,可能还有List<String>, List<int>, List<WhateverClass>, ... 加上注释的不同名称属性,在示例中为“rpc”。

谢谢你的帮助。

解决方案

我将首先介绍我实际实施的解决方案以及我在项目中使用的解决方案。之后,我将提出一个可以直接添加到简单代码中的更改建议。

我实际使用的解决方案:

由于我不想手动定义 XML 元素并为程序中的每个 List 创建一个转换器,因此我决定使用我的类的现有注释以及注释的名称属性。如果他们使用 getter 和 setter,我的解决方案将适用于将被序列化的类!它目前只绑定到使用AttributeText注释的类!

为了更灵活,我创建了一个“基础”类RequestListConverter。它有两种protected方法prepareMethodListwriteRequest. prepareMethodList将使用反射遍历给定类的所有方法并创建一个方法注释映射。writeRequest然后将给方法类型的单个对象写入接口方法中prepareMethodList给出的 Simple 的 OutputNode 。writeConverter

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获取了可能有元素或可能为空的 List。

该示例显示了类 Converter 的实现SetRequestList。它扩展了之前介绍的基RequestConverter类并实现ConverterSetRequestList.

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);
        }
    }
}

usedSetRequestList是一个简单的类,它包含一个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;
    }
}

生成的SetRequestList包含元素的 XML 如下所示:

<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 的源代码中。

4

1 回答 1

0

正如 baraky 之前所写,您可以<rpc></rpc>使用带有 Converter 的标签解决部分问题,如下所示:防止在 ElementListUnion 中包含空 ElementList 的空标签。关于这个特殊部分的完成位置
有一个评论。ExampleConverter

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.

于 2013-02-19T13:00:24.997 回答