View Javadoc

1   /*
2    * Copyright 2002,2004 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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          // we could implement this as
62          //  return findAncestorWithClass(from,Collections.singleton(tagClass));
63          // but this is so simple let's save the object creation for now
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     // Implementation methods
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             // #### should refactor this code into
249             // #### trimWhitespace() methods on the Script objects
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 &lt; and &gt; are
305      * escaped as &amp;lt; and &amp;gt;), which is the default or leave the text as XML.
306      */
307     public void setEscapeText(boolean escapeText) {
308         this.escapeText = escapeText;
309     }
310 }