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