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    }