001    // Copyright 2006, 2007, 2008, 2009, 2010, 2011 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.tapestry5.internal.model;
016    
017    import org.apache.tapestry5.ioc.Location;
018    import org.apache.tapestry5.ioc.Resource;
019    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
020    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
021    import org.apache.tapestry5.ioc.util.IdAllocator;
022    import org.apache.tapestry5.model.*;
023    import org.slf4j.Logger;
024    
025    import java.util.Collections;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Set;
029    
030    /**
031     * Internal implementation of {@link org.apache.tapestry5.model.MutableComponentModel}.
032     */
033    public final class MutableComponentModelImpl implements MutableComponentModel
034    {
035        private final ComponentModel parentModel;
036    
037        private final Resource baseResource;
038    
039        private final String componentClassName;
040    
041        private final IdAllocator persistentFieldNameAllocator = new IdAllocator();
042    
043        private final Logger logger;
044    
045        private final boolean pageClass;
046    
047        private Map<String, ParameterModel> parameters;
048    
049        private Map<String, EmbeddedComponentModel> embeddedComponents;
050    
051        /**
052         * Maps from field name to strategy.
053         */
054        private Map<String, String> persistentFields;
055    
056        private List<String> mixinClassNames;
057    
058        private Map<String, String[]> mixinOrders;
059    
060        private boolean informalParametersSupported;
061    
062        private boolean mixinAfter;
063    
064        private Map<String, String> metaData;
065    
066        private Set<Class> handledRenderPhases;
067    
068        private Map<String, Boolean> handledEvents;
069    
070        public MutableComponentModelImpl(String componentClassName, Logger logger, Resource baseResource,
071                                         ComponentModel parentModel, boolean pageClass)
072        {
073            this.componentClassName = componentClassName;
074            this.logger = logger;
075            this.baseResource = baseResource;
076            this.parentModel = parentModel;
077            this.pageClass = pageClass;
078    
079            // Pre-allocate names from the parent, to avoid name collisions.
080    
081            if (this.parentModel != null)
082            {
083                for (String name : this.parentModel.getPersistentFieldNames())
084                {
085                    persistentFieldNameAllocator.allocateId(name);
086                }
087            }
088        }
089    
090        @Override
091        public String toString()
092        {
093            return String.format("ComponentModel[%s]", componentClassName);
094        }
095    
096        public Logger getLogger()
097        {
098            return logger;
099        }
100    
101        public Resource getBaseResource()
102        {
103            return baseResource;
104        }
105    
106        public String getComponentClassName()
107        {
108            return componentClassName;
109        }
110    
111        public void addParameter(String name, boolean required, boolean allowNull, String defaultBindingPrefix,
112                                 boolean cached)
113        {
114            assert InternalUtils.isNonBlank(name);
115            assert InternalUtils.isNonBlank(defaultBindingPrefix);
116    
117            if (parameters == null)
118            {
119                parameters = CollectionFactory.newCaseInsensitiveMap();
120            }
121    
122            if (parameters.containsKey(name))
123            {
124                throw new IllegalArgumentException(String.format("Parameter '%s' of component class %s is already defined.", name, componentClassName));
125            }
126    
127            ParameterModel existingModel = getParameterModel(name);
128    
129            if (existingModel != null)
130            {
131                throw new IllegalArgumentException(String.format("Parameter '%s' of component class %s conflicts with the parameter defined by the %s base class.",
132                        name, componentClassName, existingModel.getComponentModel().getComponentClassName()));
133            }
134    
135            parameters.put(name, new ParameterModelImpl(this, name, required, allowNull, defaultBindingPrefix, cached));
136        }
137    
138        public void addParameter(String name, boolean required, boolean allowNull, String defaultBindingPrefix)
139        {
140            // assume /false/ for the default because:
141            // if the parameter is actually cached, the only effect will be to reduce that optimization
142            // in certain
143            // scenarios (mixin BindParameter). But if the value is NOT cached but we say it is,
144            // we'll get incorrect behavior.
145            addParameter(name, required, allowNull, defaultBindingPrefix, false);
146        }
147    
148        public ParameterModel getParameterModel(String parameterName)
149        {
150            ParameterModel result = InternalUtils.get(parameters, parameterName);
151    
152            if (result == null && parentModel != null)
153                result = parentModel.getParameterModel(parameterName);
154    
155            return result;
156        }
157    
158        public boolean isFormalParameter(String parameterName)
159        {
160            return getParameterModel(parameterName) != null;
161        }
162    
163        public List<String> getParameterNames()
164        {
165            List<String> names = CollectionFactory.newList();
166    
167            if (parameters != null)
168                names.addAll(parameters.keySet());
169    
170            if (parentModel != null)
171                names.addAll(parentModel.getParameterNames());
172    
173            Collections.sort(names);
174    
175            return names;
176        }
177    
178        public List<String> getDeclaredParameterNames()
179        {
180            return InternalUtils.sortedKeys(parameters);
181        }
182    
183        public MutableEmbeddedComponentModel addEmbeddedComponent(String id, String type, String componentClassName,
184                                                                  boolean inheritInformalParameters, Location location)
185        {
186            // TODO: Parent compent model? Or would we simply override the parent?
187    
188            if (embeddedComponents == null)
189                embeddedComponents = CollectionFactory.newCaseInsensitiveMap();
190            else if (embeddedComponents.containsKey(id))
191                throw new IllegalArgumentException(ModelMessages.duplicateComponentId(id, this.componentClassName));
192    
193            MutableEmbeddedComponentModel embedded = new MutableEmbeddedComponentModelImpl(id, type, componentClassName,
194                    this.componentClassName, inheritInformalParameters, location);
195    
196            embeddedComponents.put(id, embedded);
197    
198            return embedded; // So that parameters can be filled in
199        }
200    
201        public List<String> getEmbeddedComponentIds()
202        {
203            List<String> result = CollectionFactory.newList();
204    
205            if (embeddedComponents != null)
206                result.addAll(embeddedComponents.keySet());
207    
208            if (parentModel != null)
209                result.addAll(parentModel.getEmbeddedComponentIds());
210    
211            Collections.sort(result);
212    
213            return result;
214        }
215    
216        public EmbeddedComponentModel getEmbeddedComponentModel(String componentId)
217        {
218            EmbeddedComponentModel result = InternalUtils.get(embeddedComponents, componentId);
219    
220            if (result == null && parentModel != null)
221                result = parentModel.getEmbeddedComponentModel(componentId);
222    
223            return result;
224        }
225    
226        public String getFieldPersistenceStrategy(String fieldName)
227        {
228            String result = InternalUtils.get(persistentFields, fieldName);
229    
230            if (result == null && parentModel != null)
231                result = parentModel.getFieldPersistenceStrategy(fieldName);
232    
233            if (result == null)
234                throw new IllegalArgumentException(ModelMessages.missingPersistentField(fieldName));
235    
236            return result;
237        }
238    
239        public List<String> getPersistentFieldNames()
240        {
241            return persistentFieldNameAllocator.getAllocatedIds();
242        }
243    
244        public String setFieldPersistenceStrategy(String fieldName, String strategy)
245        {
246            String logicalFieldName = persistentFieldNameAllocator.allocateId(fieldName);
247    
248            if (persistentFields == null)
249                persistentFields = CollectionFactory.newMap();
250    
251            persistentFields.put(logicalFieldName, strategy);
252    
253            return logicalFieldName;
254        }
255    
256        public boolean isRootClass()
257        {
258            return parentModel == null;
259        }
260    
261        public void addMixinClassName(String mixinClassName, String... order)
262        {
263            if (mixinClassNames == null)
264                mixinClassNames = CollectionFactory.newList();
265    
266            mixinClassNames.add(mixinClassName);
267            if (order != null && order.length > 0)
268            {
269                if (mixinOrders == null)
270                    mixinOrders = CollectionFactory.newCaseInsensitiveMap();
271                mixinOrders.put(mixinClassName, order);
272            }
273        }
274    
275        public List<String> getMixinClassNames()
276        {
277            List<String> result = CollectionFactory.newList();
278    
279            if (mixinClassNames != null)
280                result.addAll(mixinClassNames);
281    
282            if (parentModel != null)
283                result.addAll(parentModel.getMixinClassNames());
284    
285            Collections.sort(result);
286    
287            return result;
288        }
289    
290        public void enableSupportsInformalParameters()
291        {
292            informalParametersSupported = true;
293        }
294    
295        public boolean getSupportsInformalParameters()
296        {
297            return informalParametersSupported;
298        }
299    
300        public ComponentModel getParentModel()
301        {
302            return parentModel;
303        }
304    
305        public boolean isMixinAfter()
306        {
307            return mixinAfter;
308        }
309    
310        public void setMixinAfter(boolean mixinAfter)
311        {
312            this.mixinAfter = mixinAfter;
313        }
314    
315        public void setMeta(String key, String value)
316        {
317            assert InternalUtils.isNonBlank(key);
318            assert InternalUtils.isNonBlank(value);
319            if (metaData == null)
320                metaData = CollectionFactory.newCaseInsensitiveMap();
321    
322            // TODO: Error if duplicate?
323    
324            metaData.put(key, value);
325        }
326    
327        public void addRenderPhase(Class renderPhase)
328        {
329            assert renderPhase != null;
330            if (handledRenderPhases == null)
331                handledRenderPhases = CollectionFactory.newSet();
332    
333            handledRenderPhases.add(renderPhase);
334        }
335    
336        public void addEventHandler(String eventType)
337        {
338            if (handledEvents == null)
339                handledEvents = CollectionFactory.newCaseInsensitiveMap();
340    
341            handledEvents.put(eventType, true);
342        }
343    
344        public String getMeta(String key)
345        {
346            String result = InternalUtils.get(metaData, key);
347    
348            if (result == null && parentModel != null)
349                result = parentModel.getMeta(key);
350    
351            return result;
352        }
353    
354        public Set<Class> getHandledRenderPhases()
355        {
356            Set<Class> result = CollectionFactory.newSet();
357    
358            if (parentModel != null)
359                result.addAll(parentModel.getHandledRenderPhases());
360    
361            if (handledRenderPhases != null)
362                result.addAll(handledRenderPhases);
363    
364            return result;
365        }
366    
367        public boolean handlesEvent(String eventType)
368        {
369            if (InternalUtils.get(handledEvents, eventType) != null)
370                return true;
371    
372            return parentModel == null ? false : parentModel.handlesEvent(eventType);
373        }
374    
375        public String[] getOrderForMixin(String mixinClassName)
376        {
377            final String[] orders = InternalUtils.get(mixinOrders, mixinClassName);
378    
379            if (orders == null && parentModel != null)
380                return parentModel.getOrderForMixin(mixinClassName);
381    
382            return orders;
383        }
384    
385        public boolean isPage()
386        {
387            return pageClass;
388        }
389    }