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