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