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;
016
017import java.util.Collection;
018import java.util.Collections;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.hivemind.ApplicationRuntimeException;
026import org.apache.hivemind.Messages;
027import org.apache.hivemind.impl.BaseLocatable;
028import org.apache.hivemind.util.Defense;
029import org.apache.hivemind.util.PropertyUtils;
030import org.apache.tapestry.bean.BeanProvider;
031import org.apache.tapestry.engine.IPageLoader;
032import org.apache.tapestry.event.PageEvent;
033import org.apache.tapestry.listener.ListenerMap;
034import org.apache.tapestry.spec.IComponentSpecification;
035import org.apache.tapestry.spec.IContainedComponent;
036
037/**
038 * Abstract base class implementing the {@link IComponent}interface.
039 * 
040 * @author Howard Lewis Ship
041 */
042
043public abstract class AbstractComponent extends BaseLocatable implements IComponent
044{
045    /**
046     * The page that contains the component, possibly itself (if the component is in fact, a page).
047     */
048
049    private IPage _page;
050
051    /**
052     * The component which contains the component. This will only be null if the component is
053     * actually a page.
054     */
055
056    private IComponent _container;
057
058    /**
059     * The simple id of this component.
060     */
061
062    private String _id;
063
064    /**
065     * The fully qualified id of this component. This is calculated the first time it is needed,
066     * then cached for later.
067     */
068
069    private String _idPath;
070
071    private static final int MAP_SIZE = 5;
072
073    /**
074     * A {@link Map}of all bindings (for which there isn't a corresponding JavaBeans property); the
075     * keys are the names of formal and informal parameters.
076     */
077
078    private Map _bindings;
079
080    private Map _components;
081
082    private static final int BODY_INIT_SIZE = 5;
083
084    private INamespace _namespace;
085
086    /**
087     * Used in place of JDK 1.3's Collections.EMPTY_MAP (which is not available in JDK 1.2).
088     */
089
090    private static final Map EMPTY_MAP = Collections.unmodifiableMap(new HashMap(1));
091
092    /**
093     * The number of {@link IRender}objects in the body of this component.
094     */
095
096    private int _bodyCount = 0;
097
098    /**
099     * An aray of elements in the body of this component.
100     */
101
102    private IRender[] _body;
103
104    /**
105     * The components' asset map.
106     */
107
108    private Map _assets;
109
110    /**
111     * A mapping that allows public instance methods to be dressed up as {@link IActionListener}
112     * listener objects.
113     * 
114     * @since 1.0.2
115     */
116
117    private ListenerMap _listeners;
118
119    /**
120     * A bean provider; these are lazily created as needed.
121     * 
122     * @since 1.0.4
123     */
124
125    private IBeanProvider _beans;
126
127    /**
128     * Returns true if the component is currently rendering.
129     * 
130     * @see #prepareForRender(IRequestCycle)
131     * @see #cleanupAfterRender(IRequestCycle)
132     * @since 4.0
133     */
134
135    private boolean _rendering;
136
137    /**
138     * @since 4.0
139     */
140
141    private boolean _active;
142
143    /** @since 4.0 */
144
145    private IContainedComponent _containedComponent;
146
147    public void addAsset(String name, IAsset asset)
148    {
149        Defense.notNull(name, "name");
150        Defense.notNull(asset, "asset");
151
152        checkActiveLock();
153
154        if (_assets == null)
155            _assets = new HashMap(MAP_SIZE);
156
157        _assets.put(name, asset);
158    }
159
160    public void addComponent(IComponent component)
161    {
162        Defense.notNull(component, "component");
163
164        checkActiveLock();
165
166        if (_components == null)
167            _components = new HashMap(MAP_SIZE);
168
169        _components.put(component.getId(), component);
170    }
171
172    /**
173     * Adds an element (which may be static text or a component) as a body element of this
174     * component. Such elements are rendered by {@link #renderBody(IMarkupWriter, IRequestCycle)}.
175     * 
176     * @since 2.2
177     */
178
179    public void addBody(IRender element)
180    {
181        Defense.notNull(element, "element");
182
183        // TODO: Tweak the ordering of operations inside the PageLoader so that this
184        // check is allowable. Currently, the component is entering active state
185        // before it loads its template.
186
187        // checkActiveLock();
188
189        // Should check the specification to see if this component
190        // allows body. Curently, this is checked by the component
191        // in render(), which is silly.
192
193        if (_body == null)
194        {
195            _body = new IRender[BODY_INIT_SIZE];
196            _body[0] = element;
197
198            _bodyCount = 1;
199            return;
200        }
201
202        // No more room? Make the array bigger.
203
204        if (_bodyCount == _body.length)
205        {
206            IRender[] newWrapped;
207
208            newWrapped = new IRender[_body.length * 2];
209
210            System.arraycopy(_body, 0, newWrapped, 0, _bodyCount);
211
212            _body = newWrapped;
213        }
214
215        _body[_bodyCount++] = element;
216    }
217
218    /**
219     * Invokes {@link #finishLoad()}. Subclasses may overide as needed, but must invoke this
220     * implementation. {@link BaseComponent} loads its HTML template.
221     */
222
223    public void finishLoad(IRequestCycle cycle, IPageLoader loader,
224            IComponentSpecification specification)
225    {
226        finishLoad();
227    }
228
229    /**
230     * Converts informal parameters into additional attributes on the curently open tag.
231     * <p>
232     * Invoked from subclasses to allow additional attributes to be specified within a tag (this
233     * works best when there is a one-to-one corespondence between an {@link IComponent}and a HTML
234     * element.
235     * <p>
236     * Iterates through the bindings for this component. Filters out bindings for formal parameters.
237     * <p>
238     * For each acceptible key, the value is extracted using {@link IBinding#getObject()}. If the
239     * value is null, no attribute is written.
240     * <p>
241     * If the value is an instance of {@link IAsset}, then {@link IAsset#buildURL()}
242     * is invoked to convert the asset to a URL.
243     * <p>
244     * Finally, {@link IMarkupWriter#attribute(String,String)}is invoked with the value (or the
245     * URL).
246     * <p>
247     * The most common use for informal parameters is to support the HTML class attribute (for use
248     * with cascading style sheets) and to specify JavaScript event handlers.
249     * <p>
250     * Components are only required to generate attributes on the result phase; this can be skipped
251     * during the rewind phase.
252     */
253
254    protected void renderInformalParameters(IMarkupWriter writer, IRequestCycle cycle)
255    {
256        String attribute;
257
258        if (_bindings == null)
259            return;
260
261        Iterator i = _bindings.entrySet().iterator();
262
263        while (i.hasNext())
264        {
265            Map.Entry entry = (Map.Entry) i.next();
266            String name = (String) entry.getKey();
267
268            if (isFormalParameter(name))
269                continue;
270
271            IBinding binding = (IBinding) entry.getValue();
272
273            Object value = binding.getObject();
274            if (value == null)
275                continue;
276
277            if (value instanceof IAsset)
278            {
279                IAsset asset = (IAsset) value;
280
281                // Get the URL of the asset and insert that.
282
283                attribute = asset.buildURL();
284            }
285            else
286                attribute = value.toString();
287
288            writer.attribute(name, attribute);
289        }
290
291    }
292
293    /** @since 4.0 */
294    private boolean isFormalParameter(String name)
295    {
296        Defense.notNull(name, "name");
297
298        return getSpecification().getParameter(name) != null;
299    }
300
301    /**
302     * Returns the named binding, or null if it doesn't exist.
303     * <p>
304     * In Tapestry 3.0, it was possible to force a binding to be stored in a component property by
305     * defining a concrete or abstract property named "nameBinding" of type {@link IBinding}. This
306     * has been removed in release 4.0 and bindings are always stored inside a Map of the component.
307     * 
308     * @see #setBinding(String,IBinding)
309     */
310
311    public IBinding getBinding(String name)
312    {
313        Defense.notNull(name, "name");
314
315        if (_bindings == null)
316            return null;
317
318        return (IBinding) _bindings.get(name);
319    }
320
321    /**
322     * Returns true if the specified parameter is bound.
323     * 
324     * @since 4.0
325     */
326
327    public boolean isParameterBound(String parameterName)
328    {
329        Defense.notNull(parameterName, "parameterName");
330
331        return _bindings != null && _bindings.containsKey(parameterName);
332    }
333
334    public IComponent getComponent(String id)
335    {
336        Defense.notNull(id, "id");
337
338        IComponent result = null;
339
340        if (_components != null)
341            result = (IComponent) _components.get(id);
342
343        if (result == null)
344            throw new ApplicationRuntimeException(Tapestry.format("no-such-component", this, id),
345                    this, null, null);
346
347        return result;
348    }
349
350    public IComponent getContainer()
351    {
352        return _container;
353    }
354
355    public void setContainer(IComponent value)
356    {
357        checkActiveLock();
358
359        if (_container != null)
360            throw new ApplicationRuntimeException(Tapestry
361                    .getMessage("AbstractComponent.attempt-to-change-container"));
362
363        _container = value;
364    }
365
366    /**
367     * Returns the name of the page, a slash, and this component's id path. Pages are different,
368     * they override this method to simply return their page name.
369     * 
370     * @see #getIdPath()
371     */
372
373    public String getExtendedId()
374    {
375        if (_page == null)
376            return null;
377
378        return _page.getPageName() + "/" + getIdPath();
379    }
380
381    public String getId()
382    {
383        return _id;
384    }
385
386    public void setId(String value)
387    {
388        if (_id != null)
389            throw new ApplicationRuntimeException(Tapestry
390                    .getMessage("AbstractComponent.attempt-to-change-component-id"));
391
392        _id = value;
393    }
394
395    public String getIdPath()
396    {
397        String containerIdPath;
398
399        if (_container == null)
400            throw new NullPointerException(Tapestry
401                    .format("AbstractComponent.null-container", this));
402
403        containerIdPath = _container.getIdPath();
404
405        if (containerIdPath == null)
406            _idPath = _id;
407        else
408            _idPath = containerIdPath + "." + _id;
409
410        return _idPath;
411    }
412
413    public IPage getPage()
414    {
415        return _page;
416    }
417
418    public void setPage(IPage value)
419    {
420        if (_page != null)
421            throw new ApplicationRuntimeException(Tapestry
422                    .getMessage("AbstractComponent.attempt-to-change-page"));
423
424        _page = value;
425    }
426
427    /**
428     * Renders all elements wrapped by the receiver.
429     */
430
431    public void renderBody(IMarkupWriter writer, IRequestCycle cycle)
432    {
433        for (int i = 0; i < _bodyCount; i++)
434            _body[i].render(writer, cycle);
435    }
436
437    /**
438     * Adds the binding with the given name, replacing any existing binding with that name.
439     * <p>
440     * 
441     * @see #getBinding(String)
442     */
443
444    public void setBinding(String name, IBinding binding)
445    {
446        Defense.notNull(name, "name");
447        Defense.notNull(binding, "binding");
448
449        if (_bindings == null)
450            _bindings = new HashMap(MAP_SIZE);
451
452        _bindings.put(name, binding);
453    }
454
455    public String toString()
456    {
457        StringBuffer buffer;
458
459        buffer = new StringBuffer(super.toString());
460
461        buffer.append('[');
462
463        buffer.append(getExtendedId());
464
465        buffer.append(']');
466
467        return buffer.toString();
468    }
469
470    /**
471     * Returns an unmodifiable {@link Map}of components, keyed on component id. Never returns null,
472     * but may return an empty map. The returned map is immutable.
473     */
474
475    public Map getComponents()
476    {
477        if (_components == null)
478            return EMPTY_MAP;
479
480        return Collections.unmodifiableMap(_components);
481
482    }
483
484    public Map getAssets()
485    {
486        if (_assets == null)
487            return EMPTY_MAP;
488
489        return Collections.unmodifiableMap(_assets);
490    }
491
492    public IAsset getAsset(String name)
493    {
494        if (_assets == null)
495            return null;
496
497        return (IAsset) _assets.get(name);
498    }
499
500    public Collection getBindingNames()
501    {
502        // If no conainer, i.e. a page, then no bindings.
503
504        if (_container == null)
505            return null;
506
507        HashSet result = new HashSet();
508
509        // All the informal bindings go into the bindings Map.
510
511        if (_bindings != null)
512            result.addAll(_bindings.keySet());
513
514        // Now, iterate over the formal parameters and add the formal parameters
515        // that have a binding.
516
517        List names = getSpecification().getParameterNames();
518
519        int count = names.size();
520
521        for (int i = 0; i < count; i++)
522        {
523            String name = (String) names.get(i);
524
525            if (result.contains(name))
526                continue;
527
528            if (getBinding(name) != null)
529                result.add(name);
530        }
531
532        return result;
533    }
534
535    /**
536     * Returns an unmodifiable {@link Map}of all bindings for this component.
537     * 
538     * @since 1.0.5
539     */
540
541    public Map getBindings()
542    {
543        if (_bindings == null)
544            return Collections.EMPTY_MAP;
545
546        return Collections.unmodifiableMap(_bindings);
547    }
548
549    /**
550     * Returns a {@link ListenerMap}&nbsp;for the component. A ListenerMap contains a number of
551     * synthetic read-only properties that implement the {@link IActionListener}interface, but in
552     * fact, cause public instance methods to be invoked.
553     * 
554     * @since 1.0.2
555     */
556
557    public ListenerMap getListeners()
558    {
559        // This is what's called a violation of the Law of Demeter!
560        // This should probably be converted over to some kind of injection, as with
561        // getMessages(), etc.
562
563        if (_listeners == null)
564            _listeners = getPage().getEngine().getInfrastructure().getListenerMapSource()
565                    .getListenerMapForObject(this);
566
567        return _listeners;
568    }
569
570    /**
571     * Returns the {@link IBeanProvider}for this component. This is lazily created the first time
572     * it is needed.
573     * 
574     * @since 1.0.4
575     */
576
577    public IBeanProvider getBeans()
578    {
579        if (_beans == null)
580            _beans = new BeanProvider(this);
581
582        return _beans;
583    }
584
585    /**
586     * Invoked, as a convienience, from
587     * {@link #finishLoad(IRequestCycle, IPageLoader, IComponentSpecification)}. This implemenation
588     * does nothing. Subclasses may override without invoking this implementation.
589     * 
590     * @since 1.0.5
591     */
592
593    protected void finishLoad()
594    {
595    }
596
597    /**
598     * The main method used to render the component. Invokes
599     * {@link #prepareForRender(IRequestCycle)}, then
600     * {@link #renderComponent(IMarkupWriter, IRequestCycle)}.
601     * {@link #cleanupAfterRender(IRequestCycle)}is invoked in a <code>finally</code> block.
602     * <p>
603     * Subclasses should not override this method; instead they will implement
604     * {@link #renderComponent(IMarkupWriter, IRequestCycle)}.
605     * 
606     * @since 2.0.3
607     */
608
609    public final void render(IMarkupWriter writer, IRequestCycle cycle)
610    {
611        try
612        {
613            _rendering = true;
614
615            prepareForRender(cycle);
616
617            renderComponent(writer, cycle);
618        }
619        finally
620        {
621            _rendering = false;
622
623            cleanupAfterRender(cycle);
624        }
625    }
626
627    /**
628     * Invoked by {@link #render(IMarkupWriter, IRequestCycle)}to prepare the component to render.
629     * This implementation sets JavaBeans properties from matching bound parameters. This
630     * implementation does nothing.
631     * 
632     * @since 2.0.3
633     */
634
635    protected void prepareForRender(IRequestCycle cycle)
636    {
637    }
638
639    /**
640     * Invoked by {@link #render(IMarkupWriter, IRequestCycle)}to actually render the component
641     * (with any parameter values already set). This is the method that subclasses must implement.
642     * 
643     * @since 2.0.3
644     */
645
646    protected abstract void renderComponent(IMarkupWriter writer, IRequestCycle cycle);
647
648    /**
649     * Invoked by {@link #render(IMarkupWriter, IRequestCycle)}after the component renders. This
650     * implementation does nothing.
651     * 
652     * @since 2.0.3
653     */
654
655    protected void cleanupAfterRender(IRequestCycle cycle)
656    {
657    }
658
659    public INamespace getNamespace()
660    {
661        return _namespace;
662    }
663
664    public void setNamespace(INamespace namespace)
665    {
666        _namespace = namespace;
667    }
668
669    /**
670     * Returns the body of the component, the element (which may be static HTML or components) that
671     * the component immediately wraps. May return null. Do not modify the returned array. The array
672     * may be padded with nulls.
673     * 
674     * @since 2.3
675     * @see #getBodyCount()
676     */
677
678    public IRender[] getBody()
679    {
680        return _body;
681    }
682
683    /**
684     * Returns the active number of elements in the the body, which may be zero.
685     * 
686     * @since 2.3
687     * @see #getBody()
688     */
689
690    public int getBodyCount()
691    {
692        return _bodyCount;
693    }
694
695    /**
696     * Empty implementation of
697     * {@link org.apache.tapestry.event.PageRenderListener#pageEndRender(PageEvent)}. This allows
698     * classes to implement {@link org.apache.tapestry.event.PageRenderListener}and only implement
699     * the {@link org.apache.tapestry.event.PageRenderListener#pageBeginRender(PageEvent)}method.
700     * 
701     * @since 3.0
702     */
703
704    public void pageEndRender(PageEvent event)
705    {
706    }
707
708    /**
709     * Sets a property of a component.
710     * 
711     * @see IComponent
712     * @since 3.0
713     * @deprecated
714     */
715    public void setProperty(String propertyName, Object value)
716    {
717        PropertyUtils.write(this, propertyName, value);
718    }
719
720    /**
721     * Gets a property of a component.
722     * 
723     * @see IComponent
724     * @since 3.0
725     * @deprecated
726     */
727    public Object getProperty(String propertyName)
728    {
729        return PropertyUtils.read(this, propertyName);
730    }
731
732    /**
733     * @since 4.0
734     */
735
736    public final boolean isRendering()
737    {
738        return _rendering;
739    }
740
741    /**
742     * Returns true if the component has been transitioned into its active state by invoking
743     * {@link #enterActiveState()}
744     * 
745     * @since 4.0
746     */
747
748    protected final boolean isInActiveState()
749    {
750        return _active;
751    }
752
753    /** @since 4.0 */
754    public final void enterActiveState()
755    {
756        _active = true;
757    }
758
759    /** @since 4.0 */
760
761    protected final void checkActiveLock()
762    {
763        if (_active)
764            throw new UnsupportedOperationException(TapestryMessages.componentIsLocked(this));
765    }
766
767    public Messages getMessages()
768    {
769        throw new IllegalStateException(TapestryMessages.providedByEnhancement("getMessages"));
770    }
771
772    public IComponentSpecification getSpecification()
773    {
774        throw new IllegalStateException(TapestryMessages.providedByEnhancement("getSpecification"));
775    }
776
777    /**
778     * Returns a localized message.
779     * 
780     * @since 3.0
781     * @deprecated To be removed in 4.1. Use {@link #getMessages()} instead.
782     */
783
784    public String getMessage(String key)
785    {
786        return getMessages().getMessage(key);
787    }
788
789    /**
790     * Formats a localized message string, using
791     * {@link Messages#format(java.lang.String, java.lang.Object[])}.
792     * 
793     * @param key
794     *            the key used to obtain a localized pattern
795     * @param arguments
796     *            passed to the formatter
797     * @since 3.0
798     * @deprecated To be removed in 4.1. Use {@link #getMessages()} instead.
799     */
800
801    public String format(String key, Object[] arguments)
802    {
803        return getMessages().format(key, arguments);
804    }
805
806    /**
807     * Convienience method for invoking {@link IMessages#format(String, Locale, Object)}
808     * 
809     * @since 3.0
810     * @deprecated To be removed in 4.1. Use {@link #getMessages()} instead.
811     */
812
813    public String format(String key, Object argument)
814    {
815        return getMessages().format(key, argument);
816    }
817
818    /**
819     * Convienience method for invoking {@link Messages#format(String, Object, Object)}.
820     * 
821     * @since 3.0
822     * @deprecated To be removed in 4.1. Use {@link #getMessages()} instead.
823     */
824
825    public String format(String key, Object argument1, Object argument2)
826    {
827        return getMessages().format(key, argument1, argument2);
828    }
829
830    /**
831     * Convienience method for {@link Messages#format(String, Object, Object, Object)}.
832     * 
833     * @since 3.0
834     * @deprecated To be removed in 4.1. Use {@link #getMessages()} instead.
835     */
836
837    public String format(String key, Object argument1, Object argument2, Object argument3)
838    {
839        return getMessages().format(key, argument1, argument2, argument3);
840    }
841
842    /** @since 4.0 */
843    public final IContainedComponent getContainedComponent()
844    {
845        return _containedComponent;
846    }
847
848    /** @since 4.0 */
849    public final void setContainedComponent(IContainedComponent containedComponent)
850    {
851        Defense.notNull(containedComponent, "containedComponent");
852
853        if (_containedComponent != null)
854            throw new ApplicationRuntimeException(TapestryMessages
855                    .attemptToChangeContainedComponent(this));
856
857        _containedComponent = containedComponent;
858    }
859
860}