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 String bindingCheckedName = bindingFieldName + "Checked"; 159 160 op.addField(bindingFieldName, IBinding.class); 161 op.addField(bindingCheckedName, Boolean.TYPE); 162 163 body.addln("if (!{0})", bindingCheckedName); 164 body.begin(); 165 body.addln("{0} = getBinding(\"{1}\");", bindingFieldName, parameterName); 166 body.addln("{0} = true;", bindingCheckedName); 167 body.end(); 168 169 body.addln("return {0};", bindingFieldName); 170 171 body.end(); 172 173 String methodName = EnhanceUtils.createAccessorMethodName(bindingFieldName); 174 175 op.addMethod(Modifier.PUBLIC, 176 new MethodSignature(IBinding.class, methodName, new Class[0], null), 177 body.toString(), location); 178 179 return methodName + "()"; 180 } 181 182 void extendCleanupAfterRender(EnhancementOperation op, String bindingFieldName, 183 String fieldName, String defaultFieldName, String cachedFieldName) 184 { 185 BodyBuilder cleanupBody = new BodyBuilder(); 186 187 // Cached is only set when the field is updated in the accessor or 188 // mutator. 189 // After rendering, we want to clear the cached value and cached flag 190 // unless the binding is invariant, in which case it can stick around 191 // for some future render. 192 193 cleanupBody.addln("if ({0} && ! {1}.isInvariant())", cachedFieldName, bindingFieldName); 194 cleanupBody.begin(); 195 cleanupBody.addln("{0} = false;", cachedFieldName); 196 cleanupBody.addln("{0} = {1};", fieldName, defaultFieldName); 197 cleanupBody.end(); 198 199 op.extendMethodImplementation(IComponent.class, 200 EnhanceUtils.CLEANUP_AFTER_RENDER_SIGNATURE, cleanupBody.toString()); 201 } 202 203 private void buildMutator(EnhancementOperation op, String parameterName, 204 String propertyName, Class propertyType, String fieldName, 205 String defaultFieldName, String cachedFieldName, 206 String bindingFieldName, Location location) 207 { 208 BodyBuilder builder = new BodyBuilder(); 209 builder.begin(); 210 211 // The mutator method may be invoked from finishLoad(), in which 212 // case it changes the default value for the parameter property, if the 213 // parameter 214 // is not bound. 215 216 builder.addln("if (! isInActiveState())"); 217 builder.begin(); 218 builder.addln("{0} = $1;", defaultFieldName); 219 builder.addln("return;"); 220 builder.end(); 221 222 // In the normal state, we update the binding first - and it's an error 223 // if the parameter is not bound. 224 225 builder.addln("if ({0} == null)", bindingFieldName); 226 builder.addln(" throw new {0}(\"Parameter ''{1}'' is not bound and can not be updated.\");", 227 ApplicationRuntimeException.class.getName(), parameterName); 228 229 // Always updated the binding first (which may fail with an exception). 230 231 builder.addln("{0}.setObject(($w) $1);", bindingFieldName); 232 233 // While rendering, we store the updated value for fast 234 // access again (while the component is still rendering). 235 // The property value will be reset to default by cleanupAfterRender(). 236 237 builder.addln("if (isRendering())"); 238 builder.begin(); 239 builder.addln("{0} = $1;", fieldName); 240 builder.addln("{0} = true;", cachedFieldName); 241 builder.end(); 242 243 builder.end(); 244 245 String mutatorMethodName = EnhanceUtils.createMutatorMethodName(propertyName); 246 247 op.addMethod(Modifier.PUBLIC, 248 new MethodSignature(void.class, mutatorMethodName, new Class[] { propertyType }, null), 249 builder.toString(), location); 250 } 251 252 // Package private for testing 253 254 void buildAccessor(EnhancementOperation op, String propertyName, Class propertyType, 255 String fieldName, String defaultFieldName, String cachedFieldName, 256 String bindingFieldName, boolean cache, Location location) 257 { 258 BodyBuilder builder = new BodyBuilder(); 259 builder.begin(); 260 261 builder.addln("if ({0}) return {1};", cachedFieldName, fieldName); 262 builder.addln("if ({0} == null) return {1};", bindingFieldName, defaultFieldName); 263 264 String javaTypeName = ClassFabUtils.getJavaClassName(propertyType); 265 266 builder.addln("{0} result = {1};", javaTypeName, EnhanceUtils.createUnwrapExpression(op, bindingFieldName, propertyType)); 267 268 // Values read via the binding are cached during the render of 269 // the component (if the parameter defines cache to be true, which 270 // is the default), or any time the binding is invariant 271 // (such as most bindings besides ExpressionBinding. 272 273 String expression = cache ? "isRendering() || {0}.isInvariant()" : "{0}.isInvariant()"; 274 275 builder.addln("if (" + expression + ")", bindingFieldName); 276 builder.begin(); 277 builder.addln("{0} = result;", fieldName); 278 builder.addln("{0} = true;", cachedFieldName); 279 builder.end(); 280 281 builder.addln("return result;"); 282 283 builder.end(); 284 285 String accessorMethodName = op.getAccessorMethodName(propertyName); 286 287 op.addMethod(Modifier.PUBLIC, new MethodSignature(propertyType, 288 accessorMethodName, null, null), builder.toString(), location); 289 } 290 291 public void setErrorLog(ErrorLog errorLog) 292 { 293 _errorLog = errorLog; 294 } 295 }