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