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