diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/BaseIndividualTemplateModel.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/BaseIndividualTemplateModel.java index 5db8cc503..56bb8ecf2 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/BaseIndividualTemplateModel.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/BaseIndividualTemplateModel.java @@ -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; + } } diff --git a/webapp/src/freemarker/ext/beans/WrapperExtractor.java b/webapp/src/freemarker/ext/beans/WrapperExtractor.java index 808521c7a..6aeb72fa2 100644 --- a/webapp/src/freemarker/ext/beans/WrapperExtractor.java +++ b/webapp/src/freemarker/ext/beans/WrapperExtractor.java @@ -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(); + } } diff --git a/webapp/src/freemarker/ext/dump/BaseDumpDirective.java b/webapp/src/freemarker/ext/dump/BaseDumpDirective.java index b5c41b9bf..76c7b643f 100644 --- a/webapp/src/freemarker/ext/dump/BaseDumpDirective.java +++ b/webapp/src/freemarker/ext/dump/BaseDumpDirective.java @@ -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 properties = new TreeMap(); - List methods = new ArrayList(); + SortedMap methods = new TreeMap(); // keys() gets only values visible to template based on the BeansWrapper used. // Note: if the BeansWrapper exposure level > BeansWrapper.EXPOSE_PROPERTIES_ONLY, @@ -380,20 +385,40 @@ 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); + String methodDisplayName = getMethodDisplayName(method); + if ( methodDisplayName.endsWith(")") ) { + String returnTypeName = getTypeName(method.getReturnType()); + Map methodValue = new HashMap(); + 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 objectValue = new HashMap(2); objectValue.put(Key.PROPERTIES.toString(), properties); - - Collections.sort(methods); objectValue.put(Key.METHODS.toString(), methods); - + map.put(Key.VALUE.toString(), objectValue); return map; } @@ -404,17 +429,16 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel { if (paramTypes.length > 0) { List paramTypeList = new ArrayList(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) { @@ -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 getSimpleMethodModelDump(Object object, SimpleMethodModel model) throws TemplateModelException { +// Map map = new HashMap(); +// Method method = (Method)DeepUnwrap.permissiveUnwrap(model); +// TemplateModel value = model.get(method.getName()); +// map.put(Key.VALUE.toString(), getDump(value)); +// return map; +// } + private Map 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()); diff --git a/webapp/test/freemarker/ext/dump/DumpDirectiveTest.java b/webapp/test/freemarker/ext/dump/DumpDirectiveTest.java index 7791d91ad..4880a9882 100644 --- a/webapp/test/freemarker/ext/dump/DumpDirectiveTest.java +++ b/webapp/test/freemarker/ext/dump/DumpDirectiveTest.java @@ -1122,6 +1122,10 @@ public class DumpDirectiveTest { public List 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 getEmployeeMethodsExpectedDump(int exposureLevel) { + private SortedMap getEmployeeMethodsExpectedDump(int exposureLevel, String familyName) { + + SortedMap expectedDump = new TreeMap(); - List expectedDump = new ArrayList(); if (exposureLevel <= BeansWrapper.EXPOSE_SAFE) { - expectedDump.add("getEmployeeCount"); - expectedDump.add("getName(String)"); - expectedDump.add("setFavoriteColors(Strings)"); - expectedDump.add("setNickname(String)"); + + Map nameExpectedDump = new HashMap(); + nameExpectedDump.put(Key.TYPE.toString(), "String"); + expectedDump.put("getName(String)", nameExpectedDump); + + expectedDump.put("setFavoriteColors(Strings)", Collections.emptyMap()); + + expectedDump.put("setNickname(String)", Collections.emptyMap()); + + Map familyNameExpectedDump = new HashMap(); + familyNameExpectedDump.put(Key.TYPE.toString(), Type.STRING); + familyNameExpectedDump.put(Key.VALUE.toString(), familyName); + expectedDump.put("familyName", familyNameExpectedDump); + + Map employeeCountExpectedDump = new HashMap(); + 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; } diff --git a/webapp/web/templates/freemarker/body/partials/dump/dump.ftl b/webapp/web/templates/freemarker/body/partials/dump/dump.ftl index 581fb09f7..27c5b2bbf 100644 --- a/webapp/web/templates/freemarker/body/partials/dump/dump.ftl +++ b/webapp/web/templates/freemarker/body/partials/dump/dump.ftl @@ -36,10 +36,6 @@ div.dump { .dump ul li.item .value { margin-left: 1.5em; } - -.dump ul.methods li { - margin-bottom: .25em; -}
@@ -78,7 +74,7 @@ div.dump { <#local value = map.value!> - <#if value??> + <#if value?has_content>
<#if type?contains(".")><@doObjectValue value /> <#elseif value?is_sequence><@doSequenceValue value type /> @@ -104,8 +100,17 @@ div.dump { <#if obj.methods?has_content>

Methods:

    - <#list obj.methods as method> - <@liItem>${method} + <#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 /> + +