1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.struts2.views.freemarker;
22
23 import java.util.Map;
24 import java.util.Set;
25
26 import org.apache.struts2.StrutsConstants;
27
28 import freemarker.core.CollectionAndSequence;
29 import freemarker.ext.beans.BeansWrapper;
30 import freemarker.ext.beans.MapModel;
31 import freemarker.ext.util.ModelFactory;
32 import freemarker.template.ObjectWrapper;
33 import freemarker.template.SimpleSequence;
34 import freemarker.template.TemplateBooleanModel;
35 import freemarker.template.TemplateCollectionModel;
36 import freemarker.template.TemplateHashModelEx;
37 import freemarker.template.TemplateModel;
38 import freemarker.template.TemplateModelException;
39
40 /***
41 * <!-- START SNIPPET: javadoc -->
42 *
43 * The StrutsBeanWrapper extends the default FreeMarker BeansWrapper and provides almost no change in functionality,
44 * <b>except</b> for how it handles maps. Normally, FreeMarker has two modes of operation: either support for friendly
45 * map built-ins (?keys, ?values, etc) but only support for String keys; OR no special built-in support (ie: ?keys
46 * returns the methods on the map instead of the keys) but support for String and non-String keys alike. Struts
47 * provides an alternative implementation that gives us the best of both worlds.
48 *
49 * <p/> It is possible that this special behavior may be confusing or can cause problems. Therefore, you can set the
50 * <b>struts.freemarker.wrapper.altMap</b> property in struts.properties to false, allowing the normal BeansWrapper
51 * logic to take place instead.
52 *
53 * <!-- END SNIPPET: javadoc -->
54 */
55 public class StrutsBeanWrapper extends BeansWrapper {
56 private boolean altMapWrapper;
57
58 StrutsBeanWrapper(boolean altMapWrapper) {
59 this.altMapWrapper = altMapWrapper;
60 }
61
62 public TemplateModel wrap(Object object) throws TemplateModelException {
63 if (object instanceof TemplateBooleanModel) {
64 return super.wrap(object);
65 }
66
67
68 if (altMapWrapper && object instanceof Map) {
69 return getInstance(object, FriendlyMapModel.FACTORY);
70 }
71
72 return super.wrap(object);
73 }
74
75 /***
76 * Attempting to get the best of both worlds of FM's MapModel and SimpleMapModel, by reimplementing the isEmpty(),
77 * keySet() and values() methods. ?keys and ?values built-ins are thus available, just as well as plain Map
78 * methods.
79 */
80 private final static class FriendlyMapModel extends MapModel implements TemplateHashModelEx {
81 static final ModelFactory FACTORY = new ModelFactory() {
82 public TemplateModel create(Object object, ObjectWrapper wrapper) {
83 return new FriendlyMapModel((Map) object, (BeansWrapper) wrapper);
84 }
85 };
86
87 public FriendlyMapModel(Map map, BeansWrapper wrapper) {
88 super(map, wrapper);
89 }
90
91 public boolean isEmpty() {
92 return ((Map) object).isEmpty();
93 }
94
95 protected Set keySet() {
96 return ((Map) object).keySet();
97 }
98
99 public TemplateCollectionModel values() {
100 return new CollectionAndSequence(new SimpleSequence(((Map) object).values(), wrapper));
101 }
102 }
103 }