KickJava   Java API By Example, From Geeks To Geeks.

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


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 JSTL i18n-capable formatting
31  * library. Currently implements the following checks:</p>
32  *
33  * <ul>
34  * <li>Expression syntax validation.
35  * <li>Tag bodies that must either be empty or non-empty given
36  * particular attributes.</li>
37  * </ul>
38  *
39  * @author Shawn Bayern
40  * @author Jan Luehe
41  */

42 public class JstlFmtTLV extends JstlBaseTLV {
43
44     //*********************************************************************
45
// Implementation Overview
46

47     /*
48      * We essentially just run the page through a SAX parser, handling
49      * the callbacks that interest us. We collapse <jsp:text> elements
50      * into the text they contain, since this simplifies processing
51      * somewhat. Even a quick glance at the implementation shows its
52      * necessary, tree-oriented nature: multiple Stacks, an understanding
53      * of 'depth', and so on all are important as we recover necessary
54      * state upon each callback. This TLV demonstrates various techniques,
55      * from the general "how do I use a SAX parser for a TLV?" to
56      * "how do I read my init parameters and then validate?" But also,
57      * the specific SAX methodology was kept as general as possible to
58      * allow for experimentation and flexibility.
59      *
60      * Much of the code and structure is duplicated from JstlCoreTLV.
61      * An effort has been made to re-use code where unambiguously useful.
62      * However, splitting logic among parent/child classes isn't
63      * necessarily the cleanest approach when writing a parser like the
64      * one we need. I'd like to reorganize this somewhat, but it's not
65      * a priority.
66      */

67
68
69     //*********************************************************************
70
// Constants
71

72     // tag names
73
private final String JavaDoc SETLOCALE = "setLocale";
74     private final String JavaDoc SETBUNDLE = "setBundle";
75     private final String JavaDoc SETTIMEZONE = "setTimeZone";
76     private final String JavaDoc BUNDLE = "bundle";
77     private final String JavaDoc MESSAGE = "message";
78     private final String JavaDoc MESSAGE_PARAM = "param";
79     private final String JavaDoc FORMAT_NUMBER = "formatNumber";
80     private final String JavaDoc PARSE_NUMBER = "parseNumber";
81     private final String JavaDoc PARSE_DATE = "parseDate";
82     // private final String EXPLANG = "expressionLanguage";
83
private final String JavaDoc JSP_TEXT = "jsp:text";
84
85     // attribute names
86
private final String JavaDoc EVAL = "evaluator";
87     private final String JavaDoc MESSAGE_KEY = "key";
88     private final String JavaDoc BUNDLE_PREFIX = "prefix";
89     private final String JavaDoc VALUE = "value";
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_FMT, 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 messageDepths = new Stack JavaDoc();
117     private String JavaDoc lastElementName = null;
118     private boolean bodyNecessary = false;
119     private boolean bodyIllegal = false;
120
121     // process under the existing context (state), then modify it
122
public void startElement(
123             String JavaDoc ns, String JavaDoc ln, String JavaDoc qn, Attributes JavaDoc a) {
124
125             // substitute our own parsed 'ln' if it's not provided
126
if (ln == null)
127                 ln = getLocalPart(qn);
128
129         // for simplicity, we can ignore <jsp:text> for our purposes
130
// (don't bother distinguishing between it and its characters)
131
if (qn.equals(JSP_TEXT))
132         return;
133
134         // check body-related constraint
135
if (bodyIllegal)
136         fail(Resources.getMessage("TLV_ILLEGAL_BODY",
137                       lastElementName));
138
139             // validate expression syntax if we need to
140
Set JavaDoc expAtts;
141             if (qn.startsWith(prefix + ":")
142                     && (expAtts = (Set JavaDoc) config.get(ln)) != null) {
143                 for (int i = 0; i < a.getLength(); i++) {
144                     String JavaDoc attName = a.getLocalName(i);
145                     if (expAtts.contains(attName)) {
146                         String JavaDoc vMsg =
147                             validateExpression(
148                                 ln,
149                                 attName,
150                                 a.getValue(i));
151                         if (vMsg != null)
152                             fail(vMsg);
153                     }
154                 }
155             }
156
157             // validate attributes
158
if (qn.startsWith(prefix + ":") && !hasNoInvalidScope(a))
159                 fail(Resources.getMessage("TLV_INVALID_ATTRIBUTE",
160                     SCOPE, qn, a.getValue(SCOPE)));
161         if (qn.startsWith(prefix + ":") && hasEmptyVar(a))
162         fail(Resources.getMessage("TLV_EMPTY_VAR", qn));
163             if (qn.startsWith(prefix + ":")
164                 && !isFmtTag(ns, ln, SETLOCALE)
165         && !isFmtTag(ns, ln, SETBUNDLE)
166         && !isFmtTag(ns, ln, SETTIMEZONE)
167                 && hasDanglingScope(a))
168                 fail(Resources.getMessage("TLV_DANGLING_SCOPE", qn));
169
170         /*
171          * Make sure <fmt:param> is nested inside <fmt:message>. Note that
172          * <fmt:param> does not need to be a direct child of <fmt:message>.
173          * Otherwise, the following would not work:
174          *
175          * <fmt:message key="..." bundle="...">
176          * <c:forEach var="arg" items="...">
177          * <fmt:param value="${arg}"/>
178          * </c:forEach>
179          * </fmt:message>
180          */

181         if (isFmtTag(ns, ln, MESSAGE_PARAM) && messageDepths.empty()) {
182         fail(Resources.getMessage("PARAM_OUTSIDE_MESSAGE"));
183         }
184
185         // now, modify state
186

187         // If we're in a <message>, record relevant state
188
if (isFmtTag(ns, ln, MESSAGE)) {
189         messageDepths.push(new Integer JavaDoc(depth));
190         }
191
192         // set up a check against illegal attribute/body combinations
193
bodyIllegal = false;
194         bodyNecessary = false;
195         if (isFmtTag(ns, ln, MESSAGE_PARAM)
196             || isFmtTag(ns, ln, FORMAT_NUMBER)
197             || isFmtTag(ns, ln, PARSE_NUMBER)
198             || isFmtTag(ns, ln, PARSE_DATE)) {
199         if (hasAttribute(a, VALUE))
200             bodyIllegal = true;
201         else
202             bodyNecessary = true;
203         } else if (isFmtTag(ns, ln, MESSAGE)
204             && !hasAttribute(a, MESSAGE_KEY)) {
205         bodyNecessary = true;
206         } else if (isFmtTag(ns, ln, BUNDLE)
207             && hasAttribute(a, BUNDLE_PREFIX)) {
208         bodyNecessary = true;
209         }
210
211         // record the most recent tag (for error reporting)
212
lastElementName = qn;
213             lastElementId = a.getValue(JSP, "id");
214
215         // we're a new element, so increase depth
216
depth++;
217     }
218
219     public void characters(char[] ch, int start, int length) {
220
221         bodyNecessary = false; // body is no longer necessary!
222

223         // ignore strings that are just whitespace
224
String JavaDoc s = new String JavaDoc(ch, start, length).trim();
225         if (s.equals(""))
226         return;
227
228         // check and update body-related constraints
229
if (bodyIllegal)
230         fail(Resources.getMessage("TLV_ILLEGAL_BODY",
231                       lastElementName));
232     }
233
234     public void endElement(String JavaDoc ns, String JavaDoc ln, String JavaDoc qn) {
235
236         // consistently, we ignore JSP_TEXT
237
if (qn.equals(JSP_TEXT))
238         return;
239
240         // handle body-related invariant
241
if (bodyNecessary)
242         fail(Resources.getMessage("TLV_MISSING_BODY",
243             lastElementName));
244         bodyIllegal = false; // reset: we've left the tag
245

246         // update <message>-related state
247
if (isFmtTag(ns, ln, MESSAGE)) {
248         messageDepths.pop();
249         }
250
251         // update our depth
252
depth--;
253     }
254     }
255 }
256
Popular Tags