001 // Copyright 2004, 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.enhance; 016 017 import java.lang.reflect.Modifier; 018 import java.util.Iterator; 019 020 import org.apache.hivemind.ApplicationRuntimeException; 021 import org.apache.hivemind.ErrorLog; 022 import org.apache.hivemind.Location; 023 import org.apache.hivemind.service.BodyBuilder; 024 import org.apache.hivemind.service.ClassFabUtils; 025 import org.apache.hivemind.service.MethodSignature; 026 import org.apache.hivemind.util.Defense; 027 import org.apache.tapestry.IBinding; 028 import org.apache.tapestry.IComponent; 029 import org.apache.tapestry.spec.IComponentSpecification; 030 import org.apache.tapestry.spec.IParameterSpecification; 031 032 /** 033 * Responsible for creating properties for connected parameters. 034 * 035 * @author Howard M. Lewis Ship 036 * @since 4.0 037 */ 038 public class ParameterPropertyWorker implements EnhancementWorker 039 { 040 private ErrorLog _errorLog; 041 042 public void performEnhancement(EnhancementOperation op, IComponentSpecification spec) 043 { 044 Iterator i = spec.getParameterNames().iterator(); 045 while (i.hasNext()) 046 { 047 String name = (String) i.next(); 048 049 IParameterSpecification ps = spec.getParameter(name); 050 051 try 052 { 053 performEnhancement(op, name, ps); 054 } 055 catch (RuntimeException ex) 056 { 057 _errorLog.error(EnhanceMessages.errorAddingProperty(ps.getPropertyName(), op 058 .getBaseClass(), ex), ps.getLocation(), ex); 059 } 060 } 061 } 062 063 /** 064 * Performs the enhancement for a single parameter; this is about to change radically in release 065 * 4.0 but for the moment we're emulating 3.0 behavior. 066 */ 067 068 private void performEnhancement(EnhancementOperation op, String parameterName, 069 IParameterSpecification ps) 070 { 071 // If the parameter name doesn't match, its because this is an alias 072 // for a true parameter; we ignore aliases. 073 074 if (!parameterName.equals(ps.getParameterName())) 075 return; 076 077 String propertyName = ps.getPropertyName(); 078 String specifiedType = ps.getType(); 079 boolean cache = ps.getCache(); 080 081 addParameter(op, parameterName, propertyName, specifiedType, cache, ps.getLocation()); 082 } 083 084 /** 085 * Adds a parameter as a (very smart) property. 086 * 087 * @param op 088 * the enhancement operation 089 * @param parameterName 090 * the name of the parameter (used to access the binding) 091 * @param propertyName 092 * the name of the property to create (usually, but not always, matches the 093 * parameterName) 094 * @param specifiedType 095 * the type declared in the DTD (only 3.0 DTD supports this), may be null (always 096 * null for 4.0 DTD) 097 * @param cache 098 * if true, then the value should be cached while the component renders; false (a 099 * much less common case) means that every access will work through binding object. 100 * @param location 101 * TODO 102 */ 103 104 public void addParameter(EnhancementOperation op, String parameterName, String propertyName, 105 String specifiedType, boolean cache, Location location) 106 { 107 Defense.notNull(op, "op"); 108 Defense.notNull(parameterName, "parameterName"); 109 Defense.notNull(propertyName, "propertyName"); 110 111 Class propertyType = EnhanceUtils.extractPropertyType(op, propertyName, specifiedType); 112 113 // 3.0 would allow connected parameter properties to be fully implemented 114 // in the component class. This is not supported in 4.0 and an existing 115 // property will be overwritten in the subclass. 116 117 op.claimProperty(propertyName); 118 119 // 3.0 used to support a property for the binding itself. That's 120 // no longer the case. 121 122 String fieldName = "_$" + propertyName; 123 String defaultFieldName = fieldName + "$Default"; 124 String cachedFieldName = fieldName + "$Cached"; 125 126 op.addField(fieldName, propertyType); 127 op.addField(defaultFieldName, propertyType); 128 op.addField(cachedFieldName, boolean.class); 129 130 buildAccessor( 131 op, 132 parameterName, 133 propertyName, 134 propertyType, 135 fieldName, 136 defaultFieldName, 137 cachedFieldName, 138 cache, 139 location); 140 141 buildMutator( 142 op, 143 parameterName, 144 propertyName, 145 propertyType, 146 fieldName, 147 defaultFieldName, 148 cachedFieldName, 149 location); 150 151 extendCleanupAfterRender( 152 op, 153 parameterName, 154 propertyName, 155 propertyType, 156 fieldName, 157 defaultFieldName, 158 cachedFieldName); 159 } 160 161 private void extendCleanupAfterRender(EnhancementOperation op, String parameterName, 162 String propertyName, Class propertyType, String fieldName, String defaultFieldName, 163 String cachedFieldName) 164 { 165 BodyBuilder cleanupBody = new BodyBuilder(); 166 167 // Cached is only set when the field is updated in the accessor or mutator. 168 // After rendering, we want to clear the cached value and cached flag 169 // unless the binding is invariant, in which case it can stick around 170 // for some future render. 171 172 String bindingName = propertyName + "Binding"; 173 174 addBindingReference(cleanupBody, bindingName, parameterName); 175 176 cleanupBody.addln("if ({0} && ! {1}.isInvariant())", cachedFieldName, bindingName); 177 cleanupBody.begin(); 178 cleanupBody.addln("{0} = false;", cachedFieldName); 179 cleanupBody.addln("{0} = {1};", fieldName, defaultFieldName); 180 cleanupBody.end(); 181 182 op.extendMethodImplementation( 183 IComponent.class, 184 EnhanceUtils.CLEANUP_AFTER_RENDER_SIGNATURE, 185 cleanupBody.toString()); 186 } 187 188 private void addBindingReference(BodyBuilder builder, String localVariableName, 189 String parameterName) 190 { 191 builder.addln( 192 "{0} {1} = getBinding(\"{2}\");", 193 IBinding.class.getName(), 194 localVariableName, 195 parameterName); 196 } 197 198 private void buildMutator(EnhancementOperation op, String parameterName, String propertyName, 199 Class propertyType, String fieldName, String defaultFieldName, String cachedFieldName, 200 Location location) 201 { 202 BodyBuilder builder = new BodyBuilder(); 203 builder.begin(); 204 205 // The mutator method may be invoked from finishLoad(), in which 206 // case it changes the default value for the parameter property, if the parameter 207 // is not bound. 208 209 builder.addln("if (! isInActiveState())"); 210 builder.begin(); 211 builder.addln("{0} = $1;", defaultFieldName); 212 builder.addln("return;"); 213 builder.end(); 214 215 // In the normal state, we update the binding firstm, and it's an error 216 // if the parameter is not bound. 217 218 addBindingReference(builder, "binding", parameterName); 219 220 builder.addln("if (binding == null)"); 221 builder.addln( 222 " throw new {0}(\"Parameter ''{1}'' is not bound and can not be updated.\");", 223 ApplicationRuntimeException.class.getName(), 224 parameterName); 225 226 // Always updated the binding first (which may fail with an exception). 227 228 builder.addln("binding.setObject(($w) $1);"); 229 230 // While rendering, we store the updated value for fast 231 // access again (while the component is still rendering). 232 // The property value will be reset to default by cleanupAfterRender(). 233 234 builder.addln("if (isRendering())"); 235 builder.begin(); 236 builder.addln("{0} = $1;", fieldName); 237 builder.addln("{0} = true;", cachedFieldName); 238 builder.end(); 239 240 builder.end(); 241 242 String mutatorMethodName = EnhanceUtils.createMutatorMethodName(propertyName); 243 244 op.addMethod(Modifier.PUBLIC, new MethodSignature(void.class, mutatorMethodName, 245 new Class[] 246 { propertyType }, null), builder.toString(), location); 247 } 248 249 // Package private for testing 250 251 void buildAccessor(EnhancementOperation op, String parameterName, String propertyName, 252 Class propertyType, String fieldName, String defaultFieldName, String cachedFieldName, 253 boolean cache, Location location) 254 { 255 BodyBuilder builder = new BodyBuilder(); 256 builder.begin(); 257 258 builder.addln("if ({0}) return {1};", cachedFieldName, fieldName); 259 260 addBindingReference(builder, "binding", parameterName); 261 262 builder.addln("if (binding == null) return {0};", defaultFieldName); 263 264 String javaTypeName = ClassFabUtils.getJavaClassName(propertyType); 265 266 builder.addln("{0} result = {1};", javaTypeName, EnhanceUtils.createUnwrapExpression( 267 op, 268 "binding", 269 propertyType)); 270 271 // Values read via the binding are cached during the render of 272 // the component (if the parameter defines cache to be true, which 273 // is the default), or any time the binding is invariant 274 // (such as most bindings besides ExpressionBinding. 275 276 String expression = cache ? "isRendering() || binding.isInvariant()" 277 : "binding.isInvariant()"; 278 279 builder.addln("if ({0})", expression); 280 builder.begin(); 281 builder.addln("{0} = result;", fieldName); 282 builder.addln("{0} = true;", cachedFieldName); 283 builder.end(); 284 285 builder.addln("return result;"); 286 287 builder.end(); 288 289 String accessorMethodName = op.getAccessorMethodName(propertyName); 290 291 op.addMethod(Modifier.PUBLIC, new MethodSignature(propertyType, accessorMethodName, null, 292 null), builder.toString(), location); 293 } 294 295 public void setErrorLog(ErrorLog errorLog) 296 { 297 _errorLog = errorLog; 298 } 299 }