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    }