NIHVIVO-1562 Working on dumping Java objects through method invocation
This commit is contained in:
parent
9da3a7c6ab
commit
f1288b90b5
3 changed files with 178 additions and 34 deletions
|
@ -5,12 +5,16 @@ package freemarker.ext.dump;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
@ -65,7 +69,8 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel {
|
||||||
DATE("Date"),
|
DATE("Date"),
|
||||||
SEQUENCE("Sequence"),
|
SEQUENCE("Sequence"),
|
||||||
HASH("Hash"),
|
HASH("Hash"),
|
||||||
HASH_EX("HashEx"),
|
// Technically it's a HashEx, but for the templates call it a Hash
|
||||||
|
HASH_EX("Hash"), // ("HashEx")
|
||||||
COLLECTION("Collection"),
|
COLLECTION("Collection"),
|
||||||
METHOD("Method"),
|
METHOD("Method"),
|
||||||
DIRECTIVE("Directive");
|
DIRECTIVE("Directive");
|
||||||
|
@ -236,6 +241,16 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> getTemplateModelData(TemplateHashModelEx model) throws TemplateModelException {
|
private Map<String, Object> getTemplateModelData(TemplateHashModelEx model) throws TemplateModelException {
|
||||||
|
Object unwrappedModel = DeepUnwrap.permissiveUnwrap(model);
|
||||||
|
// A key-value mapping.
|
||||||
|
if ( unwrappedModel instanceof Map ) {
|
||||||
|
return getMapData(model);
|
||||||
|
}
|
||||||
|
// Java objects are wrapped as TemplateHashModelEx-s.
|
||||||
|
return getObjectData(model, unwrappedModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getMapData(TemplateHashModelEx model) throws TemplateModelException {
|
||||||
Map<String, Object> map = new HashMap<String, Object>();
|
Map<String, Object> map = new HashMap<String, Object>();
|
||||||
map.put(Key.TYPE.toString(), Type.HASH_EX);
|
map.put(Key.TYPE.toString(), Type.HASH_EX);
|
||||||
Map<String, Object> items = new HashMap<String, Object>();
|
Map<String, Object> items = new HashMap<String, Object>();
|
||||||
|
@ -251,6 +266,102 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel {
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getObjectData(TemplateHashModelEx model, Object object) throws TemplateModelException {
|
||||||
|
Map<String, Object> map = new HashMap<String, Object>();
|
||||||
|
map.put(Key.TYPE.toString(), object.getClass().getName());
|
||||||
|
Set<Method> availableMethods = getMethodsAvailableToTemplate(model, object);
|
||||||
|
Map<String, Object> methods = new HashMap<String, Object>(availableMethods.size());
|
||||||
|
for ( Method method : availableMethods ) {
|
||||||
|
String methodDisplayName = getMethodDisplayName(method);
|
||||||
|
if ( ! methodDisplayName.endsWith(")") ) {
|
||||||
|
try {
|
||||||
|
Object result = method.invoke(object);
|
||||||
|
log.debug("Result of invoking method " + method.getName() + " is an object of type " + result.getClass().getName());
|
||||||
|
if (result instanceof TemplateModel) {
|
||||||
|
log.debug("Sending result of invoking method " + method.getName() + " back through getData().");
|
||||||
|
methods.put(methodDisplayName, getData((TemplateModel)result));
|
||||||
|
} else {
|
||||||
|
log.debug("No further analysis on result of invoking method " + method.getName() + " is possible");
|
||||||
|
methods.put(methodDisplayName, result.toString());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error invoking method " + method.getName() + ". Cannot dump value.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
methods.put(methodDisplayName, ""); // or null ?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map.put(Key.VALUE.toString(), methods);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getMethodDisplayName(Method method) {
|
||||||
|
String methodName = method.getName();
|
||||||
|
Class<?>[] paramTypes = method.getParameterTypes();
|
||||||
|
if (paramTypes.length > 0) {
|
||||||
|
List<String> paramTypeList = new ArrayList<String>(paramTypes.length);
|
||||||
|
for (Class<?> cls : paramTypes) {
|
||||||
|
String name = cls.getName();
|
||||||
|
String[] nameParts = name.split("\\.");
|
||||||
|
String typeName = nameParts[nameParts.length-1];
|
||||||
|
typeName = typeName.replaceAll(";", "s");
|
||||||
|
paramTypeList.add(typeName);
|
||||||
|
}
|
||||||
|
methodName += "(" + StringUtils.join(paramTypeList, ", ") + ")";
|
||||||
|
} else {
|
||||||
|
methodName = methodName.replaceAll("^(get|is)", "");
|
||||||
|
methodName = methodName.substring(0, 1).toLowerCase() + methodName.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return methodName;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Method> getMethodsAvailableToTemplate(TemplateHashModelEx model, Object object) throws TemplateModelException {
|
||||||
|
|
||||||
|
// keys() gets only values visible to template based on the BeansWrapper used.
|
||||||
|
// Note: if the BeansWrapper exposure level > BeansWrapper.EXPOSE_PROPERTIES_ONLY,
|
||||||
|
// keys() returns both method and property name for any available method with no
|
||||||
|
// parameters: e.g., both name and getName(). We are going to eliminate the latter.
|
||||||
|
TemplateCollectionModel keys = model.keys();
|
||||||
|
TemplateModelIterator iModel = keys.iterator();
|
||||||
|
Set<String> keySet = new HashSet<String>();
|
||||||
|
while (iModel.hasNext()) {
|
||||||
|
keySet.add(iModel.next().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> cls = object.getClass();
|
||||||
|
Method[] methods = cls.getMethods();
|
||||||
|
Set<Method> availableMethods = new HashSet<Method>();
|
||||||
|
for ( Method method : methods ) {
|
||||||
|
|
||||||
|
// Exclude static methods.
|
||||||
|
// int mod = method.getModifiers();
|
||||||
|
// if (Modifier.isStatic(mod)) {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Eliminate methods declared on Object
|
||||||
|
Class<?> c = method.getDeclaringClass();
|
||||||
|
if (c.equals(java.lang.Object.class)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eliminate deprecated methods
|
||||||
|
if (method.isAnnotationPresent(Deprecated.class)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include only methods included in keys(). This factors in visibility
|
||||||
|
// defined by the model's BeansWrapper.
|
||||||
|
if (keySet.contains(method.getName())) {
|
||||||
|
availableMethods.add(method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return availableMethods;
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, Object> getTemplateModelData(TemplateCollectionModel model) throws TemplateModelException {
|
private Map<String, Object> getTemplateModelData(TemplateCollectionModel model) throws TemplateModelException {
|
||||||
Map<String, Object> map = new HashMap<String, Object>();
|
Map<String, Object> map = new HashMap<String, Object>();
|
||||||
map.put(Key.TYPE.toString(), Type.COLLECTION);
|
map.put(Key.TYPE.toString(), Type.COLLECTION);
|
||||||
|
@ -317,7 +428,6 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel {
|
||||||
template.process(map, sw);
|
template.process(map, sw);
|
||||||
Writer out = env.getOut();
|
Writer out = env.getOut();
|
||||||
out.write(sw.toString());
|
out.write(sw.toString());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -592,16 +592,16 @@ public class DumpDirectiveTest {
|
||||||
|
|
||||||
Calendar c = Calendar.getInstance();
|
Calendar c = Calendar.getInstance();
|
||||||
c.set(75, Calendar.MAY, 5);
|
c.set(75, Calendar.MAY, 5);
|
||||||
Employee jdoe = new Employee("John Doe", c.getTime(), 34523);
|
Employee jdoe = new Employee("John", "Doe", c.getTime(), 34523);
|
||||||
|
|
||||||
c.set(65, Calendar.AUGUST, 10);
|
c.set(65, Calendar.AUGUST, 10);
|
||||||
Employee jsmith = new Employee("Jane Smith", c.getTime(), 11111);
|
Employee jsmith = new Employee("Jane", "Smith", c.getTime(), 78234);
|
||||||
|
|
||||||
c.set(80, Calendar.JUNE, 20);
|
c.set(80, Calendar.JUNE, 20);
|
||||||
Employee mjones = new Employee("Michael Jones", c.getTime(), 22222);
|
Employee mjones = new Employee("Michael", "Jones", c.getTime(), 65432);
|
||||||
|
|
||||||
c.set(81, Calendar.NOVEMBER, 30);
|
c.set(81, Calendar.NOVEMBER, 30);
|
||||||
Employee mturner = new Employee("Mary Turner", c.getTime(), 33333);
|
Employee mturner = new Employee("Mary", "Turner", c.getTime(), 89531);
|
||||||
|
|
||||||
List<Employee> supervisees = new ArrayList<Employee>();
|
List<Employee> supervisees = new ArrayList<Employee>();
|
||||||
supervisees.add(mjones);
|
supervisees.add(mjones);
|
||||||
|
@ -613,7 +613,10 @@ public class DumpDirectiveTest {
|
||||||
dataModel.put("employee", jdoe);
|
dataModel.put("employee", jdoe);
|
||||||
|
|
||||||
Map<String, Object> expectedDump = new HashMap<String, Object>();
|
Map<String, Object> expectedDump = new HashMap<String, Object>();
|
||||||
|
expectedDump.put(Key.NAME.toString(), varName);
|
||||||
|
expectedDump.put(Key.TYPE.toString(), "freemarker.ext.dump.DumpDirectiveTest$Employee");
|
||||||
|
|
||||||
|
//test(varName, dataModel, expectedDump);
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////// Private stub classes and helper methods ///////////////////////////
|
/////////////////////////// Private stub classes and helper methods ///////////////////////////
|
||||||
|
@ -731,55 +734,86 @@ public class DumpDirectiveTest {
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Employee {
|
public static class Employee {
|
||||||
|
|
||||||
private String name;
|
private static int count = 0;
|
||||||
|
|
||||||
|
private String firstName;
|
||||||
|
private String lastName;
|
||||||
|
private String nickname;
|
||||||
private Date birthdate;
|
private Date birthdate;
|
||||||
private int id;
|
private int id;
|
||||||
private Employee supervisor;
|
private Employee supervisor;
|
||||||
private List<Employee> supervisees;
|
private List<Employee> supervisees;
|
||||||
private float salary;
|
private float salary;
|
||||||
|
|
||||||
Employee(String name, Date birthdate, int id) {
|
Employee(String firstName, String lastName, Date birthdate, int id) {
|
||||||
this.name = name;
|
this.firstName = firstName;
|
||||||
|
this.lastName = lastName;
|
||||||
this.birthdate = birthdate;
|
this.birthdate = birthdate;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
this.nickname = "";
|
||||||
|
count++;
|
||||||
String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
Date getBirthdate() {
|
|
||||||
return birthdate;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getId() {
|
|
||||||
return id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSupervisor(Employee supervisor) {
|
void setSupervisor(Employee supervisor) {
|
||||||
this.supervisor = supervisor;
|
this.supervisor = supervisor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Employee getSupervisor() {
|
|
||||||
return supervisor;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setSupervisees(List<Employee> supervisees) {
|
void setSupervisees(List<Employee> supervisees) {
|
||||||
this.supervisees = supervisees;
|
this.supervisees = supervisees;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Employee> getSupervisees() {
|
|
||||||
return supervisees;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setSalary(float salary) {
|
void setSalary(float salary) {
|
||||||
this.salary = salary;
|
this.salary = salary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setNickname(String nickname) {
|
||||||
|
this.nickname = nickname;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not available to templates
|
||||||
float getSalary() {
|
float getSalary() {
|
||||||
return salary;
|
return salary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getEmployeeCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public accessor methods for templates */
|
||||||
|
|
||||||
|
public String getFullName() {
|
||||||
|
return firstName + " " + lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName(String which) {
|
||||||
|
return which == "first" ? firstName : lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNickname() {
|
||||||
|
return nickname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getBirthdate() {
|
||||||
|
return birthdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public int getFormerId() {
|
||||||
|
return id % 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Employee getSupervisor() {
|
||||||
|
return supervisor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Employee> getSupervisees() {
|
||||||
|
return supervisees;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,8 @@ div.dump.var p {
|
||||||
<p><strong>Variable name:</strong> ${var.name}</p>
|
<p><strong>Variable name:</strong> ${var.name}</p>
|
||||||
<p><strong>Type:</strong> ${var.type}</p>
|
<p><strong>Type:</strong> ${var.type}</p>
|
||||||
|
|
||||||
|
<#-- What to do here depends on time. Test either ${var.type} or ${var.value} -->
|
||||||
<p><strong>Value:</strong> ${var.value}</p>
|
<#-- <p><strong>Value:</strong> ${var.value}</p> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<#-- This will work after we move stylesheets to Configuration sharedVariables
|
<#-- This will work after we move stylesheets to Configuration sharedVariables
|
||||||
|
|
Loading…
Add table
Reference in a new issue