added sources for Slick

Former-commit-id: 1647fa32ef6894bd7db44f741f07c2f4dcdf9054
Former-commit-id: 0e5810dcfbe1fd59b13e7cabe9f1e93c5542da2d
This commit is contained in:
Song Minjae
2016-12-30 23:29:12 +09:00
parent d24b31e15d
commit 059abff814
329 changed files with 58400 additions and 7 deletions

View File

@@ -0,0 +1,483 @@
package org.newdawn.slick.util.xml;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
/**
* Provides a method of parsing XML into an existing data model. This does not
* provide the same functionality as JAXB or the variety of XML bindings out there. This
* is a utility to map XML onto an existing data model. The idea being that the design level
* model should not be driven by the XML schema thats defined. The two arn't always equal
* and often you end up with a set of class that represent your XML that you then have
* to traverse to extract into your normal data model.
*
* This utility hopes to take a piece of XML and map it onto a previously designed data
* model. At the moment it's way to tied to the structure of the XML but this will
* hopefully change with time.
*
* XML element names must be mapped to class names. This can be done in two ways either:
*
* - Specify an explict mapping with addElementMapping()
* - Specify the default package name and use the element name as the class name
*
* Each attribute in an element is mapped into a property of the element class, preferably
* through a set<AttrName> bean method, but alternatively by direct injection into private
* fields.
*
* Each child element is added to the target class by call the method add() on it with a single
* parameter of the type generated for the child element.
*
* Classes can optionally implement setXMLElementName(String) and setXMLElementContent(String) to
* recieve the name and content respectively of the XMLElement they were parsed from. This can
* help when mapping two elements to a single class.
*
* To reiterate, I'm not sure this is a good idea yet. It helps me as a utility since I've done
* this several times in the past but in the general case it may not be perfect. Consider a custom
* parser using XMLParser or JAXB (et al) seriously instead.
*
* @author kevin
*
*/
public class ObjectTreeParser {
/** The mapping of XML element names to class names */
private HashMap nameToClass = new HashMap();
/** The default package where classes will be searched for */
private String defaultPackage;
/** The list of elements to ignore */
private ArrayList ignored = new ArrayList();
/** The name of the method to add an child object to it's parent */
private String addMethod = "add";
/**
* Create an object tree parser with no default package
*/
public ObjectTreeParser() {
}
/**
* Create an object tree parser specifing the default package
* where classes will be search for using the XML element name
*
* @param defaultPackage The default package to be searched
*/
public ObjectTreeParser(String defaultPackage) {
this.defaultPackage = defaultPackage;
}
/**
* Add a mapping between XML element name and class name
*
* @param elementName The name of the XML element
* @param elementClass The class to be created for the given element
*/
public void addElementMapping(String elementName, Class elementClass) {
nameToClass.put(elementName, elementClass);
}
/**
* Add a name to the list of elements ignored
*
* @param elementName The name to ignore
*/
public void addIgnoredElement(String elementName) {
ignored.add(elementName);
}
/**
* Set the name of the method to use to add child objects to their
* parents. This is sometimes useful to not clash with the existing
* data model methods.
*
* @param methodName The name of the method to call
*/
public void setAddMethodName(String methodName) {
addMethod = methodName;
}
/**
* Set the default package which will be search for classes by their XML
* element name.
*
* @param defaultPackage The default package to be searched
*/
public void setDefaultPackage(String defaultPackage) {
this.defaultPackage = defaultPackage;
}
/**
* Parse the XML document located by the slick resource loader using the
* reference given.
*
* @param ref The reference to the XML document
* @return The root element of the newly parse document
* @throws SlickXMLException Indicates a failure to parse the XML, most likely the
* XML is malformed in some way.
*/
public Object parse(String ref) throws SlickXMLException {
return parse(ref, ResourceLoader.getResourceAsStream(ref));
}
/**
* Parse the XML document that can be read from the given input stream
*
* @param name The name of the document
* @param in The input stream from which the document can be read
* @return The root element of the newly parse document
* @throws SlickXMLException Indicates a failure to parse the XML, most likely the
* XML is malformed in some way.
*/
public Object parse(String name, InputStream in) throws SlickXMLException {
XMLParser parser = new XMLParser();
XMLElement root = parser.parse(name, in);
return traverse(root);
}
/**
* Parse the XML document located by the slick resource loader using the
* reference given.
*
* @param ref The reference to the XML document
* @param target The top level object that represents the root node
* @return The root element of the newly parse document
* @throws SlickXMLException Indicates a failure to parse the XML, most likely the
* XML is malformed in some way.
*/
public Object parseOnto(String ref, Object target) throws SlickXMLException {
return parseOnto(ref, ResourceLoader.getResourceAsStream(ref), target);
}
/**
* Parse the XML document that can be read from the given input stream
*
* @param name The name of the document
* @param in The input stream from which the document can be read
* @param target The top level object that represents the root node
* @return The root element of the newly parse document
* @throws SlickXMLException Indicates a failure to parse the XML, most likely the
* XML is malformed in some way.
*/
public Object parseOnto(String name, InputStream in, Object target) throws SlickXMLException {
XMLParser parser = new XMLParser();
XMLElement root = parser.parse(name, in);
return traverse(root, target);
}
/**
* Deterine the name of the class that should be used for a given
* XML element name.
*
* @param name The name of the XML element
* @return The class to be used or null if none can be found
*/
private Class getClassForElementName(String name) {
Class clazz = (Class) nameToClass.get(name);
if (clazz != null) {
return clazz;
}
if (defaultPackage != null) {
try {
return Class.forName(defaultPackage+"."+name);
} catch (ClassNotFoundException e) {
// ignore, it's just not there
}
}
return null;
}
/**
* Traverse the XML element specified generating the appropriate object structure
* for it and it's children
*
* @param current The XML element to process
* @return The object created for the given element
* @throws SlickXMLException
*/
private Object traverse(XMLElement current) throws SlickXMLException {
return traverse(current, null);
}
/**
* Traverse the XML element specified generating the appropriate object structure
* for it and it's children
*
* @param current The XML element to process
* @param instance The instance to parse onto, normally null
* @return The object created for the given element
* @throws SlickXMLException
*/
private Object traverse(XMLElement current, Object instance) throws SlickXMLException {
String name = current.getName();
if (ignored.contains(name)) {
return null;
}
Class clazz;
if (instance == null) {
clazz = getClassForElementName(name);
} else {
clazz = instance.getClass();
}
if (clazz == null) {
throw new SlickXMLException("Unable to map element "+name+" to a class, define the mapping");
}
try {
if (instance == null) {
instance = clazz.newInstance();
Method elementNameMethod = getMethod(clazz, "setXMLElementName", new Class[] {String.class});
if (elementNameMethod != null) {
invoke(elementNameMethod, instance, new Object[] {name});
}
Method contentMethod = getMethod(clazz, "setXMLElementContent", new Class[] {String.class});
if (contentMethod != null) {
invoke(contentMethod, instance, new Object[] {current.getContent()});
}
}
String[] attrs = current.getAttributeNames();
for (int i=0;i<attrs.length;i++) {
String methodName = "set"+attrs[i];
Method method = findMethod(clazz, methodName);
if (method == null) {
Field field = findField(clazz, attrs[i]);
if (field != null) {
String value = current.getAttribute(attrs[i]);
Object typedValue = typeValue(value, field.getType());
setField(field, instance, typedValue);
} else {
Log.info("Unable to find property on: "+clazz+" for attribute: "+attrs[i]);
}
} else {
String value = current.getAttribute(attrs[i]);
Object typedValue = typeValue(value, method.getParameterTypes()[0]);
invoke(method, instance, new Object[] {typedValue});
}
}
XMLElementList children = current.getChildren();
for (int i=0;i<children.size();i++) {
XMLElement element = children.get(i);
Object child = traverse(element);
if (child != null) {
String methodName = addMethod;
Method method = findMethod(clazz, methodName, child.getClass());
if (method == null) {
Log.info("Unable to find method to add: "+child+" to "+clazz);
} else {
invoke(method, instance, new Object[] {child});
}
}
}
return instance;
} catch (InstantiationException e) {
throw new SlickXMLException("Unable to instance "+clazz+" for element "+name+", no zero parameter constructor?", e);
} catch (IllegalAccessException e) {
throw new SlickXMLException("Unable to instance "+clazz+" for element "+name+", no zero parameter constructor?", e);
}
}
/**
* Convert a given value to a given type
*
* @param value The value to convert
* @param clazz The class that the returned object must be
* @return The value as the given type
* @throws SlickXMLException Indicates there is no automatic way of converting the value to the type
*/
private Object typeValue(String value, Class clazz) throws SlickXMLException {
if (clazz == String.class) {
return value;
}
try {
clazz = mapPrimitive(clazz);
return clazz.getConstructor(new Class[] {String.class}).newInstance(new Object[] {value});
} catch (Exception e) {
throw new SlickXMLException("Failed to convert: "+value+" to the expected primitive type: "+clazz, e);
}
}
/**
* Map a primitive class type to it's real object wrapper
*
* @param clazz The primitive type class
* @return The object wrapper class
*/
private Class mapPrimitive(Class clazz) {
if (clazz == Integer.TYPE) {
return Integer.class;
}
if (clazz == Double.TYPE) {
return Double.class;
}
if (clazz == Float.TYPE) {
return Float.class;
}
if (clazz == Boolean.TYPE) {
return Boolean.class;
}
if (clazz == Long.TYPE) {
return Long.class;
}
throw new RuntimeException("Unsupported primitive: "+clazz);
}
/**
* Find a field in a class by it's name. Note that this method is
* only needed because the general reflection method is case
* sensitive
*
* @param clazz The clazz to search
* @param name The name of the field to search for
* @return The field or null if none could be located
*/
private Field findField(Class clazz, String name) {
Field[] fields = clazz.getDeclaredFields();
for (int i=0;i<fields.length;i++) {
if (fields[i].getName().equalsIgnoreCase(name)) {
if (fields[i].getType().isPrimitive()) {
return fields[i];
}
if (fields[i].getType() == String.class) {
return fields[i];
}
}
}
return null;
}
/**
* Find a method in a class by it's name. Note that this method is
* only needed because the general reflection method is case
* sensitive
*
* @param clazz The clazz to search
* @param name The name of the method to search for
* @return The method or null if none could be located
*/
private Method findMethod(Class clazz, String name) {
Method[] methods = clazz.getDeclaredMethods();
for (int i=0;i<methods.length;i++) {
if (methods[i].getName().equalsIgnoreCase(name)) {
Method method = methods[i];
Class[] params = method.getParameterTypes();
if (params.length == 1) {
return method;
}
}
}
return null;
}
/**
* Find a method on a class with a single given parameter.
*
* @param clazz The clazz to search through
* @param name The name of the method to locate
* @param parameter The type the single parameter must have
* @return The method or null if none could be located
*/
private Method findMethod(Class clazz, String name, Class parameter) {
Method[] methods = clazz.getDeclaredMethods();
for (int i=0;i<methods.length;i++) {
if (methods[i].getName().equalsIgnoreCase(name)) {
Method method = methods[i];
Class[] params = method.getParameterTypes();
if (params.length == 1) {
if (method.getParameterTypes()[0].isAssignableFrom(parameter)) {
return method;
}
}
}
}
return null;
}
/**
* Set a field value on a object instance
*
* @param field The field to be set
* @param instance The instance of the object to set it on
* @param value The value to set
* @throws SlickXMLException Indicates a failure to set or access the field
*/
private void setField(Field field, Object instance, Object value) throws SlickXMLException {
try {
field.setAccessible(true);
field.set(instance, value);
} catch (IllegalArgumentException e) {
throw new SlickXMLException("Failed to set: "+field+" for an XML attribute, is it valid?", e);
} catch (IllegalAccessException e) {
throw new SlickXMLException("Failed to set: "+field+" for an XML attribute, is it valid?", e);
} finally {
field.setAccessible(false);
}
}
/**
* Call a method on a object
*
* @param method The method to call
* @param instance The objet to call the method on
* @param params The parameters to pass
* @throws SlickXMLException Indicates a failure to call or access the method
*/
private void invoke(Method method, Object instance, Object[] params) throws SlickXMLException {
try {
method.setAccessible(true);
method.invoke(instance, params);
} catch (IllegalArgumentException e) {
throw new SlickXMLException("Failed to invoke: "+method+" for an XML attribute, is it valid?", e);
} catch (IllegalAccessException e) {
throw new SlickXMLException("Failed to invoke: "+method+" for an XML attribute, is it valid?", e);
} catch (InvocationTargetException e) {
throw new SlickXMLException("Failed to invoke: "+method+" for an XML attribute, is it valid?", e);
} finally {
method.setAccessible(false);
}
}
/**
* Get a method on a given class. Only here for tidy purposes,
* hides the the big exceptions.
*
* @param clazz The class to search
* @param name The name of the method
* @param params The parameters for the method
* @return The method or null if none can be found
*/
private Method getMethod(Class clazz, String name, Class[] params) {
try {
return clazz.getMethod(name, params);
} catch (SecurityException e) {
return null;
} catch (NoSuchMethodException e) {
return null;
}
}
}

