1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.commons.jelly;
17
18 import java.io.StringWriter;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.Iterator;
22
23 import org.apache.commons.jelly.util.TagUtils;
24
25 /*** <p><code>TagSupport</code> an abstract base class which is useful to
26 * inherit from if developing your own tag.</p>
27 *
28 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
29 * @version $Revision: 155420 $
30 */
31
32 public abstract class TagSupport implements Tag {
33
34 /*** the parent of this tag */
35 protected Tag parent;
36
37 /*** the body of the tag */
38 protected Script body;
39 /*** The current context */
40
41 protected Boolean shouldTrim;
42 protected boolean hasTrimmed;
43
44 protected JellyContext context;
45
46 /*** whether xml text should be escaped */
47 private boolean escapeText = true;
48
49 /***
50 * Searches up the parent hierarchy from the given tag
51 * for a Tag of the given type
52 *
53 * @param from the tag to start searching from
54 * @param tagClass the type of the tag to find
55 * @return the tag of the given type or null if it could not be found
56 */
57 public static Tag findAncestorWithClass(Tag from, Class tagClass) {
58
59
60
61 while (from != null) {
62 if (tagClass.isInstance(from)) {
63 return from;
64 }
65 from = from.getParent();
66 }
67 return null;
68 }
69
70 /***
71 * Searches up the parent hierarchy from the given tag
72 * for a Tag matching one or more of given types.
73 *
74 * @param from the tag to start searching from
75 * @param tagClasses a Collection of Class types that might match
76 * @return the tag of the given type or null if it could not be found
77 */
78 public static Tag findAncestorWithClass(Tag from, Collection tagClasses) {
79 while (from != null) {
80 for(Iterator iter = tagClasses.iterator();iter.hasNext();) {
81 Class klass = (Class)(iter.next());
82 if (klass.isInstance(from)) {
83 return from;
84 }
85 }
86 from = from.getParent();
87 }
88 return null;
89 }
90
91 /***
92 * Searches up the parent hierarchy from the given tag
93 * for a Tag matching one or more of given types.
94 *
95 * @param from the tag to start searching from
96 * @param tagClasses an array of types that might match
97 * @return the tag of the given type or null if it could not be found
98 * @see #findAncestorWithClass(Tag,Collection)
99 */
100 public static Tag findAncestorWithClass(Tag from, Class[] tagClasses) {
101 return findAncestorWithClass(from,Arrays.asList(tagClasses));
102 }
103
104 public TagSupport() {
105 }
106
107 public TagSupport(boolean shouldTrim) {
108 setTrim( shouldTrim );
109 }
110
111 /***
112 * Sets whether whitespace inside this tag should be trimmed or not.
113 * Defaults to true so whitespace is trimmed
114 */
115 public void setTrim(boolean shouldTrim) {
116 if ( shouldTrim ) {
117 this.shouldTrim = Boolean.TRUE;
118 }
119 else {
120 this.shouldTrim = Boolean.FALSE;
121 }
122 }
123
124 public boolean isTrim() {
125 if ( this.shouldTrim == null ) {
126 Tag parent = getParent();
127 if ( parent == null ) {
128 return true;
129 }
130 else {
131 if ( parent instanceof TagSupport ) {
132 TagSupport parentSupport = (TagSupport) parent;
133
134 this.shouldTrim = ( parentSupport.isTrim() ? Boolean.TRUE : Boolean.FALSE );
135 }
136 else {
137 this.shouldTrim = Boolean.TRUE;
138 }
139 }
140 }
141
142 return this.shouldTrim.booleanValue();
143 }
144
145 /*** @return the parent of this tag */
146 public Tag getParent() {
147 return parent;
148 }
149
150 /*** Sets the parent of this tag */
151 public void setParent(Tag parent) {
152 this.parent = parent;
153 }
154
155 /*** @return the body of the tag */
156 public Script getBody() {
157 if (! hasTrimmed) {
158 hasTrimmed = true;
159 if (isTrim()) {
160 trimBody();
161 }
162 }
163 return body;
164 }
165
166 /*** Sets the body of the tag */
167 public void setBody(Script body) {
168 this.body = body;
169 this.hasTrimmed = false;
170 }
171
172 /*** @return the context in which the tag will be run */
173 public JellyContext getContext() {
174 return context;
175 }
176
177 /*** Sets the context in which the tag will be run */
178 public void setContext(JellyContext context) throws JellyTagException {
179 this.context = context;
180 }
181
182 /***
183 * Invokes the body of this tag using the given output
184 */
185 public void invokeBody(XMLOutput output) throws JellyTagException {
186 getBody().run(context, output);
187 }
188
189
190
191 /***
192 * Searches up the parent hierarchy for a Tag of the given type.
193 * @return the tag of the given type or null if it could not be found
194 */
195 protected Tag findAncestorWithClass(Class parentClass) {
196 return findAncestorWithClass(getParent(), parentClass);
197 }
198
199 /***
200 * Searches up the parent hierarchy for a Tag of one of the given types.
201 * @return the tag of the given type or null if it could not be found
202 * @see #findAncestorWithClass(Collection)
203 */
204 protected Tag findAncestorWithClass(Class[] parentClasses) {
205 return findAncestorWithClass(getParent(),parentClasses);
206 }
207
208 /***
209 * Searches up the parent hierarchy for a Tag of one of the given types.
210 * @return the tag of the given type or null if it could not be found
211 */
212 protected Tag findAncestorWithClass(Collection parentClasses) {
213 return findAncestorWithClass(getParent(),parentClasses);
214 }
215
216 /***
217 * Executes the body of the tag and returns the result as a String.
218 *
219 * @return the text evaluation of the body
220 */
221 protected String getBodyText() throws JellyTagException {
222 return getBodyText(escapeText);
223 }
224
225 /***
226 * Executes the body of the tag and returns the result as a String.
227 *
228 * @param shouldEscape Signal if the text should be escaped.
229 *
230 * @return the text evaluation of the body
231 */
232 protected String getBodyText(boolean shouldEscape) throws JellyTagException {
233 StringWriter writer = new StringWriter();
234 invokeBody(XMLOutput.createXMLOutput(writer, shouldEscape));
235 return writer.toString();
236 }
237
238
239 /***
240 * Find all text nodes inside the top level of this body and
241 * if they are just whitespace then remove them
242 */
243 protected void trimBody() {
244 TagUtils.trimScript(body);
245 }
246
247 /***
248 * Returns whether the body of this tag will be escaped or not.
249 */
250 public boolean isEscapeText() {
251 return escapeText;
252 }
253
254 /***
255 * Sets whether the body of the tag should be escaped as text (so that < and > are
256 * escaped as &lt; and &gt;), which is the default or leave the text as XML.
257 */
258 public void setEscapeText(boolean escapeText) {
259 this.escapeText = escapeText;
260 }
261 }