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.PageEvent;
033    import org.apache.tapestry.listener.ListenerMap;
034    import org.apache.tapestry.spec.IComponentSpecification;
035    import org.apache.tapestry.spec.IContainedComponent;
036    
037    /**
038     * Abstract base class implementing the {@link IComponent}interface.
039     * 
040     * @author Howard Lewis Ship
041     */
042    
043    public 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    }