View File

@@ -0,0 +1,32 @@
package org.newdawn.slick.util.xml;
import org.newdawn.slick.SlickException;
/**
* An exception to describe failures in XML. Made a special case because with XML
* to object parsing you might want to handle it differently
*
* @author kevin
*/
public class SlickXMLException extends SlickException {
/**
* Create a new exception
*
* @param message The message describing the failure
*/
public SlickXMLException(String message) {
super(message);
}
/**
* Create a new exception
*
* @param message The message describing the failure
* @param e The exception causing this failure
*/
public SlickXMLException(String message, Throwable e) {
super(message, e);
}
}

View File

@@ -0,0 +1,260 @@
package org.newdawn.slick.util.xml;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
/**
* A utility wrapper round the standard DOM XML element. This provides a more simple API
* for accessing attributes, children and providing defaults when schemas arn't used - which
* is generally a little simpler for most of us.
*
* @author kevin
*/
public class XMLElement {
/** The Java DOM implementation XML element */
private Element dom;
/** The list of children initialised on first access */
private XMLElementList children;
/** The name of the element */
private String name;
/**
* Create a new element wrapped round a DOM element
*
* @param xmlElement The DOM element to present
*/
XMLElement(Element xmlElement) {
dom = xmlElement;
name = dom.getTagName();
}
/**
* Get the names of the attributes specified on this element
*
* @return The names of the elements specified
*/
public String[] getAttributeNames() {
NamedNodeMap map = dom.getAttributes();
String[] names = new String[map.getLength()];
for (int i=0;i<names.length;i++) {
names[i] = map.item(i).getNodeName();
}
return names;
}
/**
* Get the name of this element
*
* @return The name of this element
*/
public String getName() {
return name;
}
/**
* Get the value specified for a given attribute on this element
*
* @param name The name of the attribute whose value should be retrieved
* @return The value given for the attribute
*/
public String getAttribute(String name) {
return dom.getAttribute(name);
}
/**
* Get the value specified for a given attribute on this element
*
* @param name The name of the attribute whose value should be retrieved
* @param def The default value to return if the attribute is specified
* @return The value given for the attribute
*/
public String getAttribute(String name, String def) {
String value = dom.getAttribute(name);
if ((value == null) || (value.length() == 0)) {
return def;
}
return value;
}
/**
* Get the value specified for a given attribute on this element as an integer.
*
* @param name The name of the attribute whose value should be retrieved
* @return The value given for the attribute
* @throws SlickXMLException Indicates a failure to convert the value into an integer
*/
public int getIntAttribute(String name) throws SlickXMLException {
try {
return Integer.parseInt(getAttribute(name));
} catch (NumberFormatException e) {
throw new SlickXMLException("Value read: '"+getAttribute(name)+"' is not an integer",e);
}
}
/**
* Get the value specified for a given attribute on this element as an integer.
*
* @param name The name of the attribute whose value should be retrieved
* @param def The default value to return if the attribute is specified
* @return The value given for the attribute
* @throws SlickXMLException Indicates a failure to convert the value into an integer
*/
public int getIntAttribute(String name, int def) throws SlickXMLException {
try {
return Integer.parseInt(getAttribute(name,""+def));
} catch (NumberFormatException e) {
throw new SlickXMLException("Value read: '"+getAttribute(name, ""+def)+"' is not an integer",e);
}
}
/**
* Get the value specified for a given attribute on this element as an double.
*
* @param name The name of the attribute whose value should be retrieved
* @return The value given for the attribute
* @throws SlickXMLException Indicates a failure to convert the value into an double
*/
public double getDoubleAttribute(String name) throws SlickXMLException {
try {
return Double.parseDouble(getAttribute(name));
} catch (NumberFormatException e) {
throw new SlickXMLException("Value read: '"+getAttribute(name)+"' is not a double",e);
}
}
/**
* Get the value specified for a given attribute on this element as an double.
*
* @param name The name of the attribute whose value should be retrieved
* @param def The default value to return if the attribute is specified
* @return The value given for the attribute
* @throws SlickXMLException Indicates a failure to convert the value into an double
*/
public double getDoubleAttribute(String name, double def) throws SlickXMLException {
try {
return Double.parseDouble(getAttribute(name,""+def));
} catch (NumberFormatException e) {
throw new SlickXMLException("Value read: '"+getAttribute(name, ""+def)+"' is not a double",e);
}
}
/**
* Get the value specified for a given attribute on this element as a boolean.
*
* @param name The name of the attribute whose value should be retrieved
* @return The value given for the attribute
* @throws SlickXMLException Indicates a failure to convert the value into an boolean
*/
public boolean getBooleanAttribute(String name) throws SlickXMLException {
String value = getAttribute(name);
if (value.equalsIgnoreCase("true")) {
return true;
}
if (value.equalsIgnoreCase("false")) {
return false;
}
throw new SlickXMLException("Value read: '"+getAttribute(name)+"' is not a boolean");
}
/**
* Get the value specified for a given attribute on this element as a boolean.
*
* @param name The name of the attribute whose value should be retrieved
* @param def The default value to return if the attribute is specified
* @return The value given for the attribute
* @throws SlickXMLException Indicates a failure to convert the value into an boolean
*/
public boolean getBooleanAttribute(String name, boolean def) throws SlickXMLException {
String value = getAttribute(name,""+def);
if (value.equalsIgnoreCase("true")) {
return true;
}
if (value.equalsIgnoreCase("false")) {
return false;
}
throw new SlickXMLException("Value read: '"+getAttribute(name, ""+def)+"' is not a boolean");
}
/**
* Get the text content of the element, i.e. the bit between the tags
*
* @return The text content of the node
*/
public String getContent() {
String content = "";
NodeList list = dom.getChildNodes();
for (int i=0;i<list.getLength();i++) {
if (list.item(i) instanceof Text) {
content += (list.item(i).getNodeValue());
}
}
return content;
}
/**
* Get the complete list of children for this node
*
* @return The list of children for this node
*/
public XMLElementList getChildren() {
if (children != null) {
return children;
}
NodeList list = dom.getChildNodes();
children = new XMLElementList();
for (int i=0;i<list.getLength();i++) {
if (list.item(i) instanceof Element) {
children.add(new XMLElement((Element) list.item(i)));
}
}
return children;
}
/**
* Get a list of children with a given element name
*
* @param name The name of the element type that should be retrieved
* @return A list of elements
*/
public XMLElementList getChildrenByName(String name) {
XMLElementList selected = new XMLElementList();
XMLElementList children = getChildren();
for (int i=0;i<children.size();i++) {
if (children.get(i).getName().equals(name)) {
selected.add(children.get(i));
}
}
return selected;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
String value = "[XML "+getName();
String[] attrs = getAttributeNames();
for (int i=0;i<attrs.length;i++) {
value += " "+attrs[i]+"="+getAttribute(attrs[i]);
}
value += "]";
return value;
}
}

