347 lines
10 KiB
Java
Executable File
347 lines
10 KiB
Java
Executable File
package org.moparscape.xml;
|
|
|
|
import org.moparscape.xml.impl.AbstractXmlElement;
|
|
import org.moparscape.xml.impl.XmlElement;
|
|
import org.moparscape.xml.impl.XmlElementFactory;
|
|
|
|
import java.lang.annotation.*;
|
|
import java.lang.reflect.AccessibleObject;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.Method;
|
|
import java.util.*;
|
|
|
|
public class ClassXmlElement {
|
|
|
|
public static final String[] errors = new String[]{
|
|
"ERROR GETTING VALUE"
|
|
, "ERROR: Cannot add " + ClassXmlElement.class.getSimpleName() + " without value as attribute!"
|
|
, "WARNING: INFINITE RECURSION DETECTED"
|
|
};
|
|
|
|
public static boolean debug = false;
|
|
|
|
private final String uuid;
|
|
|
|
{
|
|
uuid = UUID.randomUUID().toString();
|
|
}
|
|
|
|
@Target({ElementType.TYPE})
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
public @interface Root {
|
|
String name() default "";
|
|
}
|
|
|
|
@Target({ElementType.FIELD, ElementType.METHOD})
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
public @interface Attribute {
|
|
String name() default "";
|
|
}
|
|
|
|
@Target({ElementType.FIELD, ElementType.METHOD})
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
public @interface Child {
|
|
String name() default "";
|
|
|
|
String childName() default "";
|
|
|
|
String[] children() default {};
|
|
}
|
|
|
|
private String getString(Object ret) {
|
|
if (ret == null)
|
|
ret = errors[0];
|
|
return ret.toString();
|
|
}
|
|
|
|
private void addToDom(XmlElement addTo, Object key, Object val, boolean child) {
|
|
if (child)
|
|
addTo.getNewChildXmlElement(key.toString()).setValue(val.toString());
|
|
else
|
|
addTo.setAttribute(key.toString(), val.toString());
|
|
}
|
|
|
|
private XmlElement getAddTo(XmlElement ret, String name, boolean child) {
|
|
if (!child) // if we are expecting an attribute, never create a subobject
|
|
return ret;
|
|
XmlElement addTo = ret;
|
|
if (!name.isEmpty())
|
|
addTo = ret.getNewChildXmlElement(name);
|
|
return addTo;
|
|
}
|
|
|
|
private void addValueToDom(Object value, MethodField field, XmlElement ret, String name, int childDepth, boolean child, final Set<String> recursionDetector, final XmlElementFactory factory) {
|
|
if (value == null)
|
|
value = field.getValue();
|
|
|
|
if (name == null)
|
|
name = field.getName();
|
|
|
|
// if it's an array, we convert it to a List
|
|
// so our List handling code can be re-used
|
|
if (value instanceof Object[])
|
|
value = Arrays.asList((Object[]) value);
|
|
if (value instanceof List) {
|
|
XmlElement addTo = getAddTo(ret, name, child);
|
|
String childName = field.getName(child, childDepth);
|
|
//System.out.printf("name: '%s', childName: '%s'\n", name, childName);
|
|
List list = (List) value;
|
|
if (child) {
|
|
++childDepth;
|
|
for (Object o : list)
|
|
addValueToDom(o, field, addTo, childName, childDepth, child, recursionDetector, factory);
|
|
} else {
|
|
// if we want an attribute, assume they want the list delimited by spaces
|
|
// don't bother recursing, attributes can only handle values of type String anyhow
|
|
StringBuilder sb = new StringBuilder();
|
|
for (Object o : list)
|
|
sb.append(o.toString()).append(" ");
|
|
sb.delete(sb.length() - 1, sb.length());
|
|
addToDom(addTo, name, sb.toString(), false);
|
|
}
|
|
} else if (value instanceof Map) {
|
|
XmlElement addTo = getAddTo(ret, name, child);
|
|
Map<?, ?> map = (Map<?, ?>) value;
|
|
for (Map.Entry entry : map.entrySet()) {
|
|
// not sure how to handle this, I guess key would be name
|
|
addValueToDom(entry.getValue(), field, addTo, entry.getKey().toString(), childDepth, child, recursionDetector, factory);
|
|
//addToDom(addTo, entry.getKey(), entry.getValue(), child);
|
|
}
|
|
++childDepth;
|
|
} else if (value instanceof ClassXmlElement) {
|
|
ClassXmlElement other = ((ClassXmlElement) value);
|
|
if (child)
|
|
other.toXml(factory, getAddTo(ret, name, child), recursionDetector);
|
|
else {
|
|
XmlElement xmlChild = other.toXml(factory, null, recursionDetector);
|
|
String xmlChildValue = xmlChild.getValue();
|
|
if (xmlChildValue == null)
|
|
xmlChildValue = errors[1];
|
|
ret.setAttribute(xmlChild.getName(), xmlChildValue);
|
|
}
|
|
} else
|
|
addToDom(ret, name, getString(value), child);
|
|
}
|
|
|
|
private void processMethodField(MethodField field, XmlElement ret, final Set<String> recursionDetector, final XmlElementFactory factory) {
|
|
if (debug)
|
|
System.out.println(field);
|
|
boolean accessible = field.isAccessible();
|
|
field.setAccessible(true);
|
|
if (field.isAnnotationPresent(Child.class) || field.isAnnotationPresent(Attribute.class))
|
|
addValueToDom(null, field, ret, null, 0, field.isChild(), recursionDetector, factory);
|
|
field.setAccessible(accessible);
|
|
}
|
|
|
|
public final XmlElement toXml() {
|
|
return this.toXml(null);
|
|
}
|
|
|
|
public final XmlElement toXml(final XmlElementFactory factory) {
|
|
return this.toXml(factory == null ? AbstractXmlElement.getFactory() : factory, null, new HashSet<String>());
|
|
}
|
|
|
|
private XmlElement toXml(final XmlElementFactory factory, final XmlElement parent, final Set<String> recursionDetector) {
|
|
|
|
Class thisClass = this.getClass();
|
|
String rootName = thisClass.getSimpleName();
|
|
if (thisClass.isAnnotationPresent(Root.class)) {
|
|
String rootAnnotationName = this.getClass().getAnnotation(Root.class).name();
|
|
if (!rootAnnotationName.isEmpty())
|
|
rootName = rootAnnotationName;
|
|
}
|
|
XmlElement ret = parent != null ? parent.getNewChildXmlElement(rootName) : factory.getNewChildXmlElement(rootName);
|
|
|
|
if (recursionDetector.contains(this.uuid)) {
|
|
if (debug)
|
|
System.out.println("Infinite Recursion detected, returning...");
|
|
ret.setValue(errors[2]);
|
|
return ret;
|
|
//return E("WARNING", "INFINITE RECURSION DETECTED");
|
|
//return null;
|
|
}
|
|
recursionDetector.add(this.uuid);
|
|
|
|
List<MethodField> mfList = new LinkedList<MethodField>();
|
|
|
|
for (Field field : thisClass.getDeclaredFields())
|
|
mfList.add(new MethodField(this).setField(field));
|
|
for (Method method : thisClass.getDeclaredMethods())
|
|
mfList.add(new MethodField(this).setMethod(method));
|
|
|
|
// find and add xmlns first thing, some implementations require this (currently Dom4jXmlElement and XomXmlElement)
|
|
Iterator<MethodField> iter = mfList.iterator();
|
|
while (iter.hasNext()) {
|
|
MethodField mf = iter.next();
|
|
if(mf.isChild())
|
|
continue;
|
|
String name = mf.getName();
|
|
if(!name.equals("xmlns") && !name.startsWith("xmlns:"))
|
|
continue;
|
|
// otherwise, process the entry and remove it from the list
|
|
processMethodField(mf, ret, recursionDetector, factory);
|
|
iter.remove();
|
|
}
|
|
|
|
for(MethodField mf : mfList)
|
|
processMethodField(mf, ret, recursionDetector, factory);
|
|
|
|
recursionDetector.remove(this.uuid);
|
|
return ret;
|
|
}
|
|
|
|
private static class MethodField extends java.lang.reflect.AccessibleObject {
|
|
private final Object container;
|
|
|
|
private Method m;
|
|
private Field f;
|
|
|
|
private AccessibleObject ao;
|
|
|
|
public MethodField(Object container) {
|
|
this.container = container;
|
|
}
|
|
|
|
public MethodField setField(Field f) {
|
|
return setMethodField(null, f);
|
|
}
|
|
|
|
public MethodField setMethod(Method m) {
|
|
return setMethodField(m, null);
|
|
}
|
|
|
|
private MethodField setMethodField(Method m, Field f) {
|
|
if (m == null && f == null)
|
|
throw new Error("Method and field cannot be null!");
|
|
this.m = m;
|
|
if (m != null)
|
|
ao = m;
|
|
this.f = f;
|
|
if (f != null)
|
|
ao = f;
|
|
return this;
|
|
}
|
|
|
|
public boolean isChild() {
|
|
return ao.isAnnotationPresent(Child.class);
|
|
}
|
|
|
|
public String getName() {
|
|
return getName(false, 0);
|
|
}
|
|
|
|
public String getName(boolean child, int childDepth) {
|
|
String name = "";
|
|
if (ao.isAnnotationPresent(Attribute.class))
|
|
name = ao.getAnnotation(Attribute.class).name();
|
|
else if (ao.isAnnotationPresent(Child.class)) {
|
|
name = ao.getAnnotation(Child.class).name();
|
|
if (child) {
|
|
String childName = ao.getAnnotation(Child.class).childName();
|
|
String[] childrenNames = ao.getAnnotation(Child.class).children();
|
|
if (childDepth < childrenNames.length)
|
|
childName = childrenNames[childDepth];
|
|
|
|
// if childName isn't set, but children is, then take the last valid child
|
|
if (childName.isEmpty() && childrenNames.length > 0)
|
|
childName = childrenNames[childrenNames.length - 1];
|
|
|
|
// if the above doesn't exist, then use name but stripS
|
|
if (childName.isEmpty())
|
|
name = stripS(name);
|
|
else
|
|
name = childName;
|
|
}
|
|
}
|
|
|
|
if (!name.isEmpty())
|
|
return name;
|
|
|
|
if (f != null)
|
|
name = f.getName();
|
|
else {
|
|
name = m.getName();
|
|
// if there is a leading get, strip it off
|
|
if (name.toLowerCase().startsWith("get") && name.length() > 3)
|
|
name = name.substring(3, name.length());
|
|
}
|
|
if (child)
|
|
name = stripS(name);
|
|
|
|
// then set it as the field name, replacing _ with :
|
|
name = name.replace('_', ':');
|
|
return name;
|
|
}
|
|
|
|
public static String stripS(String name) {
|
|
String nameLower = name.toLowerCase();
|
|
if (name.length() < 2 || !nameLower.endsWith("s"))
|
|
return name;
|
|
if (name.length() > 3 && nameLower.endsWith("ies"))
|
|
name = name.substring(0, name.length() - 3) + "y";
|
|
else
|
|
name = name.substring(0, name.length() - 1);
|
|
return name;
|
|
|
|
}
|
|
|
|
public Object getValue() {
|
|
Object ret = null;
|
|
try {
|
|
if (f != null)
|
|
ret = f.get(container);
|
|
else
|
|
ret = m.invoke(container);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
@Override
|
|
public void setAccessible(boolean flag) throws SecurityException {
|
|
ao.setAccessible(flag);
|
|
}
|
|
|
|
@Override
|
|
public boolean isAccessible() {
|
|
return ao.isAccessible();
|
|
}
|
|
|
|
@Override
|
|
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
|
|
return ao.getAnnotation(annotationClass);
|
|
}
|
|
|
|
@Override
|
|
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
|
|
return ao.isAnnotationPresent(annotationClass);
|
|
}
|
|
|
|
@Override
|
|
public Annotation[] getAnnotations() {
|
|
return ao.getAnnotations();
|
|
}
|
|
|
|
@Override
|
|
public Annotation[] getDeclaredAnnotations() {
|
|
return ao.getDeclaredAnnotations();
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return ao.hashCode();
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
return ao.equals(obj);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return (f == null ? "method" : "field ") + "(" + (isChild() ? "child) " : "attribute)") + ": " + ao.toString();
|
|
}
|
|
}
|
|
}
|