001 // Copyright 2005 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.tapestry;
016
017 import org.apache.hivemind.ApplicationRuntimeException;
018 import org.apache.hivemind.HiveMind;
019 import org.apache.hivemind.Location;
020 import org.apache.hivemind.util.Defense;
021
022 import java.util.ArrayList;
023 import java.util.List;
024
025 /**
026 * Constants and static methods.
027 *
028 * @author Howard M. Lewis Ship
029 * @since 4.0
030 */
031 public final class TapestryUtils
032 {
033 public static final String PAGE_RENDER_SUPPORT_ATTRIBUTE = "org.apache.tapestry.PageRenderSupport";
034
035 public static final String FORM_ATTRIBUTE = "org.apache.tapestry.Form";
036
037 public static final String FIELD_PRERENDER = "org.apache.tapestry.form.Prerender";
038
039 private static final char QUOTE = '\'';
040
041 private static final char BACKSLASH = '\\';
042
043 private static final String EMPTY_QUOTES = "''";
044
045 /* defeat instantiation */
046 private TapestryUtils() { }
047
048 /**
049 * Stores an attribute into the request cycle, verifying that no object with that key is already
050 * present.
051 *
052 * @param cycle
053 * the cycle to store the attribute into
054 * @param key
055 * the key to store the attribute as
056 * @param object
057 * the attribute value to store
058 * @throws IllegalStateException
059 * if a non-null value has been stored into the cycle with the provided key.
060 */
061
062 public static void storeUniqueAttribute(IRequestCycle cycle, String key, Object object)
063 {
064 Defense.notNull(cycle, "cycle");
065 Defense.notNull(key, "key");
066 Defense.notNull(object, "object");
067
068 Object existing = cycle.getAttribute(key);
069 if (existing != null)
070 throw new IllegalStateException(TapestryMessages.nonUniqueAttribute(
071 object,
072 key,
073 existing));
074
075 cycle.setAttribute(key, object);
076 }
077
078 /**
079 * Stores the support object using {@link #storeUniqueAttribute(IRequestCycle, String, Object)}.
080 */
081
082 public static void storePageRenderSupport(IRequestCycle cycle, PageRenderSupport support)
083 {
084 storeUniqueAttribute(cycle, PAGE_RENDER_SUPPORT_ATTRIBUTE, support);
085 }
086
087 /**
088 * Store the IForm instance using {@link #storeUniqueAttribute(IRequestCycle, String, Object)}.
089 */
090
091 public static void storeForm(IRequestCycle cycle, IForm form)
092 {
093 storeUniqueAttribute(cycle, FORM_ATTRIBUTE, form);
094 }
095
096 /**
097 * Stores the {@link IComponent} into the cycle by FormSupport before doing a field
098 * prerender.
099 * @param cycle
100 * @param component
101 */
102 public static void storePrerender(IRequestCycle cycle, IComponent component)
103 {
104 storeUniqueAttribute(cycle, FIELD_PRERENDER, component);
105 }
106
107 /**
108 * Gets the previously stored {@link org.apache.tapestry.PageRenderSupport} object.
109 *
110 * @param cycle
111 * the request cycle storing the support object
112 * @param component
113 * the component which requires the support (used to report exceptions)
114 * @throws ApplicationRuntimeException
115 * if no support object has been stored
116 */
117
118 public static PageRenderSupport getPageRenderSupport(IRequestCycle cycle, IComponent component)
119 {
120 Defense.notNull(component, "component");
121
122 PageRenderSupport result = getOptionalPageRenderSupport(cycle);
123 if (result == null)
124 throw new ApplicationRuntimeException(TapestryMessages.noPageRenderSupport(component),
125 component.getLocation(), null);
126
127 return result;
128 }
129
130 /**
131 * Gets the previously stored {@link IForm} object.
132 *
133 * @param cycle
134 * the request cycle storing the support object
135 * @param component
136 * the component which requires the form (used to report exceptions)
137 * @throws ApplicationRuntimeException
138 * if no form object has been stored
139 */
140 public static IForm getForm(IRequestCycle cycle, IComponent component)
141 {
142 Defense.notNull(cycle, "cycle");
143 Defense.notNull(component, "component");
144
145 IForm result = (IForm) cycle.getAttribute(FORM_ATTRIBUTE);
146
147 if (result == null)
148 throw new ApplicationRuntimeException(TapestryMessages.noForm(component), component.getLocation(), null);
149
150 return result;
151 }
152
153 public static void removePageRenderSupport(IRequestCycle cycle)
154 {
155 cycle.removeAttribute(PAGE_RENDER_SUPPORT_ATTRIBUTE);
156 }
157
158 public static void removeForm(IRequestCycle cycle)
159 {
160 cycle.removeAttribute(FORM_ATTRIBUTE);
161 }
162
163 public static void removePrerender(IRequestCycle cycle)
164 {
165 cycle.removeAttribute(FIELD_PRERENDER);
166 }
167
168 /**
169 * Returns the {@link PageRenderSupport} object if previously stored, or null otherwise.
170 * This is used in the rare case that a component wishes to adjust its behavior based on whether
171 * the page render support services are available (typically, adjust for whether enclosed by a
172 * Body component, or not).
173 */
174
175 public static PageRenderSupport getOptionalPageRenderSupport(IRequestCycle cycle)
176 {
177 return (PageRenderSupport) cycle.getAttribute(PAGE_RENDER_SUPPORT_ATTRIBUTE);
178 }
179
180 /**
181 * Splits a string using the default delimiter of ','.
182 */
183
184 public static String[] split(String input)
185 {
186 return split(input, ',');
187 }
188
189 /**
190 * Splits a single string into an array of strings, using a specific delimiter character.
191 */
192
193 public static String[] split(String input, char delimiter)
194 {
195 if (HiveMind.isBlank(input))
196 return new String[0];
197
198 List strings = new ArrayList();
199
200 char[] buffer = input.toCharArray();
201
202 int start = 0;
203 int length = 0;
204
205 for (int i = 0; i < buffer.length; i++)
206 {
207 if (buffer[i] != delimiter)
208 {
209 length++;
210 continue;
211 }
212
213 // Consecutive delimiters will result in a sequence
214 // of empty strings.
215
216 String token = new String(buffer, start, length);
217 strings.add(token.trim());
218
219 start = i + 1;
220 length = 0;
221 }
222
223 // If the string contains no delimiters, then
224 // wrap it in an array and return it.
225
226 if (start == 0 && length == buffer.length)
227 {
228 return new String[] { input };
229 }
230
231 // The final token.
232 String token = new String(buffer, start, length);
233 strings.add(token.trim());
234
235 return (String[]) strings.toArray(new String[strings.size()]);
236 }
237
238 /**
239 * Capitalize the first letter of the input if at least 1 character.
240 */
241
242 public static String capitalize(String input)
243 {
244 if (input == null || input.length() < 1)
245 return input;
246
247 return input.substring(0, 1).toUpperCase() + input.substring(1);
248 }
249
250 /**
251 * Enquotes a string within single quotes, ready for insertion as part of a block of JavaScript.
252 * Single quotes and backslashes within the input string are properly escaped.
253 */
254
255 public static String enquote(String input)
256 {
257 if (input == null)
258 return EMPTY_QUOTES;
259
260 char[] chars = input.toCharArray();
261
262 // Add room for the two quotes and a couple of escaped characters
263
264 StringBuffer buffer = new StringBuffer(chars.length + 5);
265
266 buffer.append(QUOTE);
267
268 for (int i = 0; i < chars.length; i++)
269 {
270 char ch = chars[i];
271
272 if (ch == QUOTE || ch == BACKSLASH)
273 buffer.append(BACKSLASH);
274
275 buffer.append(ch);
276 }
277
278 buffer.append(QUOTE);
279
280 return buffer.toString();
281 }
282
283 /**
284 * A Tapestry component id is a little more liberal than an XML NMTOKEN. NMTOKEN must be
285 * [A-Za-z][A-Za-z0-9:_.-]*, but a component id might include a leading dollar sign (for an
286 * anonymous component with a fabricated id).
287 */
288
289 public static String convertTapestryIdToNMToken(String baseId)
290 {
291 String result = baseId.replace('$', '_');
292
293 while (result.startsWith("_"))
294 result = result.substring(1);
295
296 return result;
297 }
298
299 /**
300 * Converts a clientId into a client-side DOM reference; i.e.
301 * <code>document.getElementById('<i>id</i>')</code>.
302 */
303
304 public static String buildClientElementReference(String clientId)
305 {
306 Defense.notNull(clientId, "clientId");
307
308 return "document.getElementById('" + clientId + "')";
309 }
310
311 /**
312 * Used by some generated code; obtains a component and ensures it is of the correct type.
313 */
314
315 public static IComponent getComponent(IComponent container, String componentId,
316 Class expectedType, Location location)
317 {
318 Defense.notNull(container, "container");
319 Defense.notNull(componentId, "componentId");
320 Defense.notNull(expectedType, "expectedType");
321 // Don't always have a location
322
323 IComponent component = null;
324
325 try
326 {
327 component = container.getComponent(componentId);
328 }
329 catch (Exception ex)
330 {
331 throw new ApplicationRuntimeException(ex.getMessage(), location, ex);
332 }
333
334 if (!expectedType.isAssignableFrom(component.getClass()))
335 throw new ApplicationRuntimeException(TapestryMessages.componentWrongType(
336 component,
337 expectedType), location, null);
338
339 return component;
340 }
341 }