如何使用 commons-digester 调用带有弹出对象的方法?
How can I call a method with popped object using commons-digester?
我有一个 XML 文档,如下所示:
<!-- language: xml -->
<items>
<item type="java.lang.Boolean" name="foo" value="true" />
</items>
我希望 <root>
元素创建一个 java.util.Map
对象并让每个 <item>
元素创建一个适当类型的对象,然后向 Map
-- 类似于 SetNextRule
但调用的参数来自堆栈。
我已经创建了一个自定义 Rule
,它将使用 type
属性(在本例中为 java.lang.Boolean
)中指定的类型创建一个对象 value
属性并将其压入堆栈。
现在,我想将项目从堆栈顶部弹出,并将其用作 Map
对象(只是 "under" 堆栈上的 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
我得到的错误是在 java.lang.Boolean
class 中找不到方法 put
。所以,问题是例如Boolean
对象位于堆栈的顶部,我想将其用作在堆栈的下一个顶层元素上调用的 put
方法的参数:
堆栈:
java.lang.Boolean value=true <-- top of stack, desired call param
java.util.HashMap contents = {} <-- desired call target
有没有办法使用现有的 commons-digester 规则来执行此操作,或者我是否必须创建另一个自定义规则来执行此类操作?
我最终编写了一个结合了两个操作的自定义规则:构建 属性 值的新实例 和 将其插入到属性包中。
这是对我的 真实 用例的改编,所以代码可能不是 100% 完美,因为我 copy/pasted 并改编了它,在这里.我也明白使用 java.lang.String
以外的 属性 值没有多大意义,但它确实适用于我的用例(实际上不使用 java.util.Properties
,但是 class 是一个很好的类比)。
<!-- 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");
}
}
不过,我很高兴发现有一种开箱即用的方法可以做到这一点。
对于不同的方法,您可以将问题从 digester 本身移出并使用增强映射 class 来提供与现有 digester 规则更兼容的方法:
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
结果而不是自定义 class,您可以使用类似的方法将自定义 class 包装为原生 [=14] =],例如 com.google.common.collect.ForwardingMap
如果您使用的是 Guava。
我有一个 XML 文档,如下所示:
<!-- language: xml -->
<items>
<item type="java.lang.Boolean" name="foo" value="true" />
</items>
我希望 <root>
元素创建一个 java.util.Map
对象并让每个 <item>
元素创建一个适当类型的对象,然后向 Map
-- 类似于 SetNextRule
但调用的参数来自堆栈。
我已经创建了一个自定义 Rule
,它将使用 type
属性(在本例中为 java.lang.Boolean
)中指定的类型创建一个对象 value
属性并将其压入堆栈。
现在,我想将项目从堆栈顶部弹出,并将其用作 Map
对象(只是 "under" 堆栈上的 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
我得到的错误是在 java.lang.Boolean
class 中找不到方法 put
。所以,问题是例如Boolean
对象位于堆栈的顶部,我想将其用作在堆栈的下一个顶层元素上调用的 put
方法的参数:
堆栈:
java.lang.Boolean value=true <-- top of stack, desired call param
java.util.HashMap contents = {} <-- desired call target
有没有办法使用现有的 commons-digester 规则来执行此操作,或者我是否必须创建另一个自定义规则来执行此类操作?
我最终编写了一个结合了两个操作的自定义规则:构建 属性 值的新实例 和 将其插入到属性包中。
这是对我的 真实 用例的改编,所以代码可能不是 100% 完美,因为我 copy/pasted 并改编了它,在这里.我也明白使用 java.lang.String
以外的 属性 值没有多大意义,但它确实适用于我的用例(实际上不使用 java.util.Properties
,但是 class 是一个很好的类比)。
<!-- 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");
}
}
不过,我很高兴发现有一种开箱即用的方法可以做到这一点。
对于不同的方法,您可以将问题从 digester 本身移出并使用增强映射 class 来提供与现有 digester 规则更兼容的方法:
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
结果而不是自定义 class,您可以使用类似的方法将自定义 class 包装为原生 [=14] =],例如 com.google.common.collect.ForwardingMap
如果您使用的是 Guava。