KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > taglibs > standard > tlv > JstlCoreTLV


1 /*
2  * Copyright 1999-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
17 package org.apache.taglibs.standard.tlv;
18
19 import java.util.Set JavaDoc;
20 import java.util.Stack JavaDoc;
21
22 import javax.servlet.jsp.tagext.PageData JavaDoc;
23 import javax.servlet.jsp.tagext.ValidationMessage JavaDoc;
24
25 import org.apache.taglibs.standard.resources.Resources;
26 import org.xml.sax.Attributes JavaDoc;
27 import org.xml.sax.helpers.DefaultHandler JavaDoc;
28
29 /**
30  * <p>A SAX-based TagLibraryValidator for the core JSTL tag library.
31  * Currently implements the following checks:</p>
32  *
33  * <ul>
34  * <li>Expression syntax validation.
35  * <li>Choose / when / otherwise constraints</li>
36  * <li>Tag bodies that must either be empty or non-empty given
37  * particular attributes. (E.g., <set> cannot have a body when
38  * 'value' is specified; it *must* have a body otherwise.) For
39  * these purposes, "having a body" refers to non-whitespace
40  * content inside the tag.</li>
41  * <li>Other minor constraints.</li>
42  * </ul>
43  *
44  * @author Shawn Bayern
45  */

