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    }