    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}