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 }