如何使用commons-digester调用带弹出对象的方法?

时间:2017-11-24 14:57:35

标签: java xml-parsing apache-commons-digester

我有一个XML文档,如下所示:

<!-- language: xml -->
<items>
  <item type="java.lang.Boolean" name="foo" value="true" />
</items>

我希望<root>元素创建一个java.util.Map对象,让每个<item>元素创建一个相应类型的对象,然后在{{{{}}中添加一个条目1}} - 类似于Map,但带有来自堆栈的调用的参数。

我已经创建了一个自定义SetNextRule,它将使用{中的值}创建Rule属性(在本例中为type)中指定类型的对象{1}}属性并将其推入堆栈。

现在,我想将项目从堆栈顶部弹出,并将其用作java.lang.Boolean对象上value方法的参数(仅仅是&#34) ;&#34;堆栈上的put对象。

这是我到目前为止编写的代码:

Map

我收到的错误是Boolean类中无法找到方法<!-- language: lang-java --> Digester digester = new Digester(); digester.addObjectCreate("items", HashMap.class); digester.addRule(new MyObjectCreateRule()); // This knows how to create e.g. java.lang.Boolean objects digester.addCallMethod("items/item", "put", 2, new Class<?>[] { String.class, Object.class }); digester.addCallParam("items/item", 0, "name"); digester.addCallParam("items/item", 1, true); // take argument from stack 。所以,问题是例如put对象位于堆栈的顶部,我想将它用作在堆栈上的next-to-top元素上调用的java.lang.Boolean方法的参数:

堆栈:

Boolean

有没有办法用现有的commons-digester规则执行此操作,还是必须创建另一个执行此类操作的自定义规则?

2 个答案:

答案 0 :(得分:0)

我最终编写了一个结合了这两个操作的自定义规则:构建属性值的新实例,并将插入到属性包中。

这是对我所拥有的真实用例的改编,所以代码可能不是100%完美,因为我在这里复制/粘贴和改编它。我也明白使用java.lang.String以外的属性值并没有多大意义,但它确实适用于我的用例(实际上并没有使用java.util.Properties,但这个类是一个很好的类比)。

<!-- language: lang-java -->
/**
 * Implements a create-object-set-property Digester rule.
 */
public class SetPropertyRule
    extends Rule
{
    private String _classAttributeName;
    private String _nameAttributeName;
    private String _valueAttributeName;
    private HashSet<String> _acceptableClassNames;

    /**
     * Creates a new SetPreferenceRule with default attribute names and classes.
     *
     * Default class attribute name = "type".
     * Default name attribute name = "name".
     * Default value attribute name = "value".
     * Default allowed classes = String, Integer, Double, and Boolean.
     */
    public SetPropertiesRule()
    {
        this("type", "name", "value",
             new Class<?>[] { String.class, Integer.class, Double.class, Boolean.class });
    }

    /**
     * Creates a new SetPropertyRule to construct a name/value pair and
     * set it on a Properties object.
     *
     * The Properties object should be at the top of the current
     * Digester stack.
     *
     * @param classAttributeName The name of the attribute that holds the property's value type.
     * @param nameAttributeName The name of the attribute that holds the property's name.
     * @param valueAttributeName The name of the attribute that holds the property's value.
     * @param acceptableClasses The list of acceptable property value types.
     */
    public SetPreferenceRule(String classAttributeName, String nameAttributeName, String valueAttributeName, Class<?>[] acceptableClasses)
    {
        super();

        _classAttributeName = classAttributeName;
        _nameAttributeName = nameAttributeName;
        _valueAttributeName = valueAttributeName;
        _acceptableClassNames = new HashSet<String>(acceptableClasses.length);
        for(Class<?> clazz : acceptableClasses)
            _acceptableClassNames.add(clazz.getName());
    }

    @Override
    public void begin(String namespace,
                      String name,
                      Attributes attributes)
        throws Exception
    {
        // Store the values of these attributes on the digester param stack
        getDigester().pushParams(
                attributes.getValue(_classAttributeName),
                attributes.getValue(_nameAttributeName),
                attributes.getValue(_valueAttributeName)
        );
    }

    @Override
    public void end(String namespace,
                    String name)
        throws Exception
    {
        Object[] attributeValues = getDigester().popParams();

        Object props = getDigester().peek();
        if(!(props instanceof java.util.Properties))
        {
            String typeName;
            if(null == props)
                typeName = "<null>";
            else
                typeName = props.getClass().getName();

            throw new IllegalStateException("Expected instance of " + Properties.class.getName() + ", got " + typeName + " instead");
        }

        String className = (String)attributeValues[0];
        checkClassName(className);

        // Create an instance of the preference value class
        Class<?> clazz = Class.forName(className);
        Constructor<?> cons = clazz.getConstructor(String.class);
        Object value = cons.newInstance((String)attributeValues[2]);

        ((Properties)props).put((String)attributeValues[1], value);
    }

    private void checkClassName(String className)
    {
        if(!_acceptableClassNames.contains(className))
            throw new IllegalArgumentException("Class " + className + " is not allowed");
    }
}

然而,我很高兴发现有一种开箱即用的方法可以做到这一点。

答案 1 :(得分:0)

对于不同的方法,您可以将问题移出沼泽器本身并使用增强的地图类来提供与现有沼气池规则更兼容的方法:

public static class MyHashMap extends HashMap {
  public Object put(String clazz, String name, String value) {
    Object obj = ... // create object from clazz/name/value
    return super.put(name, obj);
  }
}

然后只使用现有的addCallMethod / addCallParam规则:

Digester digester = new Digester();
digester.addObjectCreate("items", MyHashMap.class);
digester.addCallMethod("items/item", "put", 3, new Class<?>[] { String.class, String.class, String.class });
digester.addCallParam("items/item", 0, "type");
digester.addCallParam("items/item", 1, "name");
digester.addCallParam("items/item", 2, "value");

如果您需要获得纯HashMap而不是自定义类,则可以使用与自定义类包装原生HashMap类似的方法,例如com.google.common.collect.ForwardingMap你正在使用番石榴。