View File

@@ -0,0 +1,68 @@
package org.newdawn.slick.util.xml;
import java.util.ArrayList;
import java.util.Collection;
/**
* A simple typed list.
*
* @author kevin
*/
public class XMLElementList {
/** The list of elements */
private ArrayList list = new ArrayList();
/**
* Create a new list
*/
public XMLElementList() {
}
/**
* Add an element to the list
*
* @param element The element to be added
*/
public void add(XMLElement element) {
list.add(element);
}
/**
* Get the number of elements in the list
*
* @return The number of elements in the list
*/
public int size() {
return list.size();
}
/**
* Get the element at a specified index
*
* @param i The index of the element
* @return The element at the specified index
*/
public XMLElement get(int i) {
return (XMLElement) list.get(i);
}
/**
* Check if this list contains the given element
*
* @param element The element to check for
* @return True if the element is in the list
*/
public boolean contains(XMLElement element) {
return list.contains(element);
}
/**
* Add all the elements in this list to another collection
*
* @param collection The collection the elements should be added to
*/
public void addAllTo(Collection collection) {
collection.addAll(list);
}
}

View File

@@ -0,0 +1,63 @@
package org.newdawn.slick.util.xml;
import java.io.InputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.util.ResourceLoader;
import org.w3c.dom.Document;
/**
* A simple utility wrapper around the Java DOM implementation to hopefully
* make XML parsing that bit easier without requiring YAL.
*
* @author kevin
*/
public class XMLParser {
/** The factory used to to create document builders that parse XML into the DOM */
private static DocumentBuilderFactory factory;
/**
* Create a new parser
*/
public XMLParser() {
}
/**
* Parse the XML document located by the slick resource loader using the
* reference given.
*
* @param ref The reference to the XML document
* @return The root element of the newly parse document
* @throws SlickException Indicates a failure to parse the XML, most likely the
* XML is malformed in some way.
*/
public XMLElement parse(String ref) throws SlickException {
return parse(ref, ResourceLoader.getResourceAsStream(ref));
}
/**
* Parse the XML document that can be read from the given input stream
*
* @param name The name of the document
* @param in The input stream from which the document can be read
* @return The root element of the newly parse document
* @throws SlickXMLException Indicates a failure to parse the XML, most likely the
* XML is malformed in some way.
*/
public XMLElement parse(String name, InputStream in) throws SlickXMLException {
try {
if (factory == null) {
factory = DocumentBuilderFactory.newInstance();
}
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(in);
return new XMLElement(doc.getDocumentElement());
} catch (Exception e) {
throw new SlickXMLException("Failed to parse document: "+name, e);
}
}
}

View File

@@ -0,0 +1,3 @@
<BODY>
Some utilities for reading XML using Java DOM and for mapping XML onto existing data models
</BODY>