001// Copyright 2004, 2005 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.tapestry.spec;
016
017import java.util.ArrayList;
018import java.util.Collection;
019import java.util.Collections;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.Iterator;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026
027import org.apache.hivemind.ApplicationRuntimeException;
028import org.apache.hivemind.HiveMind;
029import org.apache.hivemind.Resource;
030import org.apache.hivemind.util.ToStringBuilder;
031
032/**
033 * A specification for a component, as read from an XML specification file.
034 * <p>
035 * A specification consists of
036 * <ul>
037 * <li>An implementing class
038 * <li>An optional description
039 * <li>A set of contained components
040 * <li>Bindings for the properties of each contained component
041 * <li>A set of named assets
042 * <li>Definitions for managed beans
043 * <li>Any reserved names (used for HTML attributes)
044 * <li>Declared properties
045 * <li>Property injections
046 * </ul>
047 * <p>
048 * From this information, an actual component may be instantiated and initialized. Instantiating a
049 * component is usually a recursive process, since to initialize a container component, it is
050 * necessary to instantiate and initialize its contained components as well.
051 * 
052 * @see org.apache.tapestry.IComponent
053 * @see IContainedComponent
054 * @see org.apache.tapestry.engine.IPageLoader
055 * @author Howard Lewis Ship
056 */
057
058public class ComponentSpecification extends LocatablePropertyHolder implements
059        IComponentSpecification
060{
061    private String _componentClassName;
062
063    /** @since 1.0.9 * */
064
065    private String _description;
066
067    /**
068     * Keyed on component id, value is {@link IContainedComponent}.
069     */
070
071    protected Map _components;
072
073    /**
074     * Keyed on asset name, value is {@link IAssetSpecification}.
075     */
076
077    protected Map _assets;
078
079    /**
080     * Defines all formal parameters. Keyed on parameter name, value is
081     * {@link IParameterSpecification}.
082     */
083
084    protected Map _parameters;
085
086    /**
087     * Defines all helper beans. Keyed on name, value is {@link IBeanSpecification}.
088     * 
089     * @since 1.0.4
090     */
091
092    protected Map _beans;
093
094    /**
095     * The names of all reserved informal parameter names (as lower-case). This allows the page
096     * loader to filter out any informal parameters during page load, rather than during render.
097     * 
098     * @since 1.0.5
099     */
100
101    protected Set _reservedParameterNames;
102
103    /**
104     * Is the component allowed to have a body (that is, wrap other elements?).
105     */
106
107    private boolean _allowBody = true;
108
109    /**
110     * Is the component allow to have informal parameter specified.
111     */
112
113    private boolean _allowInformalParameters = true;
114
115    /**
116     * The XML Public Id used when the page or component specification was read (if applicable).
117     * 
118     * @since 2.2
119     */
120
121    private String _publicId;
122
123    /**
124     * Indicates that the specification is for a page, not a component.
125     * 
126     * @since 2.2
127     */
128
129    private boolean _pageSpecification;
130
131    /**
132     * The location from which the specification was obtained.
133     * 
134     * @since 3.0
135     */
136
137    private Resource _specificationLocation;
138
139    /**
140     * A Map of {@link IPropertySpecification}keyed on the name of the property.
141     * 
142     * @since 3.0
143     */
144
145    private Map _propertySpecifications;
146
147    /**
148     * List of {@link InjectSpecification}.
149     * 
150     * @since 4.0
151     */
152
153    private List _injectSpecifications;
154
155    /**
156     * Keyed on property name, value is some other object (such as an IAssetSpecification) that has
157     * claimed a property of the page.
158     * 
159     * @since 4.0
160     */
161
162    private Map _claimedProperties;
163
164    /**
165     * @since 4.0
166     */
167
168    private boolean _deprecated = false;
169
170    /**
171     * @throws ApplicationRuntimeException
172     *             if the name already exists.
173     */
174
175    public void addAsset(String name, IAssetSpecification asset)
176    {
177        if (_assets == null)
178            _assets = new HashMap();
179
180        IAssetSpecification existing = (IAssetSpecification) _assets.get(name);
181
182        if (existing != null)
183            throw new ApplicationRuntimeException(SpecMessages.duplicateAsset(name, existing),
184                    asset.getLocation(), null);
185
186        claimProperty(asset.getPropertyName(), asset);
187
188        _assets.put(name, asset);
189
190    }
191
192    /**
193     * @throws ApplicationRuntimeException
194     *             if the id is already defined.
195     */
196
197    public void addComponent(String id, IContainedComponent component)
198    {
199        if (_components == null)
200            _components = new HashMap();
201
202        IContainedComponent existing = (IContainedComponent) _components.get(id);
203
204        if (existing != null)
205            throw new ApplicationRuntimeException(SpecMessages.duplicateComponent(id, existing),
206                    component.getLocation(), null);
207
208        _components.put(id, component);
209
210        claimProperty(component.getPropertyName(), component);
211    }
212
213    /**
214     * Adds the parameter. The name is added as a reserved name.
215     * 
216     * @throws ApplicationRuntimeException
217     *             if the name already exists.
218     */
219
220    public void addParameter(IParameterSpecification spec)
221    {
222        if (_parameters == null)
223            _parameters = new HashMap();
224
225        String name = spec.getParameterName();
226
227        addParameterByName(name, spec);
228
229        Iterator i = spec.getAliasNames().iterator();
230        while (i.hasNext())
231        {
232            String alias = (String) i.next();
233
234            addParameterByName(alias, spec);
235        }
236
237        claimProperty(spec.getPropertyName(), spec);
238    }
239
240    private void addParameterByName(String name, IParameterSpecification spec)
241    {
242        IParameterSpecification existing = (IParameterSpecification) _parameters.get(name);
243
244        if (existing != null)
245            throw new ApplicationRuntimeException(SpecMessages.duplicateParameter(name, existing),
246                    spec.getLocation(), null);
247
248        _parameters.put(name, spec);
249
250        addReservedParameterName(name);
251    }
252
253    /**
254     * Returns true if the component is allowed to wrap other elements (static HTML or other
255     * components). The default is true.
256     * 
257     * @see #setAllowBody(boolean)
258     */
259
260    public boolean getAllowBody()
261    {
262        return _allowBody;
263    }
264
265    /**
266     * Returns true if the component allows informal parameters (parameters not formally defined).
267     * Informal parameters are generally used to create additional HTML attributes for an HTML tag
268     * rendered by the component. This is often used to specify JavaScript event handlers or the
269     * class of the component (for Cascarding Style Sheets).
270     * <p>
271     * The default value is true.
272     * 
273     * @see #setAllowInformalParameters(boolean)
274     */
275
276    public boolean getAllowInformalParameters()
277    {
278        return _allowInformalParameters;
279    }
280
281    /**
282     * Returns the {@link IAssetSpecification}with the given name, or null if no such specification
283     * exists.
284     * 
285     * @see #addAsset(String,IAssetSpecification)
286     */
287
288    public IAssetSpecification getAsset(String name)
289    {
290
291        return (IAssetSpecification) get(_assets, name);
292    }
293
294    /**
295     * Returns a <code>List</code> of the String names of all assets, in alphabetical order
296     */
297
298    public List getAssetNames()
299    {
300        return sortedKeys(_assets);
301    }
302
303    /**
304     * Returns the specification of a contained component with the given id, or null if no such
305     * contained component exists.
306     * 
307     * @see #addComponent(String, IContainedComponent)
308     */
309
310    public IContainedComponent getComponent(String id)
311    {
312        return (IContainedComponent) get(_components, id);
313    }
314
315    public String getComponentClassName()
316    {
317        return _componentClassName;
318    }
319
320    /**
321     * Returns an <code>List</code> of the String names of the {@link IContainedComponent}s for
322     * this component.
323     * 
324     * @see #addComponent(String, IContainedComponent)
325     */
326
327    public List getComponentIds()
328    {
329        return sortedKeys(_components);
330    }
331
332    /**
333     * Returns the specification of a parameter with the given name, or null if no such parameter
334     * exists.
335     * 
336     * @see #addParameter(String, IParameterSpecification)
337     */
338
339    public IParameterSpecification getParameter(String name)
340    {
341        return (IParameterSpecification) get(_parameters, name);
342    }
343
344    public Collection getRequiredParameters()
345    {
346        if (_parameters == null)
347            return Collections.EMPTY_LIST;
348
349        Collection result = new ArrayList();
350
351        Iterator i = _parameters.entrySet().iterator();
352        while (i.hasNext())
353        {
354            Map.Entry entry = (Map.Entry) i.next();
355            String name = (String) entry.getKey();
356            IParameterSpecification spec = (IParameterSpecification) entry.getValue();
357
358            if (!spec.isRequired())
359                continue;
360
361            if (!name.equals(spec.getParameterName()))
362                continue;
363
364            result.add(spec);
365        }
366
367        return result;
368    }
369
370    /**
371     * Returns a List of of String names of all parameters. This list is in alphabetical order.
372     * 
373     * @see #addParameter(String, IParameterSpecification)
374     */
375
376    public List getParameterNames()
377    {
378        return sortedKeys(_parameters);
379    }
380
381    public void setAllowBody(boolean value)
382    {
383        _allowBody = value;
384    }
385
386    public void setAllowInformalParameters(boolean value)
387    {
388        _allowInformalParameters = value;
389    }
390
391    public void setComponentClassName(String value)
392    {
393        _componentClassName = value;
394    }
395
396    /**
397     * @since 1.0.4
398     * @throws ApplicationRuntimeException
399     *             if the bean already has a specification.
400     */
401
402    public void addBeanSpecification(String name, IBeanSpecification specification)
403    {
404        if (_beans == null)
405            _beans = new HashMap();
406
407        IBeanSpecification existing = (IBeanSpecification) _beans.get(name);
408
409        if (existing != null)
410            throw new ApplicationRuntimeException(SpecMessages.duplicateBean(name, existing),
411                    specification.getLocation(), null);
412
413        claimProperty(specification.getPropertyName(), specification);
414
415        _beans.put(name, specification);
416    }
417
418    /**
419     * Returns the {@link IBeanSpecification}for the given name, or null if not such specification
420     * exists.
421     * 
422     * @since 1.0.4
423     */
424
425    public IBeanSpecification getBeanSpecification(String name)
426    {
427        return (IBeanSpecification) get(_beans, name);
428    }
429
430    /**
431     * Returns an unmodifiable collection of the names of all beans.
432     */
433
434    public Collection getBeanNames()
435    {
436        if (_beans == null)
437            return Collections.EMPTY_LIST;
438
439        return Collections.unmodifiableCollection(_beans.keySet());
440    }
441
442    /**
443     * Adds the value as a reserved name. Reserved names are not allowed as the names of informal
444     * parameters. Since the comparison is caseless, the value is converted to lowercase before
445     * being stored.
446     * 
447     * @since 1.0.5
448     */
449
450    public void addReservedParameterName(String value)
451    {
452        if (_reservedParameterNames == null)
453            _reservedParameterNames = new HashSet();
454
455        _reservedParameterNames.add(value.toLowerCase());
456    }
457
458    /**
459     * Returns true if the value specified is in the reserved name list. The comparison is caseless.
460     * All formal parameters are automatically in the reserved name list, as well as any additional
461     * reserved names specified in the component specification. The latter refer to HTML attributes
462     * generated directly by the component.
463     * 
464     * @since 1.0.5
465     */
466
467    public boolean isReservedParameterName(String value)
468    {
469        if (_reservedParameterNames == null)
470            return false;
471
472        return _reservedParameterNames.contains(value.toLowerCase());
473    }
474
475    public String toString()
476    {
477        ToStringBuilder builder = new ToStringBuilder(this);
478
479        builder.append("componentClassName", _componentClassName);
480        builder.append("pageSpecification", _pageSpecification);
481        builder.append("specificationLocation", _specificationLocation);
482        builder.append("allowBody", _allowBody);
483        builder.append("allowInformalParameter", _allowInformalParameters);
484
485        return builder.toString();
486    }
487
488    /**
489     * Returns the documentation for this component.
490     * 
491     * @since 1.0.9
492     */
493
494    public String getDescription()
495    {
496        return _description;
497    }
498
499    /**
500     * Sets the documentation for this component.
501     * 
502     * @since 1.0.9
503     */
504
505    public void setDescription(String description)
506    {
507        _description = description;
508    }
509
510    /**
511     * Returns the XML Public Id for the specification file, or null if not applicable.
512     * <p>
513     * This method exists as a convienience for the Spindle plugin. A previous method used an
514     * arbitrary version string, the public id is more useful and less ambiguous.
515     * 
516     * @since 2.2
517     */
518
519    public String getPublicId()
520    {
521        return _publicId;
522    }
523
524    /** @since 2.2 * */
525
526    public void setPublicId(String publicId)
527    {
528        _publicId = publicId;
529    }
530
531    /**
532     * Returns true if the specification is known to be a page specification and not a component
533     * specification. Earlier versions of the framework did not distinguish between the two, but
534     * starting in 2.2, there are seperate XML entities for pages and components. Pages omit several
535     * attributes and entities related to parameters, as parameters only make sense for components.
536     * 
537     * @since 2.2
538     */
539
540    public boolean isPageSpecification()
541    {
542        return _pageSpecification;
543    }
544
545    /** @since 2.2 * */
546
547    public void setPageSpecification(boolean pageSpecification)
548    {
549        _pageSpecification = pageSpecification;
550    }
551
552    /** @since 2.2 * */
553
554    private List sortedKeys(Map input)
555    {
556        if (input == null)
557            return Collections.EMPTY_LIST;
558
559        List result = new ArrayList(input.keySet());
560
561        Collections.sort(result);
562
563        return result;
564    }
565
566    /** @since 2.2 * */
567
568    private Object get(Map map, Object key)
569    {
570        if (map == null)
571            return null;
572
573        return map.get(key);
574    }
575
576    /** @since 3.0 * */
577
578    public Resource getSpecificationLocation()
579    {
580        return _specificationLocation;
581    }
582
583    /** @since 3.0 * */
584
585    public void setSpecificationLocation(Resource specificationLocation)
586    {
587        _specificationLocation = specificationLocation;
588    }
589
590    /**
591     * Adds a new property specification. The name of the property must not already be defined (and
592     * must not change after being added).
593     * 
594     * @since 3.0
595     */
596
597    public void addPropertySpecification(IPropertySpecification spec)
598    {
599        if (_propertySpecifications == null)
600            _propertySpecifications = new HashMap();
601
602        String name = spec.getName();
603        IPropertySpecification existing = (IPropertySpecification) _propertySpecifications
604                .get(name);
605
606        if (existing != null)
607            throw new ApplicationRuntimeException(SpecMessages.duplicateProperty(name, existing),
608                    spec.getLocation(), null);
609
610        claimProperty(name, spec);
611
612        _propertySpecifications.put(name, spec);
613    }
614
615    /**
616     * Returns a sorted, immutable list of the names of all
617     * {@link org.apache.tapestry.spec.IPropertySpecification}s.
618     * 
619     * @since 3.0
620     */
621
622    public List getPropertySpecificationNames()
623    {
624        return sortedKeys(_propertySpecifications);
625    }
626
627    /**
628     * Returns the named {@link org.apache.tapestry.spec.IPropertySpecification}, or null if no
629     * such specification exist.
630     * 
631     * @since 3.0
632     * @see #addPropertySpecification(IPropertySpecification)
633     */
634
635    public IPropertySpecification getPropertySpecification(String name)
636    {
637        return (IPropertySpecification) get(_propertySpecifications, name);
638    }
639
640    public void addInjectSpecification(InjectSpecification spec)
641    {
642        if (_injectSpecifications == null)
643            _injectSpecifications = new ArrayList();
644
645        claimProperty(spec.getProperty(), spec);
646
647        _injectSpecifications.add(spec);
648    }
649
650    public List getInjectSpecifications()
651    {
652        return safeList(_injectSpecifications);
653    }
654
655    private List safeList(List input)
656    {
657        if (input == null)
658            return Collections.EMPTY_LIST;
659
660        return Collections.unmodifiableList(input);
661    }
662
663    private void claimProperty(String propertyName, Object subSpecification)
664    {
665        if (propertyName == null)
666            return;
667
668        if (_claimedProperties == null)
669            _claimedProperties = new HashMap();
670
671        Object existing = _claimedProperties.get(propertyName);
672
673        if (existing != null)
674            throw new ApplicationRuntimeException(SpecMessages.claimedProperty(
675                    propertyName,
676                    existing), HiveMind.getLocation(subSpecification), null);
677
678        _claimedProperties.put(propertyName, subSpecification);
679    }
680
681    /** @since 4.0 */
682    public boolean isDeprecated()
683    {
684        return _deprecated;
685    }
686
687    /** @since 4.0 */
688    public void setDeprecated(boolean deprecated)
689    {
690        _deprecated = deprecated;
691    }
692
693    public Set getReservedParameterNames()
694    {
695        if (_reservedParameterNames == null)
696            return Collections.EMPTY_SET;
697
698        return Collections.unmodifiableSet(_reservedParameterNames);
699    }
700}