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    }