NIHVIVO-3087 Display information on object method parameters, values, and return type in dump
This commit is contained in:
parent
7df0381a1e
commit
f49460245b
5 changed files with 101 additions and 33 deletions
|
@ -181,4 +181,12 @@ public abstract class BaseIndividualTemplateModel extends BaseTemplateModel {
|
||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String ageInUnits(String units) {
|
||||||
|
return "5 " + units;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int age() {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
package freemarker.ext.beans;
|
package freemarker.ext.beans;
|
||||||
|
|
||||||
import freemarker.template.TemplateModel;
|
import java.lang.reflect.Member;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to extract information about the wrapper used to wrap an object in
|
* Class to extract information about the wrapper used to wrap an object in
|
||||||
|
@ -17,4 +18,8 @@ public class WrapperExtractor {
|
||||||
public static int getWrapperExposureLevel(BeanModel model) {
|
public static int getWrapperExposureLevel(BeanModel model) {
|
||||||
return model.wrapper.getExposureLevel();
|
return model.wrapper.getExposureLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Member getMember(SimpleMethodModel model) {
|
||||||
|
return model.getMember();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@ 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.Member;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -23,8 +23,11 @@ import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import freemarker.core.Environment;
|
import freemarker.core.Environment;
|
||||||
|
import freemarker.ext.beans.BeansWrapper;
|
||||||
import freemarker.ext.beans.CollectionModel;
|
import freemarker.ext.beans.CollectionModel;
|
||||||
|
import freemarker.ext.beans.SimpleMethodModel;
|
||||||
import freemarker.ext.beans.StringModel;
|
import freemarker.ext.beans.StringModel;
|
||||||
|
import freemarker.ext.beans.WrapperExtractor;
|
||||||
import freemarker.template.Template;
|
import freemarker.template.Template;
|
||||||
import freemarker.template.TemplateBooleanModel;
|
import freemarker.template.TemplateBooleanModel;
|
||||||
import freemarker.template.TemplateCollectionModel;
|
import freemarker.template.TemplateCollectionModel;
|
||||||
|
@ -54,6 +57,8 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel {
|
||||||
private static final String TEMPLATE_DEFAULT = "dump.ftl"; // change to dump.ftl when old dump is removed
|
private static final String TEMPLATE_DEFAULT = "dump.ftl"; // change to dump.ftl when old dump is removed
|
||||||
private static final Pattern PROPERTY_NAME_PATTERN = Pattern.compile("^(get|is)\\w");
|
private static final Pattern PROPERTY_NAME_PATTERN = Pattern.compile("^(get|is)\\w");
|
||||||
|
|
||||||
|
private BeansWrapper wrapper;
|
||||||
|
|
||||||
enum Key {
|
enum Key {
|
||||||
CLASS("class"),
|
CLASS("class"),
|
||||||
DATE_TYPE("dateType"),
|
DATE_TYPE("dateType"),
|
||||||
|
@ -323,7 +328,7 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel {
|
||||||
|
|
||||||
// Compile the collections of properties and methods available to the template
|
// Compile the collections of properties and methods available to the template
|
||||||
SortedMap<String, Object> properties = new TreeMap<String, Object>();
|
SortedMap<String, Object> properties = new TreeMap<String, Object>();
|
||||||
List<String> methods = new ArrayList<String>();
|
SortedMap<String, Object> methods = new TreeMap<String, Object>();
|
||||||
|
|
||||||
// keys() gets only values visible to template based on the BeansWrapper used.
|
// keys() gets only values visible to template based on the BeansWrapper used.
|
||||||
// Note: if the BeansWrapper exposure level > BeansWrapper.EXPOSE_PROPERTIES_ONLY,
|
// Note: if the BeansWrapper exposure level > BeansWrapper.EXPOSE_PROPERTIES_ONLY,
|
||||||
|
@ -380,18 +385,38 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel {
|
||||||
// Else look for the entire methodName in the key set. Include those
|
// Else look for the entire methodName in the key set. Include those
|
||||||
// starting with "get" or "is" that were not found above.
|
// starting with "get" or "is" that were not found above.
|
||||||
// NB This does not properly account for methods exposed as properties
|
// NB This does not properly account for methods exposed as properties
|
||||||
// using BeansWrapper.finetuneMethodAppearance().
|
// using BeansWrapper.finetuneMethodAppearance(), and perhaps other
|
||||||
|
// changes to method exposure through that method.
|
||||||
if (keySet.contains(methodName)) {
|
if (keySet.contains(methodName)) {
|
||||||
String methodDisplayName = getMethodDisplayName(method);
|
String methodDisplayName = getMethodDisplayName(method);
|
||||||
methods.add(methodDisplayName);
|
if ( methodDisplayName.endsWith(")") ) {
|
||||||
|
String returnTypeName = getTypeName(method.getReturnType());
|
||||||
|
Map<String, String> methodValue = new HashMap<String, String>();
|
||||||
|
if ( ! returnTypeName.equals("void") ) {
|
||||||
|
methodValue.put(Key.TYPE.toString(), returnTypeName);
|
||||||
|
}
|
||||||
|
methods.put(methodDisplayName, methodValue);
|
||||||
|
} else {
|
||||||
|
SimpleMethodModel methodModel = (SimpleMethodModel)model.get(methodName);
|
||||||
|
Member member = WrapperExtractor.getMember(methodModel);
|
||||||
|
try {
|
||||||
|
if (member instanceof Method) {
|
||||||
|
Method m = (Method) member;
|
||||||
|
Object result = m.invoke(object);
|
||||||
|
// But we need to use the same wrapper that wrapped it
|
||||||
|
TemplateModel wrappedResult = new BeansWrapper().wrap(result);
|
||||||
|
methods.put(methodDisplayName, getDump(wrappedResult));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> objectValue = new HashMap<String, Object>(2);
|
Map<String, Object> objectValue = new HashMap<String, Object>(2);
|
||||||
objectValue.put(Key.PROPERTIES.toString(), properties);
|
objectValue.put(Key.PROPERTIES.toString(), properties);
|
||||||
|
|
||||||
Collections.sort(methods);
|
|
||||||
objectValue.put(Key.METHODS.toString(), methods);
|
objectValue.put(Key.METHODS.toString(), methods);
|
||||||
|
|
||||||
map.put(Key.VALUE.toString(), objectValue);
|
map.put(Key.VALUE.toString(), objectValue);
|
||||||
|
@ -404,18 +429,17 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel {
|
||||||
if (paramTypes.length > 0) {
|
if (paramTypes.length > 0) {
|
||||||
List<String> paramTypeList = new ArrayList<String>(paramTypes.length);
|
List<String> paramTypeList = new ArrayList<String>(paramTypes.length);
|
||||||
for (Class<?> cls : paramTypes) {
|
for (Class<?> cls : paramTypes) {
|
||||||
String name = cls.getName();
|
paramTypeList.add(getTypeName(cls));
|
||||||
String[] nameParts = name.split("\\.");
|
|
||||||
String typeName = nameParts[nameParts.length-1];
|
|
||||||
typeName = typeName.replaceAll(";", "s");
|
|
||||||
paramTypeList.add(typeName);
|
|
||||||
}
|
}
|
||||||
methodName += "(" + StringUtils.join(paramTypeList, ", ") + ")";
|
methodName += "(" + StringUtils.join(paramTypeList, ", ") + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
return methodName;
|
return methodName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getTypeName(Class<?> cls) {
|
||||||
|
return cls.getSimpleName().replace("[]", "s");
|
||||||
|
}
|
||||||
|
|
||||||
// Return the method name as it is represented in TemplateHashModelEx.keys()
|
// Return the method name as it is represented in TemplateHashModelEx.keys()
|
||||||
private String getPropertyName(String methodName) {
|
private String getPropertyName(String methodName) {
|
||||||
String keyName = methodName.replaceAll("^(get|is)", "");
|
String keyName = methodName.replaceAll("^(get|is)", "");
|
||||||
|
@ -464,7 +488,6 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel {
|
||||||
log.error("Method help() of " + modelClass + " of class " + cls.getName() + " has incorrect return type.");
|
log.error("Method help() of " + modelClass + " of class " + cls.getName() + " has incorrect return type.");
|
||||||
return null;
|
return null;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
//
|
|
||||||
log.error("Error invoking method help() on " + modelClass + " of class " + cls.getName());
|
log.error("Error invoking method help() on " + modelClass + " of class " + cls.getName());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -479,6 +502,14 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// private Map<String, Object> getSimpleMethodModelDump(Object object, SimpleMethodModel model) throws TemplateModelException {
|
||||||
|
// Map<String, Object> map = new HashMap<String, Object>();
|
||||||
|
// Method method = (Method)DeepUnwrap.permissiveUnwrap(model);
|
||||||
|
// TemplateModel value = model.get(method.getName());
|
||||||
|
// map.put(Key.VALUE.toString(), getDump(value));
|
||||||
|
// return map;
|
||||||
|
// }
|
||||||
|
|
||||||
private Map<String, Object> getTemplateModelDump(TemplateModel model) throws TemplateModelException {
|
private Map<String, Object> getTemplateModelDump(TemplateModel model) throws TemplateModelException {
|
||||||
// One of the more specific cases should have applied. Track whether this actually occurs.
|
// One of the more specific cases should have applied. Track whether this actually occurs.
|
||||||
log.debug("Found template model of type " + model.getClass().getName());
|
log.debug("Found template model of type " + model.getClass().getName());
|
||||||
|
|
|
@ -1122,6 +1122,10 @@ public class DumpDirectiveTest {
|
||||||
public List<String> getFavoriteColors() {
|
public List<String> getFavoriteColors() {
|
||||||
return favoriteColors;
|
return favoriteColors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String familyName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Employee getEmployee() {
|
private Employee getEmployee() {
|
||||||
|
@ -1210,21 +1214,36 @@ public class DumpDirectiveTest {
|
||||||
expectedDump.put(Key.PROPERTIES.toString(), propertiesExpectedDump);
|
expectedDump.put(Key.PROPERTIES.toString(), propertiesExpectedDump);
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
expectedDump.put(Key.METHODS.toString(), getEmployeeMethodsExpectedDump(exposureLevel));
|
expectedDump.put(Key.METHODS.toString(), getEmployeeMethodsExpectedDump(exposureLevel, "Doe"));
|
||||||
|
|
||||||
return expectedDump;
|
return expectedDump;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getEmployeeMethodsExpectedDump(int exposureLevel) {
|
private SortedMap<String, Object> getEmployeeMethodsExpectedDump(int exposureLevel, String familyName) {
|
||||||
|
|
||||||
|
SortedMap<String, Object> expectedDump = new TreeMap<String, Object>();
|
||||||
|
|
||||||
List<String> expectedDump = new ArrayList<String>();
|
|
||||||
if (exposureLevel <= BeansWrapper.EXPOSE_SAFE) {
|
if (exposureLevel <= BeansWrapper.EXPOSE_SAFE) {
|
||||||
expectedDump.add("getEmployeeCount");
|
|
||||||
expectedDump.add("getName(String)");
|
Map<String, Object> nameExpectedDump = new HashMap<String, Object>();
|
||||||
expectedDump.add("setFavoriteColors(Strings)");
|
nameExpectedDump.put(Key.TYPE.toString(), "String");
|
||||||
expectedDump.add("setNickname(String)");
|
expectedDump.put("getName(String)", nameExpectedDump);
|
||||||
|
|
||||||
|
expectedDump.put("setFavoriteColors(Strings)", Collections.emptyMap());
|
||||||
|
|
||||||
|
expectedDump.put("setNickname(String)", Collections.emptyMap());
|
||||||
|
|
||||||
|
Map<String, Object> familyNameExpectedDump = new HashMap<String, Object>();
|
||||||
|
familyNameExpectedDump.put(Key.TYPE.toString(), Type.STRING);
|
||||||
|
familyNameExpectedDump.put(Key.VALUE.toString(), familyName);
|
||||||
|
expectedDump.put("familyName", familyNameExpectedDump);
|
||||||
|
|
||||||
|
Map<String, Object> employeeCountExpectedDump = new HashMap<String, Object>();
|
||||||
|
employeeCountExpectedDump.put(Key.TYPE.toString(), Type.NUMBER);
|
||||||
|
employeeCountExpectedDump.put(Key.VALUE.toString(), Employee.getEmployeeCount());
|
||||||
|
expectedDump.put("getEmployeeCount", employeeCountExpectedDump);
|
||||||
}
|
}
|
||||||
Collections.sort(expectedDump);
|
|
||||||
return expectedDump;
|
return expectedDump;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1291,7 +1310,7 @@ public class DumpDirectiveTest {
|
||||||
expectedDump.put(Key.PROPERTIES.toString(), propertiesExpectedDump);
|
expectedDump.put(Key.PROPERTIES.toString(), propertiesExpectedDump);
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
expectedDump.put(Key.METHODS.toString(), getEmployeeMethodsExpectedDump(exposureLevel));
|
expectedDump.put(Key.METHODS.toString(), getEmployeeMethodsExpectedDump(exposureLevel, "Smith"));
|
||||||
|
|
||||||
return expectedDump;
|
return expectedDump;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,10 +36,6 @@ div.dump {
|
||||||
.dump ul li.item .value {
|
.dump ul li.item .value {
|
||||||
margin-left: 1.5em;
|
margin-left: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dump ul.methods li {
|
|
||||||
margin-bottom: .25em;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="dump">
|
<div class="dump">
|
||||||
|
@ -78,7 +74,7 @@ div.dump {
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
<#local value = map.value!>
|
<#local value = map.value!>
|
||||||
<#if value??>
|
<#if value?has_content>
|
||||||
<div class="values">
|
<div class="values">
|
||||||
<#if type?contains(".")><@doObjectValue value />
|
<#if type?contains(".")><@doObjectValue value />
|
||||||
<#elseif value?is_sequence><@doSequenceValue value type />
|
<#elseif value?is_sequence><@doSequenceValue value type />
|
||||||
|
@ -104,8 +100,17 @@ div.dump {
|
||||||
<#if obj.methods?has_content>
|
<#if obj.methods?has_content>
|
||||||
<p><strong>Methods:</strong</p>
|
<p><strong>Methods:</strong</p>
|
||||||
<ul class="methods">
|
<ul class="methods">
|
||||||
<#list obj.methods as method>
|
<#list obj.methods?keys as method>
|
||||||
<@liItem>${method}</@liItem>
|
<#local value = obj.methods[method]>
|
||||||
|
<@liItem>
|
||||||
|
<#if ! value?has_content> <#-- no return value -->
|
||||||
|
${method}
|
||||||
|
<#elseif value?is_string> <#-- value is return type -->
|
||||||
|
${method} => ${value}
|
||||||
|
<#else> <#-- no-arg method: value is result of method invocation -->
|
||||||
|
${method} => <@divValue><@doTypeAndValue value /></@divValue>
|
||||||
|
</#if>
|
||||||
|
</@liItem>
|
||||||
</#list>
|
</#list>
|
||||||
</ul>
|
</ul>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue