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
015package org.apache.tapestry5.internal.model;
016
017import org.apache.tapestry5.ioc.Location;
018import org.apache.tapestry5.ioc.Resource;
019import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
020import org.apache.tapestry5.ioc.internal.util.InternalUtils;
021import org.apache.tapestry5.ioc.util.IdAllocator;
022import org.apache.tapestry5.model.*;
023import org.slf4j.Logger;
024
025import java.util.Collections;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029
030/**
031 * Internal implementation of {@link org.apache.tapestry5.model.MutableComponentModel}.
032 */
033public 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}