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
015package org.apache.tapestry;
016
017import java.util.ArrayList;
018import java.util.List;
019
020import org.apache.hivemind.ApplicationRuntimeException;
021import org.apache.hivemind.HiveMind;
022import org.apache.hivemind.Location;
023import org.apache.hivemind.util.Defense;
024
025/**
026 * Constants and static methods.
027 * 
028 * @author Howard M. Lewis Ship
029 * @since 4.0
030 */
031public 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}