diff --git a/webapp/src/freemarker/ext/dump/BaseDumpDirective.java b/webapp/src/freemarker/ext/dump/BaseDumpDirective.java index 120e39187..a75cad89c 100644 --- a/webapp/src/freemarker/ext/dump/BaseDumpDirective.java +++ b/webapp/src/freemarker/ext/dump/BaseDumpDirective.java @@ -5,6 +5,7 @@ package freemarker.ext.dump; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; +import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; @@ -44,7 +45,18 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel { TemplateHashModel dataModel = env.getDataModel(); TemplateModel model = dataModel.get(varName); - map.putAll(getData(model)); + + // TemplateMethodModel and TemplateDirectiveModel objects can only be + // included in the data model at the top level. + if (model instanceof TemplateMethodModel) { + map.putAll( getTemplateModelData( ( TemplateMethodModel)model, varName ) ); + + } else if (model instanceof TemplateDirectiveModel) { + map.putAll( getTemplateModelData( ( TemplateDirectiveModel)model, varName ) ); + + } else { + map.putAll(getData(model)); + } return map; } @@ -54,9 +66,12 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel { // Don't return null if model == null. We still want to send the map to the template. if (model != null) { - // Do TemplateHashModel case first, because model of POJOs are - // StringModels, which are both TemplateScalarModels and TemplateHashModels. - if (model instanceof TemplateHashModel) { + // Do TemplateHashModel cases first, because models of some Java objects are + // StringModels, and so are both TemplateScalarModels and TemplateHashModelExs. + if (model instanceof TemplateHashModelEx) { + map.putAll( getTemplateModelData( ( TemplateHashModelEx)model ) ); + + } else if (model instanceof TemplateHashModel) { map.putAll( getTemplateModelData( ( TemplateHashModel)model ) ); } else if (model instanceof TemplateScalarModel) { @@ -73,16 +88,7 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel { } else if (model instanceof TemplateSequenceModel){ map.putAll( getTemplateModelData( ( TemplateSequenceModel)model ) ); - - } else if (model instanceof TemplateHashModelEx) { - map.putAll( getTemplateModelData( ( TemplateHashModelEx)model ) ); - - } else if (model instanceof TemplateMethodModel) { - map.putAll( getTemplateModelData( ( TemplateMethodModel)model ) ); - - } else if (model instanceof TemplateDirectiveModel) { - map.putAll( getTemplateModelData( ( TemplateDirectiveModel)model ) ); - + } else { map.putAll( getTemplateModelData( (TemplateModel)model ) ); } @@ -141,23 +147,44 @@ public abstract class BaseDumpDirective implements TemplateDirectiveModel { return map; } - private Map getTemplateModelData(TemplateMethodModel model) throws TemplateModelException { + private Map getTemplateModelData(TemplateMethodModel model, String varName) throws TemplateModelException { Map map = new HashMap(); map.put("type", "Method"); - //map.put("value", model.getAsNumber()); + map.put("help", getHelp(model, varName)); return map; } - private Map getTemplateModelData(TemplateDirectiveModel model) throws TemplateModelException { + private Map getTemplateModelData(TemplateDirectiveModel model, String varName) throws TemplateModelException { Map map = new HashMap(); map.put("type", "Directive");; - //map.put("value", model.getAsDate()); + map.put("help", getHelp(model, varName)); + return map; + } + + @SuppressWarnings("unchecked") + private Map getHelp(TemplateModel model, String varName) { + Map map = null; + if ( model instanceof TemplateMethodModel || model instanceof TemplateDirectiveModel ) { + Class cls = model.getClass(); + Method[] methods = cls.getMethods(); + for (Method method : methods) { + if ( method.getName().equals("help") ) { + try { + map = (Map) method.invoke(model, varName); + } catch (Exception e) { + String modelClass = model instanceof TemplateMethodModel ? "TemplateMethodModel" : "TemplateDirectiveModel"; + log.error("Error invoking method help() on " + modelClass + " of class " + cls.getName()); + } + break; + } + } + } return map; } private Map getTemplateModelData(TemplateModel model) throws TemplateModelException { // One of the above cases should have applied. Track whether this actually occurs. - log.debug("Found model with no known type"); // + log.debug("Found model with no known type"); Map map = new HashMap(); Object unwrappedModel = DeepUnwrap.permissiveUnwrap(model); map.put("type", unwrappedModel.getClass().getName()); diff --git a/webapp/test/freemarker/ext/dump/DumpDirectiveTest.java b/webapp/test/freemarker/ext/dump/DumpDirectiveTest.java index b981ec8a6..fdbfc33ec 100644 --- a/webapp/test/freemarker/ext/dump/DumpDirectiveTest.java +++ b/webapp/test/freemarker/ext/dump/DumpDirectiveTest.java @@ -5,11 +5,14 @@ package freemarker.ext.dump; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.junit.Assert; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.fail; + import org.junit.Before; import org.junit.Test; @@ -34,7 +37,7 @@ public class DumpDirectiveTest { try { template = new Template("template", new StringReader(templateStr), config); } catch (Exception e) { - Assert.fail(e.getMessage()); + fail(e.getMessage()); } } @@ -54,9 +57,9 @@ public class DumpDirectiveTest { try { Environment env = template.createProcessingEnvironment(dataModel, new StringWriter()); Map dumpData = new DumpDirective().getTemplateVariableData(varName, env); - Assert.assertEquals(expected, dumpData); + assertEquals(expected, dumpData); } catch (Exception e) { - Assert.fail(e.getMessage()); + fail(e.getMessage()); } } @@ -76,9 +79,9 @@ public class DumpDirectiveTest { try { Environment env = template.createProcessingEnvironment(dataModel, new StringWriter()); Map dumpData = new DumpDirective().getTemplateVariableData(varName, env); - Assert.assertEquals(expected, dumpData); + assertEquals(expected, dumpData); } catch (Exception e) { - Assert.fail(e.getMessage()); + fail(e.getMessage()); } } @@ -98,9 +101,9 @@ public class DumpDirectiveTest { try { Environment env = template.createProcessingEnvironment(dataModel, new StringWriter()); Map dumpData = new DumpDirective().getTemplateVariableData(varName, env); - Assert.assertEquals(expected, dumpData); + assertEquals(expected, dumpData); } catch (Exception e) { - Assert.fail(e.getMessage()); + fail(e.getMessage()); } } @@ -120,58 +123,144 @@ public class DumpDirectiveTest { try { Environment env = template.createProcessingEnvironment(dataModel, new StringWriter()); Map dumpData = new DumpDirective().getTemplateVariableData(varName, env); - Assert.assertEquals(expected, dumpData); + assertEquals(expected, dumpData); } catch (Exception e) { - Assert.fail(e.getMessage()); + fail(e.getMessage()); } } // RY test method and directive types with and without help methods @Test - public void dumpMethod() { + public void dumpHelplessMethod() { - String varName = "profileUri"; - TemplateMethodModel value = new StubMethodModel(); + String varName = "square"; + TemplateMethodModel value = new HelplessMethod(); Map dataModel = new HashMap(); dataModel.put(varName, value); Map expected = new HashMap(); expected.put("name", varName); expected.put("type", "Method"); - //expected.put("value", value); + expected.put("help", null); try { Environment env = template.createProcessingEnvironment(dataModel, new StringWriter()); Map dumpData = new DumpDirective().getTemplateVariableData(varName, env); - Assert.assertEquals(expected, dumpData); + assertEquals(expected, dumpData); } catch (Exception e) { - Assert.fail(e.getMessage()); + fail(e.getMessage()); } } @Test - public void dumpDirective() { + public void dumpHelpfulMethod() { - String varName = "widget"; - TemplateDirectiveModel value = new StubDirectiveModel(); + String varName = "square"; + TemplateMethodModel value = new HelpfulMethod(); + Map dataModel = new HashMap(); + dataModel.put(varName, value); + + Map expected = new HashMap(); + expected.put("name", varName); + expected.put("type", "Method"); + expected.put("help", getMethodHelp(varName)); + + try { + Environment env = template.createProcessingEnvironment(dataModel, new StringWriter()); + Map dumpData = new DumpDirective().getTemplateVariableData(varName, env); + assertEquals(expected, dumpData); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + @Test + public void dumpMethodWithBadHelp() { + + String varName = "square"; + TemplateMethodModel value = new MethodWithBadHelp(); + Map dataModel = new HashMap(); + dataModel.put(varName, value); + + Map expected = new HashMap(); + expected.put("name", varName); + expected.put("type", "Method"); + expected.put("help", null); + + try { + Environment env = template.createProcessingEnvironment(dataModel, new StringWriter()); + Map dumpData = new DumpDirective().getTemplateVariableData(varName, env); + assertEquals(expected, dumpData); + } catch (Exception e) { + fail(e.getMessage()); + } + } + @Test + public void dumpHelplessDirective() { + + String varName = "dump"; + TemplateDirectiveModel value = new HelplessDirective(); Map dataModel = new HashMap(); dataModel.put(varName, value); Map expected = new HashMap(); expected.put("name", varName); expected.put("type", "Directive"); - //expected.put("value", value); + expected.put("help", null); try { Environment env = template.createProcessingEnvironment(dataModel, new StringWriter()); Map dumpData = new DumpDirective().getTemplateVariableData(varName, env); - Assert.assertEquals(expected, dumpData); + assertEquals(expected, dumpData); } catch (Exception e) { - Assert.fail(e.getMessage()); + fail(e.getMessage()); } } - + + @Test + public void dumpHelpfulDirective() { + + String varName = "dump"; + TemplateDirectiveModel value = new HelpfulDirective(); + Map dataModel = new HashMap(); + dataModel.put(varName, value); + + Map expected = new HashMap(); + expected.put("name", varName); + expected.put("type", "Directive"); + expected.put("help", getDirectiveHelp(varName)); + + try { + Environment env = template.createProcessingEnvironment(dataModel, new StringWriter()); + Map dumpData = new DumpDirective().getTemplateVariableData(varName, env); + assertEquals(expected, dumpData); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + @Test + public void dumpDirectiveWithBadHelp() { + + String varName = "dump"; + TemplateDirectiveModel value = new DirectiveWithBadHelp(); + Map dataModel = new HashMap(); + dataModel.put(varName, value); + + Map expected = new HashMap(); + expected.put("name", varName); + expected.put("type", "Directive"); + expected.put("help", null); + + try { + Environment env = template.createProcessingEnvironment(dataModel, new StringWriter()); + Map dumpData = new DumpDirective().getTemplateVariableData(varName, env); + assertEquals(expected, dumpData); + } catch (Exception e) { + fail(e.getMessage()); + } + } // RY Do these with different BeansWrappers @Test public void dumpSequence() { @@ -188,22 +277,108 @@ public class DumpDirectiveTest { } - private class StubMethodModel implements TemplateMethodModel { + private class HelplessMethod implements TemplateMethodModel { @Override public Object exec(List arg0) throws TemplateModelException { return null; } } + + private class HelpfulMethod implements TemplateMethodModel { + + @Override + public Object exec(List arg0) throws TemplateModelException { + return null; + } + + public Map help(String name) { + return getMethodHelp(name); + } + } + + private class MethodWithBadHelp implements TemplateMethodModel { + + @Override + public Object exec(List arg0) throws TemplateModelException { + return null; + } + + public Map help() { + return new HashMap(); + } + } - private class StubDirectiveModel implements TemplateDirectiveModel { + private class HelplessDirective implements TemplateDirectiveModel { @Override public void execute(Environment arg0, Map arg1, TemplateModel[] arg2, TemplateDirectiveBody arg3) throws TemplateException, - IOException { - + IOException { + } + } + + private class HelpfulDirective implements TemplateDirectiveModel { + + @Override + public void execute(Environment arg0, Map arg1, TemplateModel[] arg2, + TemplateDirectiveBody arg3) throws TemplateException, + IOException { } + public Map help(String name) { + return getDirectiveHelp(name); + } } + + private class DirectiveWithBadHelp implements TemplateDirectiveModel { + + @Override + public void execute(Environment arg0, Map arg1, TemplateModel[] arg2, + TemplateDirectiveBody arg3) throws TemplateException, + IOException { + } + + public String help(String name) { + return "help"; + } + } + + private Map getDirectiveHelp(String name) { + Map map = new HashMap(); + + map.put("effect", "Dump the contents of a template variable."); + + map.put("comments", "Sequences (lists and arrays) are enclosed in square brackets. Hashes are enclosed in curly braces."); + + Map params = new HashMap(); + params.put("var", "name of variable to dump"); + map.put("params", params); + + List examples = new ArrayList(); + examples.add("<@" + name + " var=\"urls\" />"); + map.put("examples", examples); + + return map; + } + + private Map getMethodHelp(String name) { + Map map = new HashMap(); + + map.put("name", name); + + map.put("returns", "The square of the argument"); + + Listparams = new ArrayList(); + params.add("Integer to square"); + map.put("params", params); + + List examples = new ArrayList(); + examples.add(name + "(4)"); + map.put("examples", examples); + + return map; + } + + }