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