    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    }