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  
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          // we could implement this as
59          //  return findAncestorWithClass(from,Collections.singleton(tagClass));
60          // but this is so simple let's save the object creation for now
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     // Implementation methods
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 &lt; and &gt; are
256      * escaped as &amp;lt; and &amp;gt;), which is the default or leave the text as XML.
257      */
258     public void setEscapeText(boolean escapeText) {
259         this.escapeText = escapeText;
260     }
261 }