46 public class JstlCoreTLV extends JstlBaseTLV {
47
48     //*********************************************************************
49
// Implementation Overview
50

51     /*
52      * We essentially just run the page through a SAX parser, handling
53      * the callbacks that interest us. We collapse <jsp:text> elements
54      * into the text they contain, since this simplifies processing
55      * somewhat. Even a quick glance at the implementation shows its
56      * necessary, tree-oriented nature: multiple Stacks, an understanding
57      * of 'depth', and so on all are important as we recover necessary
58      * state upon each callback. This TLV demonstrates various techniques,
59      * from the general "how do I use a SAX parser for a TLV?" to
60      * "how do I read my init parameters and then validate?" But also,
61      * the specific SAX methodology was kept as general as possible to
62      * allow for experimentation and flexibility.
63      */

64
65
66     //*********************************************************************
67
// Constants
68

69     // tag names
70
private final String JavaDoc CHOOSE = "choose";
71     private final String JavaDoc WHEN = "when";
72     private final String JavaDoc OTHERWISE = "otherwise";
73     private final String JavaDoc EXPR = "out";
74     private final String JavaDoc SET = "set";
75     private final String JavaDoc IMPORT = "import";
76     private final String JavaDoc URL = "url";
77     private final String JavaDoc REDIRECT = "redirect";
78     private final String JavaDoc PARAM = "param";
79     // private final String EXPLANG = "expressionLanguage";
80
private final String JavaDoc TEXT = "text";
81
82     // attribute names
83
private final String JavaDoc VALUE = "value";
84     private final String JavaDoc DEFAULT = "default";
85     private final String JavaDoc VAR_READER = "varReader";
86
87     // alternative identifiers for tags
88
private final String JavaDoc IMPORT_WITH_READER = "import varReader=''";
89     private final String JavaDoc IMPORT_WITHOUT_READER = "import var=''";
90
91
92     //*********************************************************************
93
// set its type and delegate validation to super-class
94
public ValidationMessage JavaDoc[] validate(
95         String JavaDoc prefix, String JavaDoc uri, PageData JavaDoc page) {
96     return super.validate( TYPE_CORE, prefix, uri, page );
97     }
98
99
100     //*********************************************************************
101
// Contract fulfillment
102

103     protected DefaultHandler JavaDoc getHandler() {
104     return new Handler JavaDoc();
105     }
106
107
108     //*********************************************************************
109
// SAX event handler
110

111     /** The handler that provides the base of our implementation. */
112     private class Handler extends DefaultHandler JavaDoc {
113
114     // parser state
115
private int depth = 0;
116     private Stack JavaDoc chooseDepths = new Stack JavaDoc();
117     private Stack JavaDoc chooseHasOtherwise = new Stack JavaDoc();
118     private Stack JavaDoc chooseHasWhen = new Stack JavaDoc();
119         private Stack JavaDoc urlTags = new Stack JavaDoc();
120     private String JavaDoc lastElementName = null;
121     private boolean bodyNecessary = false;
122     private boolean bodyIllegal = false;
123
124     // process under the existing context (state), then modify it
125
public void startElement(
126             String JavaDoc ns, String JavaDoc ln, String JavaDoc qn, Attributes JavaDoc a) {
127
128         // substitute our own parsed 'ln' if it's not provided
129
if (ln == null)
130         ln = getLocalPart(qn);
131
132         // for simplicity, we can ignore <jsp:text> for our purposes
133
// (don't bother distinguishing between it and its characters)
134
if (isJspTag(ns, ln, TEXT))
135         return;
136
137         // check body-related constraint
138
if (bodyIllegal)
139         fail(Resources.getMessage("TLV_ILLEGAL_BODY", lastElementName));
140
141         // validate expression syntax if we need to
142
Set JavaDoc expAtts;
143         if (qn.startsWith(prefix + ":")
144             && (expAtts = (Set JavaDoc) config.get(ln)) != null) {
145         for (int i = 0; i < a.getLength(); i++) {
146             String JavaDoc attName = a.getLocalName(i);
147             if (expAtts.contains(attName)) {
148             String JavaDoc vMsg =
149                 validateExpression(
150                 ln,
151                 attName,
152                 a.getValue(i));
153             if (vMsg != null)
154                 fail(vMsg);
155             }
156         }
157         }
158
159             // validate attributes
160
if (qn.startsWith(prefix + ":") && !hasNoInvalidScope(a))
161                 fail(Resources.getMessage("TLV_INVALID_ATTRIBUTE",
162                     SCOPE, qn, a.getValue(SCOPE)));
163         if (qn.startsWith(prefix + ":") && hasEmptyVar(a))
164         fail(Resources.getMessage("TLV_EMPTY_VAR", qn));
165         if (qn.startsWith(prefix + ":") && hasDanglingScope(a))
166         fail(Resources.getMessage("TLV_DANGLING_SCOPE", qn));
167
168         // check invariants for <choose>
169
if (chooseChild()) {
170         // mark <choose> for the first the first <when>
171
if (isCoreTag(ns, ln, WHEN)) {
172             chooseHasWhen.pop();
173             chooseHasWhen.push(Boolean.TRUE);
174         }
175
176         // ensure <choose> has the right children
177
if(!isCoreTag(ns, ln, WHEN) && !isCoreTag(ns, ln, OTHERWISE)) {
178             fail(Resources.getMessage("TLV_ILLEGAL_CHILD_TAG",
179             prefix, CHOOSE, qn));
180         }
181
182         // make sure <otherwise> is the last tag
183
if (((Boolean JavaDoc) chooseHasOtherwise.peek()).booleanValue()) {
184            fail(Resources.getMessage("TLV_ILLEGAL_ORDER",
185             qn, prefix, OTHERWISE, CHOOSE));
186         }
187         if (isCoreTag(ns, ln, OTHERWISE)) {
188             chooseHasOtherwise.pop();
189             chooseHasOtherwise.push(Boolean.TRUE);
190         }
191
192         }
193
194         // check constraints for <param> vis-a-vis URL-related tags
195
if (isCoreTag(ns, ln, PARAM)) {
196         // no <param> outside URL tags.
197
if (urlTags.empty() || urlTags.peek().equals(PARAM))
198             fail(Resources.getMessage("TLV_ILLEGAL_ORPHAN", PARAM));
199
200         // no <param> where the most recent <import> has a reader
201
if (!urlTags.empty() &&
202             urlTags.peek().equals(IMPORT_WITH_READER))
203             fail(Resources.getMessage("TLV_ILLEGAL_PARAM",
204             prefix, PARAM, IMPORT, VAR_READER));
205         } else {
206         // tag ISN'T <param>, so it's illegal under non-reader <import>
207
if (!urlTags.empty()
208             && urlTags.peek().equals(IMPORT_WITHOUT_READER))
209             fail(Resources.getMessage("TLV_ILLEGAL_CHILD_TAG",
210             prefix, IMPORT, qn));
211         }
212
213         // now, modify state
214

215         // we're a choose, so record new choose-specific state
216
if (isCoreTag(ns, ln, CHOOSE)) {
217         chooseDepths.push(new Integer JavaDoc(depth));
218         chooseHasWhen.push(Boolean.FALSE);
219         chooseHasOtherwise.push(Boolean.FALSE);
220         }
221
222         // if we're introducing a URL-related tag, record it
223
if (isCoreTag(ns, ln, IMPORT)) {
224         if (hasAttribute(a, VAR_READER))
225             urlTags.push(IMPORT_WITH_READER);
226         else
227             urlTags.push(IMPORT_WITHOUT_READER);
228         } else if (isCoreTag(ns, ln, PARAM))
229         urlTags.push(PARAM);
230         else if (isCoreTag(ns, ln, REDIRECT))
231         urlTags.push(REDIRECT);
232         else if (isCoreTag(ns, ln, URL))
233         urlTags.push(URL);
234
235         // set up a check against illegal attribute/body combinations
236
bodyIllegal = false;
237         bodyNecessary = false;
238         if (isCoreTag(ns, ln, EXPR)) {
239         if (hasAttribute(a, DEFAULT))
240             bodyIllegal = true;
241         } else if (isCoreTag(ns, ln, SET)) {
242         if (hasAttribute(a, VALUE))
243             bodyIllegal = true;
244         // else
245
// bodyNecessary = true;
246
}
247
248         // record the most recent tag (for error reporting)
249
lastElementName = qn;
250         lastElementId = a.getValue(JSP, "id");
251
252         // we're a new element, so increase depth
253
depth++;
254     }
255
256     public void characters(char[] ch, int start, int length) {
257
258         bodyNecessary = false; // body is no longer necessary!
259

260         // ignore strings that are just whitespace
261
String JavaDoc s = new String JavaDoc(ch, start, length).trim();
262         if (s.equals(""))
263         return;
264
265         // check and update body-related constraints
266
if (bodyIllegal)
267         fail(Resources.getMessage("TLV_ILLEGAL_BODY", lastElementName));
268         if (!urlTags.empty()
269             && urlTags.peek().equals(IMPORT_WITHOUT_READER)) {
270         // we're in an <import> without a Reader; nothing but
271
// <param> is allowed
272
fail(Resources.getMessage("TLV_ILLEGAL_BODY",
273             prefix + ":" + IMPORT));
274         }
275
276         // make sure <choose> has no non-whitespace text
277
if (chooseChild()) {
278         String JavaDoc msg =
279             Resources.getMessage("TLV_ILLEGAL_TEXT_BODY",
280             prefix, CHOOSE,
281             (s.length() < 7 ? s : s.substring(0,7)));
282         fail(msg);
283         }
284     }
285
286     public void endElement(String JavaDoc ns, String JavaDoc ln, String JavaDoc qn) {
287
288         // consistently, we ignore JSP_TEXT
289
if (isJspTag(ns, ln, TEXT))
290         return;
291
292         // handle body-related invariant
293
if (bodyNecessary)
294         fail(Resources.getMessage("TLV_MISSING_BODY",
295             lastElementName));
296         bodyIllegal = false; // reset: we've left the tag
297

298         // update <choose>-related state
299
if (isCoreTag(ns, ln, CHOOSE)) {
300         Boolean JavaDoc b = (Boolean JavaDoc) chooseHasWhen.pop();
301         if (!b.booleanValue())
302             fail(Resources.getMessage("TLV_PARENT_WITHOUT_SUBTAG",
303             CHOOSE, WHEN));
304         chooseDepths.pop();
305         chooseHasOtherwise.pop();
306         }
307
308         // update state related to URL tags
309
if (isCoreTag(ns, ln, IMPORT)
310                     || isCoreTag(ns, ln, PARAM)
311             || isCoreTag(ns, ln, REDIRECT)
312             || isCoreTag(ns, ln, URL))
313         urlTags.pop();
314
315         // update our depth
316
depth--;
317     }
318
319     // are we directly under a <choose>?
320
private boolean chooseChild() {
321         return (!chooseDepths.empty()
322         && (depth - 1) == ((Integer JavaDoc) chooseDepths.peek()).intValue());
323     }
324
325     }
326 }
327
Popular Tags