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}