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