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
015package org.apache.tapestry.enhance;
016
017import java.lang.reflect.Modifier;
018import java.util.Iterator;
019
020import org.apache.hivemind.ErrorLog;
021import org.apache.hivemind.Location;
022import org.apache.hivemind.service.BodyBuilder;
023import org.apache.hivemind.service.MethodSignature;
024import org.apache.hivemind.util.Defense;
025import org.apache.tapestry.IBinding;
026import org.apache.tapestry.IComponent;
027import org.apache.tapestry.binding.BindingSource;
028import org.apache.tapestry.event.PageDetachListener;
029import org.apache.tapestry.spec.IComponentSpecification;
030import 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 */
041public 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}