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.ErrorLog; 021 import org.apache.hivemind.Location; 022 import org.apache.hivemind.service.BodyBuilder; 023 import org.apache.hivemind.service.MethodSignature; 024 import org.apache.hivemind.util.Defense; 025 import org.apache.tapestry.IBinding; 026 import org.apache.tapestry.IComponent; 027 import org.apache.tapestry.binding.BindingSource; 028 import org.apache.tapestry.event.PageDetachListener; 029 import org.apache.tapestry.spec.IComponentSpecification; 030 import org.apache.tapestry.spec.IPropertySpecification; 031 032 /** 033 * Responsible for adding properties to a class corresponding to specified properties in the 034 * component's specification. 035 * 036 * @author Howard M. Lewis Ship 037 * @since 4.0 038 */ 039 public class SpecifiedPropertyWorker implements EnhancementWorker 040 { 041 private ErrorLog _errorLog; 042 043 private BindingSource _bindingSource; 044 045 /** 046 * Iterates over the specified properties, creating an enhanced property for each (a field, an 047 * accessor, a mutator). Persistent properties will invoke 048 * {@link org.apache.tapestry.Tapestry#fireObservedChange(IComponent, String, Object)}in thier 049 * mutator. 050 */ 051 052 public void performEnhancement(EnhancementOperation op, IComponentSpecification spec) 053 { 054 Iterator i = spec.getPropertySpecificationNames().iterator(); 055 056 while (i.hasNext()) 057 { 058 String name = (String) i.next(); 059 IPropertySpecification ps = spec.getPropertySpecification(name); 060 061 try 062 { 063 performEnhancement(op, ps); 064 } 065 catch (RuntimeException ex) 066 { 067 _errorLog.error( 068 EnhanceMessages.errorAddingProperty(name, op.getBaseClass(), ex), 069 ps.getLocation(), 070 ex); 071 } 072 } 073 } 074 075 private void performEnhancement(EnhancementOperation op, IPropertySpecification ps) 076 { 077 Defense.notNull(ps, "ps"); 078 079 String propertyName = ps.getName(); 080 String specifiedType = ps.getType(); 081 boolean persistent = ps.isPersistent(); 082 String initialValue = ps.getInitialValue(); 083 Location location = ps.getLocation(); 084 085 addProperty(op, propertyName, specifiedType, persistent, initialValue, location); 086 } 087 088 public void addProperty(EnhancementOperation op, String propertyName, String specifiedType, 089 boolean persistent, String initialValue, Location location) 090 { 091 Class propertyType = EnhanceUtils.extractPropertyType(op, propertyName, specifiedType); 092 093 op.claimProperty(propertyName); 094 095 String field = "_$" + propertyName; 096 097 op.addField(field, propertyType); 098 099 // Release 3.0 would squack a bit about overriding non-abstract methods 100 // if they exist. 4.0 is less picky ... it blindly adds new methods, possibly 101 // overwriting methods in the base component class. 102 103 EnhanceUtils.createSimpleAccessor(op, field, propertyName, propertyType, location); 104 105 addMutator(op, propertyName, propertyType, field, persistent, location); 106 107 if (initialValue == null) 108 addReinitializer(op, propertyType, field); 109 else 110 addInitialValue(op, propertyName, propertyType, field, initialValue, location); 111 } 112 113 private void addReinitializer(EnhancementOperation op, Class propertyType, String fieldName) 114 { 115 String defaultFieldName = fieldName + "$default"; 116 117 op.addField(defaultFieldName, propertyType); 118 119 // On finishLoad(), store the current value into the default field. 120 121 op.extendMethodImplementation( 122 IComponent.class, 123 EnhanceUtils.FINISH_LOAD_SIGNATURE, 124 defaultFieldName + " = " + fieldName + ";"); 125 126 // On pageDetach(), restore the attribute to its default value. 127 128 op.extendMethodImplementation( 129 PageDetachListener.class, 130 EnhanceUtils.PAGE_DETACHED_SIGNATURE, 131 fieldName + " = " + defaultFieldName + ";"); 132 } 133 134 private void addInitialValue(EnhancementOperation op, String propertyName, Class propertyType, 135 String fieldName, String initialValue, Location location) 136 { 137 String description = EnhanceMessages.initialValueForProperty(propertyName); 138 139 InitialValueBindingCreator creator = new InitialValueBindingCreator(_bindingSource, 140 description, initialValue, location); 141 142 String creatorField = op.addInjectedField( 143 fieldName + "$initialValueBindingCreator", 144 InitialValueBindingCreator.class, 145 creator); 146 147 String bindingField = fieldName + "$initialValueBinding"; 148 op.addField(bindingField, IBinding.class); 149 150 BodyBuilder builder = new BodyBuilder(); 151 152 builder.addln("{0} = {1}.createBinding(this);", bindingField, creatorField); 153 154 op.extendMethodImplementation(IComponent.class, EnhanceUtils.FINISH_LOAD_SIGNATURE, builder 155 .toString()); 156 157 builder.clear(); 158 159 builder.addln("{0} = {1};", fieldName, EnhanceUtils.createUnwrapExpression( 160 op, 161 bindingField, 162 propertyType)); 163 164 String code = builder.toString(); 165 166 // In finishLoad() and pageDetach(), de-reference the binding to get the value 167 // for the property. 168 169 op.extendMethodImplementation(IComponent.class, EnhanceUtils.FINISH_LOAD_SIGNATURE, code); 170 op.extendMethodImplementation( 171 PageDetachListener.class, 172 EnhanceUtils.PAGE_DETACHED_SIGNATURE, 173 code); 174 175 } 176 177 private void addMutator(EnhancementOperation op, String propertyName, Class propertyType, 178 String fieldName, boolean persistent, Location location) 179 { 180 String methodName = EnhanceUtils.createMutatorMethodName(propertyName); 181 182 BodyBuilder body = new BodyBuilder(); 183 184 body.begin(); 185 186 if (persistent) 187 { 188 body.add("org.apache.tapestry.Tapestry#fireObservedChange(this, "); 189 body.addQuoted(propertyName); 190 body.addln(", ($w) $1);"); 191 } 192 193 body.addln(fieldName + " = $1;"); 194 195 body.end(); 196 197 MethodSignature sig = new MethodSignature(void.class, methodName, new Class[] 198 { propertyType }, null); 199 200 op.addMethod(Modifier.PUBLIC, sig, body.toString(), location); 201 } 202 203 public void setErrorLog(ErrorLog errorLog) 204 { 205 _errorLog = errorLog; 206 } 207 208 public void setBindingSource(BindingSource bindingSource) 209 { 210 _bindingSource = bindingSource; 211 } 212 }