2

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

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

我希望<root>元素创建一个java.util.Map对象并让每个<item>元素创建一个适当类型的对象,然后将一个条目添加到Map-- 类似于 aSetNextRule但带有来自堆栈的调用的参数。

我已经创建了一个自定义Rule,它将使用属性中的值创建type属性中指定类型的对象(java.lang.Boolean在本例中)value并将其推送到堆栈上。

现在,我想将项目从堆栈顶部弹出,并将其用作对象上的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在类中找不到该方法的错误java.lang.Boolean。所以,问题是 egBoolean对象位于堆栈的顶部,我想将它用作堆栈put上的下一个到顶部元素上调用的方法的参数:

堆:

java.lang.Boolean value=true     <-- top of stack, desired call param
java.util.HashMap contents = {}  <-- desired call target

有没有办法使用现有的 commons-digester 规则来做到这一点,或者我是否必须创建另一个执行此类操作的自定义规则?

4

2 回答 2

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

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

于 2017-11-25T15:10:12.060 回答
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");

如果您需要得到一个 pureHashMap结果而不是自定义类,您可以使用类似的方法与您的自定义类包装一个 native HashMap,例如,com.google.common.collect.ForwardingMap如果您使用 Guava。

于 2017-12-10T23:17:46.747 回答