001    // Copyright 2009, 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.pageload;
016    
017    import org.apache.tapestry5.internal.TapestryInternalUtils;
018    import org.apache.tapestry5.internal.services.ComponentInstantiatorSource;
019    import org.apache.tapestry5.internal.services.Instantiator;
020    import org.apache.tapestry5.internal.structure.ComponentPageElement;
021    import org.apache.tapestry5.ioc.Location;
022    import org.apache.tapestry5.ioc.Orderable;
023    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
024    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
025    import org.apache.tapestry5.ioc.internal.util.TapestryException;
026    import org.apache.tapestry5.model.ComponentModel;
027    import org.apache.tapestry5.model.EmbeddedComponentModel;
028    import org.apache.tapestry5.services.ComponentClassResolver;
029    import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
030    
031    import java.util.HashSet;
032    import java.util.Map;
033    import java.util.Set;
034    
035    public class EmbeddedComponentAssemblerImpl implements EmbeddedComponentAssembler
036    {
037        private final ComponentInstantiatorSource instantiatorSource;
038    
039        private final ComponentAssemblerSource assemblerSource;
040    
041        private final ComponentResourceSelector selector;
042    
043        private final ComponentModel componentModel;
044    
045        private final Location location;
046    
047        private final Map<String, Instantiator> mixinIdToInstantiator = CollectionFactory.newCaseInsensitiveMap();
048        private final Map<String, String[]> mixinsIdToOrderConstraints = CollectionFactory.newCaseInsensitiveMap();
049    
050        /**
051         * Maps parameter names (both simple, and qualified with the mixin id) to the corresponding QualifiedParameterName.
052         */
053        private final Map<String, ParameterBinder> parameterNameToBinder = CollectionFactory.newCaseInsensitiveMap();
054    
055        // The id of the mixin to receive informal parameters. If null, the component itself recieves them.
056        // If the component doesn't support them, they are quietly dropped.
057    
058        private final String informalParametersMixinId;
059    
060        private final String componentPsuedoMixinId;
061    
062        private Map<String, Boolean> bound;
063    
064        /**
065         * @param assemblerSource
066         * @param instantiatorSource     used to access component models
067         * @param componentClassResolver used to convert mixin types to component models
068         * @param componentClassName     class name of embedded component
069         * @param selector               used to select template and other resources
070         * @param embeddedModel          embedded model (may be null for components defined in the template)
071         * @param templateMixins         list of mixins from the t:mixins element (possibly null)
072         * @param location               location of components element in its container's template
073         */
074        public EmbeddedComponentAssemblerImpl(ComponentAssemblerSource assemblerSource,
075                                              ComponentInstantiatorSource instantiatorSource, ComponentClassResolver componentClassResolver,
076                                              String componentClassName, ComponentResourceSelector selector, EmbeddedComponentModel embeddedModel,
077                                              String templateMixins, Location location)
078        {
079            this.assemblerSource = assemblerSource;
080            this.instantiatorSource = instantiatorSource;
081            this.selector = selector;
082            this.location = location;
083    
084            componentModel = getModel(componentClassName);
085    
086            // Add the implementation mixins defined by the component model.
087    
088            for (String className : componentModel.getMixinClassNames())
089            {
090                addMixin(className, componentModel.getOrderForMixin(className));
091            }
092    
093            // If there's an embedded model (i.e., there was an @Component annotation)
094            // then it may define some mixins.
095    
096            if (embeddedModel != null)
097            {
098                for (String className : embeddedModel.getMixinClassNames())
099                {
100                    addMixin(className, embeddedModel.getConstraintsForMixin(className));
101                }
102            }
103    
104            // And the template may include a t:mixins element to define yet more mixins.
105            // Template strings specified as:
106            for (String mixinDef : TapestryInternalUtils.splitAtCommas(templateMixins))
107            {
108                Orderable<String> order = TapestryInternalUtils.mixinTypeAndOrder(mixinDef);
109                String className = componentClassResolver.resolveMixinTypeToClassName(order.getId());
110    
111                addMixin(className, order.getConstraints());
112            }
113    
114            // Finally (new in 5.3, for TAP5-1680), the component itself can sometimes acts as a mixin;
115            // this allows for dealing with parameter name conflicts between the component and the mixin
116            // (especially where informal parameters are involved).
117    
118            componentPsuedoMixinId = InternalUtils.lastTerm(componentClassName);
119    
120            informalParametersMixinId = prescanMixins();
121    
122        }
123    
124        private String prescanMixins()
125        {
126            // Mixin id found to support informal parameters
127    
128            String supportsInformals = null;
129    
130            for (Map.Entry<String, Instantiator> entry : mixinIdToInstantiator.entrySet())
131            {
132                String mixinId = entry.getKey();
133                ComponentModel mixinModel = entry.getValue().getModel();
134    
135                updateParameterNameToQualified(mixinId, mixinModel);
136    
137                if (supportsInformals == null && mixinModel.getSupportsInformalParameters())
138                    supportsInformals = mixinId;
139            }
140    
141            // The component comes last and overwrites simple names from the others.
142    
143            updateParameterNameToQualified(null, componentModel);
144    
145            return supportsInformals;
146        }
147    
148        private void updateParameterNameToQualified(String mixinId, ComponentModel model)
149        {
150            for (String parameterName : model.getParameterNames())
151            {
152                String defaultBindingPrefix = model.getParameterModel(parameterName).getDefaultBindingPrefix();
153    
154                ParameterBinderImpl binder = new ParameterBinderImpl(mixinId, parameterName, defaultBindingPrefix);
155    
156                parameterNameToBinder.put(parameterName, binder);
157    
158                if (mixinId != null)
159                    parameterNameToBinder.put(mixinId + "." + parameterName, binder);
160            }
161        }
162    
163        private void addMixin(String className, String... order)
164        {
165            Instantiator mixinInstantiator = instantiatorSource.getInstantiator(className);
166    
167            String mixinId = InternalUtils.lastTerm(className);
168    
169            if (mixinIdToInstantiator.containsKey(mixinId))
170                throw new TapestryException(PageloadMessages.uniqueMixinRequired(mixinId), location, null);
171    
172            mixinIdToInstantiator.put(mixinId, mixinInstantiator);
173            mixinsIdToOrderConstraints.put(mixinId, order);
174        }
175    
176        private ComponentModel getModel(String className)
177        {
178            return instantiatorSource.getInstantiator(className).getModel();
179        }
180    
181        public ComponentAssembler getComponentAssembler()
182        {
183            return assemblerSource.getAssembler(componentModel.getComponentClassName(), selector);
184        }
185    
186    
187        public ParameterBinder createParameterBinder(String qualifiedParameterName)
188        {
189            int dotx = qualifiedParameterName.indexOf('.');
190    
191            if (dotx < 0)
192            {
193                return createParameterBinderFromSimpleParameterName(qualifiedParameterName);
194            }
195    
196            return createParameterBinderFromQualifiedParameterName(qualifiedParameterName, qualifiedParameterName.substring(0, dotx),
197                    qualifiedParameterName.substring(dotx + 1));
198        }
199    
200        private ParameterBinder createParameterBinderFromSimpleParameterName(String parameterName)
201        {
202    
203            // Look for a *formal* parameter with the simple name on the component itself.
204    
205            ParameterBinder binder = getComponentAssembler().getBinder(parameterName);
206    
207            if (binder != null)
208            {
209                return binder;
210            }
211    
212            // Next see if any mixin has a formal parameter with this simple name.
213    
214            binder = parameterNameToBinder.get(parameterName);
215    
216            if (binder != null)
217            {
218                return binder;
219            }
220    
221    
222            // So, is there an mixin that's claiming all informal parameters?
223    
224            if (informalParametersMixinId != null)
225            {
226                return new ParameterBinderImpl(informalParametersMixinId, parameterName, null);
227            }
228    
229            // Maybe the component claims informal parameters?
230            if (componentModel.getSupportsInformalParameters())
231                return new ParameterBinderImpl(null, parameterName, null);
232    
233            // Otherwise, informal parameter are not supported by the component or any mixin.
234    
235            return null;
236        }
237    
238        private ParameterBinder createParameterBinderFromQualifiedParameterName(String qualifiedParameterName, String mixinId, String parameterName)
239        {
240    
241            if (mixinId.equalsIgnoreCase(componentPsuedoMixinId))
242            {
243                return createParameterBinderForComponent(qualifiedParameterName, parameterName);
244            }
245    
246            if (!mixinIdToInstantiator.containsKey(mixinId))
247            {
248                throw new TapestryException(
249                        String.format("Mixin id for parameter '%s' not found. Attached mixins: %s.", qualifiedParameterName,
250                                InternalUtils.joinSorted(mixinIdToInstantiator.keySet())), location,
251                        null);
252            }
253    
254            ParameterBinder binder = parameterNameToBinder.get(qualifiedParameterName);
255    
256            if (binder != null)
257            {
258                return binder;
259            }
260    
261            // Ok, so perhaps this is a qualified name for an informal parameter of the mixin.
262    
263            Instantiator instantiator = mixinIdToInstantiator.get(mixinId);
264    
265            assert instantiator != null;
266    
267            return bindInformalParameter(qualifiedParameterName, mixinId, parameterName, instantiator.getModel());
268        }
269    
270        private ParameterBinder bindInformalParameter(String qualifiedParameterName, String mixinId, String parameterName, ComponentModel model)
271        {
272            if (model.getSupportsInformalParameters())
273            {
274                return new ParameterBinderImpl(mixinId, parameterName, null);
275            }
276    
277            // Pretty sure this was not caught as an error in 5.2.
278    
279            throw new TapestryException(String.format("Binding parameter %s as an informal parameter does not make sense, as %s does not support informal parameters.",
280                    qualifiedParameterName, model.getComponentClassName()), location, null);
281        }
282    
283        private ParameterBinder createParameterBinderForComponent(String qualifiedParameterName, String parameterName)
284        {
285            ParameterBinder binder = getComponentAssembler().getBinder(parameterName);
286    
287            if (binder != null)
288            {
289                return binder;
290            }
291    
292            return bindInformalParameter(qualifiedParameterName, null, parameterName, componentModel);
293        }
294    
295    
296        public boolean isBound(String parameterName)
297        {
298            return InternalUtils.get(bound, parameterName) != null;
299        }
300    
301        public void setBound(String parameterName)
302        {
303            if (bound == null)
304                bound = CollectionFactory.newCaseInsensitiveMap();
305    
306            bound.put(parameterName, true);
307        }
308    
309        public int addMixinsToElement(ComponentPageElement newElement)
310        {
311            for (Map.Entry<String, Instantiator> entry : mixinIdToInstantiator.entrySet())
312            {
313                String mixinId = entry.getKey();
314                Instantiator instantiator = entry.getValue();
315    
316                newElement.addMixin(mixinId, instantiator, mixinsIdToOrderConstraints.get(mixinId));
317            }
318    
319            return mixinIdToInstantiator.size();
320        }
321    
322        public Location getLocation()
323        {
324            return location;
325        }
326    
327        public Set<String> getFormalParameterNames()
328        {
329            return new HashSet<String>(componentModel.getParameterNames());
330        }
331    }