NIHVIVO-3087 Display information on object method parameters, values, and return type in dump

This commit is contained in:
ryounes 2011-08-04 16:07:03 +00:00
parent 7df0381a1e
commit f49460245b
5 changed files with 101 additions and 33 deletions

View file

@ -181,4 +181,12 @@ public abstract class BaseIndividualTemplateModel extends BaseTemplateModel {
}
return id;
}
public String ageInUnits(String units) {
return "5 " + units;
}
public int age() {
return 10;
}
}

View file

@ -2,7 +2,8 @@
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
@ -17,4 +18,8 @@ public class WrapperExtractor {
public static int getWrapperExposureLevel(BeanModel model) {
return model.wrapper.getExposureLevel();
}
public static Member getMember(SimpleMethodModel model) {
return model.getMember();
}
}

View file

@ -5,9 +5,9 @@ package freemarker.ext.dump;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -23,8 +23,11 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import freemarker.core.Environment;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.CollectionModel;
import freemarker.ext.beans.SimpleMethodModel;
import freemarker.ext.beans.StringModel;
import freemarker.ext.beans.WrapperExtractor;
import freemarker.template.Template;
import freemarker.template.TemplateBooleanModel;
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 Pattern PROPERTY_NAME_PATTERN = Pattern.compile("^(get|is)\\w");
private BeansWrapper wrapper;
enum Key {
CLASS("class"),
DATE_TYPE("dateType"),
@ -323,7 +328,7 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel {
// Compile the collections of properties and methods available to the template
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.
// 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
// starting with "get" or "is" that were not found above.
// 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)) {
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);
objectValue.put(Key.PROPERTIES.toString(), properties);
Collections.sort(methods);
objectValue.put(Key.METHODS.toString(), methods);
map.put(Key.VALUE.toString(), objectValue);
@ -404,18 +429,17 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel {
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);
paramTypeList.add(getTypeName(cls));
}
methodName += "(" + StringUtils.join(paramTypeList, ", ") + ")";
}
return methodName;
}
private String getTypeName(Class<?> cls) {
return cls.getSimpleName().replace("[]", "s");
}
// Return the method name as it is represented in TemplateHashModelEx.keys()
private String getPropertyName(String methodName) {
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.");
return null;
} catch (Exception e) {
//
log.error("Error invoking method help() on " + modelClass + " of class " + cls.getName());
return null;
}
@ -479,6 +502,14 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel {
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 {
// One of the more specific cases should have applied. Track whether this actually occurs.
log.debug("Found template model of type " + model.getClass().getName());

View file

@ -1122,6 +1122,10 @@ public class DumpDirectiveTest {
public List<String> getFavoriteColors() {
return favoriteColors;
}
public String familyName() {
return lastName;
}
}
private Employee getEmployee() {
@ -1210,21 +1214,36 @@ public class DumpDirectiveTest {
expectedDump.put(Key.PROPERTIES.toString(), propertiesExpectedDump);
// Methods
expectedDump.put(Key.METHODS.toString(), getEmployeeMethodsExpectedDump(exposureLevel));
expectedDump.put(Key.METHODS.toString(), getEmployeeMethodsExpectedDump(exposureLevel, "Doe"));
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) {
expectedDump.add("getEmployeeCount");
expectedDump.add("getName(String)");
expectedDump.add("setFavoriteColors(Strings)");
expectedDump.add("setNickname(String)");
Map<String, Object> nameExpectedDump = new HashMap<String, Object>();
nameExpectedDump.put(Key.TYPE.toString(), "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;
}
@ -1291,7 +1310,7 @@ public class DumpDirectiveTest {
expectedDump.put(Key.PROPERTIES.toString(), propertiesExpectedDump);
// Methods
expectedDump.put(Key.METHODS.toString(), getEmployeeMethodsExpectedDump(exposureLevel));
expectedDump.put(Key.METHODS.toString(), getEmployeeMethodsExpectedDump(exposureLevel, "Smith"));
return expectedDump;
}

View file

@ -36,10 +36,6 @@ div.dump {
.dump ul li.item .value {
margin-left: 1.5em;
}
.dump ul.methods li {
margin-bottom: .25em;
}
</style>
<div class="dump">
@ -78,7 +74,7 @@ div.dump {
</#if>
<#local value = map.value!>
<#if value??>
<#if value?has_content>
<div class="values">
<#if type?contains(".")><@doObjectValue value />
<#elseif value?is_sequence><@doSequenceValue value type />
@ -104,8 +100,17 @@ div.dump {
<#if obj.methods?has_content>
<p><strong>Methods:</strong</p>
<ul class="methods">
<#list obj.methods as method>
<@liItem>${method}</@liItem>
<#list obj.methods?keys as method>
<#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>
</ul>
</#if>