001: /*
002: * Copyright 2002,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.jelly;
017:
018: import java.io.StringWriter;
019: import java.util.Arrays;
020: import java.util.Collection;
021: import java.util.Iterator;
022:
023: import org.apache.commons.jelly.util.TagUtils;
024:
025: /** <p><code>TagSupport</code> an abstract base class which is useful to
026: * inherit from if developing your own tag.</p>
027: *
028: * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
029: * @version $Revision: 155420 $
030: */
031:
032: public abstract class TagSupport implements Tag {
033:
034: /** the parent of this tag */
035: protected Tag parent;
036:
037: /** the body of the tag */
038: protected Script body;
039: /** The current context */
040:
041: protected Boolean shouldTrim;
042: protected boolean hasTrimmed;
043:
044: protected JellyContext context;
045:
046: /** whether xml text should be escaped */
047: private boolean escapeText = true;
048:
049: /**
050: * Searches up the parent hierarchy from the given tag
051: * for a Tag of the given type
052: *
053: * @param from the tag to start searching from
054: * @param tagClass the type of the tag to find
055: * @return the tag of the given type or null if it could not be found
056: */
057: public static Tag findAncestorWithClass(Tag from, Class tagClass) {
058: // we could implement this as
059: // return findAncestorWithClass(from,Collections.singleton(tagClass));
060: // but this is so simple let's save the object creation for now
061: while (from != null) {
062: if (tagClass.isInstance(from)) {
063: return from;
064: }
065: from = from.getParent();
066: }
067: return null;
068: }
069:
070: /**
071: * Searches up the parent hierarchy from the given tag
072: * for a Tag matching one or more of given types.
073: *
074: * @param from the tag to start searching from
075: * @param tagClasses a Collection of Class types that might match
076: * @return the tag of the given type or null if it could not be found
077: */
078: public static Tag findAncestorWithClass(Tag from,
079: Collection tagClasses) {
080: while (from != null) {
081: for (Iterator iter = tagClasses.iterator(); iter.hasNext();) {
082: Class klass = (Class) (iter.next());
083: if (klass.isInstance(from)) {
084: return from;
085: }
086: }
087: from = from.getParent();
088: }
089: return null;
090: }
091:
092: /**
093: * Searches up the parent hierarchy from the given tag
094: * for a Tag matching one or more of given types.
095: *
096: * @param from the tag to start searching from
097: * @param tagClasses an array of types that might match
098: * @return the tag of the given type or null if it could not be found
099: * @see #findAncestorWithClass(Tag,Collection)
100: */
101: public static Tag findAncestorWithClass(Tag from, Class[] tagClasses) {
102: return findAncestorWithClass(from, Arrays.asList(tagClasses));
103: }
104:
105: public TagSupport() {
106: }
107:
108: public TagSupport(boolean shouldTrim) {
109: setTrim(shouldTrim);
110: }
111:
112: /**
113: * Sets whether whitespace inside this tag should be trimmed or not.
114: * Defaults to true so whitespace is trimmed
115: */
116: public void setTrim(boolean shouldTrim) {
117: if (shouldTrim) {
118: this .shouldTrim = Boolean.TRUE;
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: } else {
130: if (parent instanceof TagSupport) {
131: TagSupport parentSupport = (TagSupport) parent;
132:
133: this .shouldTrim = (parentSupport.isTrim() ? Boolean.TRUE
134: : Boolean.FALSE);
135: } else {
136: this .shouldTrim = Boolean.TRUE;
137: }
138: }
139: }
140:
141: return this .shouldTrim.booleanValue();
142: }
143:
144: /** @return the parent of this tag */
145: public Tag getParent() {
146: return parent;
147: }
148:
149: /** Sets the parent of this tag */
150: public void setParent(Tag parent) {
151: this .parent = parent;
152: }
153:
154: /** @return the body of the tag */
155: public Script getBody() {
156: if (!hasTrimmed) {
157: hasTrimmed = true;
158: if (isTrim()) {
159: trimBody();
160: }
161: }
162: return body;
163: }
164:
165: /** Sets the body of the tag */
166: public void setBody(Script body) {
167: this .body = body;
168: this .hasTrimmed = false;
169: }
170:
171: /** @return the context in which the tag will be run */
172: public JellyContext getContext() {
173: return context;
174: }
175:
176: /** Sets the context in which the tag will be run */
177: public void setContext(JellyContext context)
178: 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)
233: throws JellyTagException {
234: StringWriter writer = new StringWriter();
235: invokeBody(XMLOutput.createXMLOutput(writer, shouldEscape));
236: return writer.toString();
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: }
|