避免在Simple Framework输出中使用空元素标记

时间:2013-02-19 11:05:41

标签: java simple-framework

序列化所需元素时,避免"empty element tags"的正确方法是什么?

示例:

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

空列表将导致引用的&#34;空元素标记中的单个XML元素&#34;式:

<rpc/>

我实际需要的是以下表示:

<rpc></rpc>

编辑:我需要一个不同类型列表的解决方案!例如,可能还有List<String>List<int>List<WhateverClass>,... PLUS注释的不同名称属性,即&#34; rpc&#34;在示例中。

感谢您的帮助。

解决方案

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

我实际使用的解决方案:

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

为了更灵活,我创建了一个&#34; base&#34;班RequestListConverter。它有两个protected方法prepareMethodListwriteRequestprepareMethodList将使用反射遍历给定类的所有方法,并创建方法 - 注释 - 映射。然后,writeRequest会将给定方法prepareMethodList的类型的单个对象写入到write接口的Converter方法中给出的Simple的OutputNode。

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接口,因此它提供了未实现的方法readwrite,它获取了可能包含元素或可能为空的List。

该示例显示了类SetRequestList的Converter的实现。它扩展了先前引入的基类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;
    }
}

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

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

生成的SetRequestList为空的XML将如下所示:

<get></get>

是的,正是我所需要的事实,我可以继续使用SetRequest或任何类别的注释!无需再次(重新)定义XML结构!

简单

的代码提案

注意:这只是一个解决方案提案,尚未经过测试!

我查看了Simple的源代码,发现Formatter类实际上是在编写开始和结束标记,以及空元素标记。它是通过移交Format对象创建的。简单的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的源代码中。

1 个答案:

答案 0 :(得分:0)

正如baraky之前所写的那样,您可以使用<rpc></rpc>标签解决部件,并在此处发布转换器:Prevent inclusion of empty tag for an empty ElementList in an ElementListUnion
ExampleConverter对此特殊部分进行了评论。

此外,要获取name属性,请参阅此处:How do you access field annotations from a custom Converter with Simple?

所以你需要的是Converter - 你班级的实施。对于类型(intString等),请查看Transformer - 类。