KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tapestry > AbstractComponent


1 // Copyright 2004, 2005 The Apache Software Foundation
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15 package org.apache.tapestry;
16
17 import java.util.Collection JavaDoc;
18 import java.util.Collections JavaDoc;
19 import java.util.HashMap JavaDoc;
20 import java.util.HashSet JavaDoc;
21 import java.util.Iterator JavaDoc;
22 import java.util.List JavaDoc;
23 import java.util.Map JavaDoc;
24
25 import org.apache.hivemind.ApplicationRuntimeException;
26 import org.apache.hivemind.Messages;
27 import org.apache.hivemind.impl.BaseLocatable;
28 import org.apache.hivemind.util.Defense;
29 import org.apache.hivemind.util.PropertyUtils;
30 import org.apache.tapestry.bean.BeanProvider;
31 import org.apache.tapestry.engine.IPageLoader;
32 import org.apache.tapestry.event.PageEvent;
33 import org.apache.tapestry.listener.ListenerMap;
34 import org.apache.tapestry.spec.IComponentSpecification;
35
36 /**
37  * Abstract base class implementing the {@link IComponent}interface.
38  *
39  * @author Howard Lewis Ship
40  */

41
42 public abstract class AbstractComponent extends BaseLocatable implements IComponent
43 {
44     /**
45      * The page that contains the component, possibly itself (if the component is in fact, a page).
46      */

47
48     private IPage _page;
49
50     /**
51      * The component which contains the component. This will only be null if the component is
52      * actually a page.
53      */

54
55     private IComponent _container;
56
57     /**
58      * The simple id of this component.
59      */

60
61     private String JavaDoc _id;
62
63     /**
64      * The fully qualified id of this component. This is calculated the first time it is needed,
65      * then cached for later.
66      */

67
68     private String JavaDoc _idPath;
69
70     private static final int MAP_SIZE = 5;
71
72     /**
73      * A {@link Map}of all bindings (for which there isn't a corresponding JavaBeans property); the
74      * keys are the names of formal and informal parameters.
75      */

76
77     private Map JavaDoc _bindings;
78
79     private Map JavaDoc _components;
80
81     private static final int BODY_INIT_SIZE = 5;
82
83     private INamespace _namespace;
84
85     /**
86      * Used in place of JDK 1.3's Collections.EMPTY_MAP (which is not available in JDK 1.2).
87      */

88
89     private static final Map JavaDoc EMPTY_MAP = Collections.unmodifiableMap(new HashMap JavaDoc(1));
90
91     /**
92      * The number of {@link IRender}objects in the body of this component.
93      */

94
95     private int _bodyCount = 0;
96
97     /**
98      * An aray of elements in the body of this component.
99      */

100
101     private IRender[] _body;
102
103     /**
104      * The components' asset map.
105      */

106
107     private Map JavaDoc _assets;
108
109     /**
110      * A mapping that allows public instance methods to be dressed up as {@link IActionListener}
111      * listener objects.
112      *
113      * @since 1.0.2
114      */

115
116     private ListenerMap _listeners;
117
118     /**
119      * A bean provider; these are lazily created as needed.
120      *
121      * @since 1.0.4
122      */

123
124     private IBeanProvider _beans;
125
126     /**
127      * Returns true if the component is currently rendering.
128      *
129      * @see #prepareForRender(IRequestCycle)
130      * @see #cleanupAfterRender(IRequestCycle)
131      * @since 4.0
132      */

133
134     private boolean _rendering;
135
136     /**
137      * @since 4.0
138      */

139
140     private boolean _active;
141
142     public void addAsset(String JavaDoc name, IAsset asset)
143     {
144         Defense.notNull(name, "name");
145         Defense.notNull(asset, "asset");
146
147         checkActiveLock();
148
149         if (_assets == null)
150             _assets = new HashMap JavaDoc(MAP_SIZE);
151
152         _assets.put(name, asset);
153     }
154
155     public void addComponent(IComponent component)
156     {
157         Defense.notNull(component, "component");
158
159         checkActiveLock();
160
161         if (_components == null)
162             _components = new HashMap JavaDoc(MAP_SIZE);
163
164         _components.put(component.getId(), component);
165     }
166
167     /**
168      * Adds an element (which may be static text or a component) as a body element of this
169      * component. Such elements are rendered by {@link #renderBody(IMarkupWriter, IRequestCycle)}.
170      *
171      * @since 2.2
172      */

173
174     public void addBody(IRender element)
175     {
176         Defense.notNull(element, "element");
177
178         // TODO: Tweak the ordering of operations inside the PageLoader so that this
179
// check is allowable. Currently, the component is entering active state
180
// before it loads its template.
181

182         // checkActiveLock();
183

184         // Should check the specification to see if this component
185
// allows body. Curently, this is checked by the component
186
// in render(), which is silly.
187

188         if (_body == null)
189         {
190             _body = new IRender[BODY_INIT_SIZE];
191             _body[0] = element;
192
193             _bodyCount = 1;
194             return;
195         }
196
197         // No more room? Make the array bigger.
198

199         if (_bodyCount == _body.length)
200         {
201             IRender[] newWrapped;
202
203             newWrapped = new IRender[_body.length * 2];
204
205             System.arraycopy(_body, 0, newWrapped, 0, _bodyCount);
206
207             _body = newWrapped;
208         }
209
210         _body[_bodyCount++] = element;
211     }
212
213     /**
214      * Invokes {@link #finishLoad()}. Subclasses may overide as needed, but must invoke this
215      * implementation. {@link BaseComponent}loads its HTML template.
216      */

217
218     public void finishLoad(IRequestCycle cycle, IPageLoader loader,
219             IComponentSpecification specification)
220     {
221         finishLoad();
222     }
223
224     /**
225      * Converts informal parameters into additional attributes on the curently open tag.
226      * <p>
227      * Invoked from subclasses to allow additional attributes to be specified within a tag (this
228      * works best when there is a one-to-one corespondence between an {@link IComponent}and a HTML
229      * element.
230      * <p>
231      * Iterates through the bindings for this component. Filters out bindings for formal parameters.
232      * <p>
233      * For each acceptible key, the value is extracted using {@link IBinding#getObject()}. If the
234      * value is null, no attribute is written.
235      * <p>
236      * If the value is an instance of {@link IAsset}, then {@link IAsset#buildURL(IRequestCycle)}
237      * is invoked to convert the asset to a URL.
238      * <p>
239      * Finally, {@link IMarkupWriter#attribute(String,String)}is invoked with the value (or the
240      * URL).
241      * <p>
242      * The most common use for informal parameters is to support the HTML class attribute (for use
243      * with cascading style sheets) and to specify JavaScript event handlers.
244      * <p>
245      * Components are only required to generate attributes on the result phase; this can be skipped
246      * during the rewind phase.
247      */

248
249     protected void renderInformalParameters(IMarkupWriter writer, IRequestCycle cycle)
250     {
251         String JavaDoc attribute;
252
253         if (_bindings == null)
254             return;
255
256         Iterator JavaDoc i = _bindings.entrySet().iterator();
257
258         while (i.hasNext())
259         {
260             Map.Entry JavaDoc entry = (Map.Entry JavaDoc) i.next();
261             String JavaDoc name = (String JavaDoc) entry.getKey();
262
263             if (isFormalParameter(name))
264                 continue;
265
266             IBinding binding = (IBinding) entry.getValue();
267
268             Object JavaDoc value = binding.getObject();
269             if (value == null)
270                 continue;
271
272             if (value instanceof IAsset)
273             {
274                 IAsset asset = (IAsset) value;
275
276                 // Get the URL of the asset and insert that.
277

278                 attribute = asset.buildURL(cycle);
279             }
280             else
281                 attribute = value.toString();
282
283             writer.attribute(name, attribute);
284         }
285
286     }
287
288     /** @since 4.0 */
289     private boolean isFormalParameter(String JavaDoc name)
290     {
291         Defense.notNull(name, "name");
292
293         return getSpecification().getParameter(name) != null;
294     }
295
296     /**
297      * Returns the named binding, or null if it doesn't exist.
298      * <p>
299      * In Tapestry 3.0, it was possible to force a binding to be stored in a component property by
300      * defining a concrete or abstract property named "nameBinding" of type {@link IBinding}. This
301      * has been removed in release 4.0 and bindings are always stored inside a Map of the component.
302      *
303      * @see #setBinding(String,IBinding)
304      */

305
306     public IBinding getBinding(String JavaDoc name)
307     {
308         Defense.notNull(name, "name");
309
310         if (_bindings == null)
311             return null;
312
313         return (IBinding) _bindings.get(name);
314     }
315
316     /**
317      * Returns true if the specified parameter is bound.
318      *
319      * @since 4.0
320      */

321
322     public boolean isParameterBound(String JavaDoc parameterName)
323     {
324         Defense.notNull(parameterName, "parameterName");
325
326         return _bindings != null && _bindings.containsKey(parameterName);
327     }
328
329     public IComponent getComponent(String JavaDoc id)
330     {
331         Defense.notNull(id, "id");
332
333         IComponent result = null;
334
335         if (_components != null)
336             result = (IComponent) _components.get(id);
337
338         if (result == null)
339             throw new ApplicationRuntimeException(Tapestry.format("no-such-component", this, id),
340                     this, null, null);
341
342         return result;
343     }
344
345     public IComponent getContainer()
346     {
347         return _container;
348     }
349
350     public void setContainer(IComponent value)
351     {
352         checkActiveLock();
353
354         if (_container != null)
355             throw new ApplicationRuntimeException(Tapestry
356                     .getMessage("AbstractComponent.attempt-to-change-container"));
357
358         _container = value;
359     }
360
361     /**
362      * Returns the name of the page, a slash, and this component's id path. Pages are different,
363      * they override this method to simply return their page name.
364      *
365      * @see #getIdPath()
366      */

367
368     public String JavaDoc getExtendedId()
369     {
370         if (_page == null)
371             return null;
372
373         return _page.getPageName() + "/" + getIdPath();
374     }
375
376     public String JavaDoc getId()
377     {
378         return _id;
379     }
380
381     public void setId(String JavaDoc value)
382     {
383         if (_id != null)
384             throw new ApplicationRuntimeException(Tapestry
385                     .getMessage("AbstractComponent.attempt-to-change-component-id"));
386
387         _id = value;
388     }
389
390     public String JavaDoc getIdPath()
391     {
392         String JavaDoc containerIdPath;
393
394         if (_container == null)
395             throw new NullPointerException JavaDoc(Tapestry
396                     .format("AbstractComponent.null-container", this));
397
398         containerIdPath = _container.getIdPath();
399
400         if (containerIdPath == null)
401             _idPath = _id;
402         else
403             _idPath = containerIdPath + "." + _id;
404
405         return _idPath;
406     }
407
408     public IPage getPage()
409     {
410         return _page;
411     }
412
413     public void setPage(IPage value)
414     {
415         if (_page != null)
416             throw new ApplicationRuntimeException(Tapestry
417                     .getMessage("AbstractComponent.attempt-to-change-page"));
418
419         _page = value;
420     }
421
422     /**
423      * Renders all elements wrapped by the receiver.
424      */

425
426     public void renderBody(IMarkupWriter writer, IRequestCycle cycle)
427     {
428         for (int i = 0; i < _bodyCount; i++)
429             _body[i].render(writer, cycle);
430     }
431
432     /**
433      * Adds the binding with the given name, replacing any existing binding with that name.
434      * <p>
435      *
436      * @see #getBinding(String)
437      */

438
439     public void setBinding(String JavaDoc name, IBinding binding)
440     {
441         Defense.notNull(name, "name");
442         Defense.notNull(binding, "binding");
443
444         if (_bindings == null)
445             _bindings = new HashMap JavaDoc(MAP_SIZE);
446
447         _bindings.put(name, binding);
448     }
449
450     public String JavaDoc toString()
451     {
452         StringBuffer JavaDoc buffer;
453
454         buffer = new StringBuffer JavaDoc(super.toString());
455
456         buffer.append('[');
457
458         buffer.append(getExtendedId());
459
460         buffer.append(']');
461
462         return buffer.toString();
463     }
464
465     /**
466      * Returns an unmodifiable {@link Map}of components, keyed on component id. Never returns null,
467      * but may return an empty map. The returned map is immutable.
468      */

469
470     public Map JavaDoc getComponents()
471     {
472         if (_components == null)
473             return EMPTY_MAP;
474
475         return Collections.unmodifiableMap(_components);
476
477     }
478
479     public Map JavaDoc getAssets()
480     {
481         if (_assets == null)
482             return EMPTY_MAP;
483
484         return Collections.unmodifiableMap(_assets);
485     }
486
487     public IAsset getAsset(String JavaDoc name)
488     {
489         if (_assets == null)
490             return null;
491
492         return (IAsset) _assets.get(name);
493     }
494
495     public Collection JavaDoc getBindingNames()
496     {
497         // If no conainer, i.e. a page, then no bindings.
498

499         if (_container == null)
500             return null;
501
502         HashSet JavaDoc result = new HashSet JavaDoc();
503
504         // All the informal bindings go into the bindings Map.
505

506         if (_bindings != null)
507             result.addAll(_bindings.keySet());
508
509         // Now, iterate over the formal parameters and add the formal parameters
510
// that have a binding.
511

512         List JavaDoc names = getSpecification().getParameterNames();
513
514         int count = names.size();
515
516         for (int i = 0; i < count; i++)
517         {
518             String JavaDoc name = (String JavaDoc) names.get(i);
519
520             if (result.contains(name))
521                 continue;
522
523             if (getBinding(name) != null)
524                 result.add(name);
525         }
526
527         return result;
528     }
529
530     /**
531      * Returns an unmodifiable {@link Map}of all bindings for this component.
532      *
533      * @since 1.0.5
534      */

535
536     public Map JavaDoc getBindings()
537     {
538         if (_bindings == null)
539             return Collections.EMPTY_MAP;
540
541         return Collections.unmodifiableMap(_bindings);
542     }
543
544     /**
545      * Returns a {@link ListenerMap}&nbsp;for the component. A ListenerMap contains a number of
546      * synthetic read-only properties that implement the {@link IActionListener}interface, but in
547      * fact, cause public instance methods to be invoked.
548      *
549      * @since 1.0.2
550      */

551
552     public ListenerMap getListeners()
553     {
554         // This is what's called a violation of the Law of Demeter!
555
// This should probably be converted over to some kind of injection, as with
556
// getMessages(), etc.
557

558         if (_listeners == null)
559             _listeners = getPage().getEngine().getInfrastructure().getListenerMapSource()
560                     .getListenerMapForObject(this);
561
562         return _listeners;
563     }
564
565     /**
566      * Returns the {@link IBeanProvider}for this component. This is lazily created the first time
567      * it is needed.
568      *
569      * @since 1.0.4
570      */

571
572     public IBeanProvider getBeans()
573     {
574         if (_beans == null)
575             _beans = new BeanProvider(this);
576
577         return _beans;
578     }
579
580     /**
581      * Invoked, as a convienience, from
582      * {@link #finishLoad(IRequestCycle, IPageLoader, IComponentSpecification)}. This implemenation
583      * does nothing. Subclasses may override without invoking this implementation.
584      *
585      * @since 1.0.5
586      */

587
588     protected void finishLoad()
589     {
590     }
591
592     /**
593      * The main method used to render the component. Invokes
594      * {@link #prepareForRender(IRequestCycle)}, then
595      * {@link #renderComponent(IMarkupWriter, IRequestCycle)}.
596      * {@link #cleanupAfterRender(IRequestCycle)}is invoked in a <code>finally</code> block.
597      * <p>
598      * Subclasses should not override this method; instead they will implement
599      * {@link #renderComponent(IMarkupWriter, IRequestCycle)}.
600      *
601      * @since 2.0.3
602      */

603
604     public final void render(IMarkupWriter writer, IRequestCycle cycle)
605     {
606         try
607         {
608             _rendering = true;
609
610             prepareForRender(cycle);
611
612             renderComponent(writer, cycle);
613         }
614         finally
615         {
616             _rendering = false;
617
618             cleanupAfterRender(cycle);
619         }
620     }
621
622     /**
623      * Invoked by {@link #render(IMarkupWriter, IRequestCycle)}to prepare the component to render.
624      * This implementation sets JavaBeans properties from matching bound parameters. This
625      * implementation does nothing.
626      *
627      * @since 2.0.3
628      */

629
630     protected void prepareForRender(IRequestCycle cycle)
631     {
632     }
633
634     /**
635      * Invoked by {@link #render(IMarkupWriter, IRequestCycle)}to actually render the component
636      * (with any parameter values already set). This is the method that subclasses must implement.
637      *
638      * @since 2.0.3
639      */

640
641     protected abstract void renderComponent(IMarkupWriter writer, IRequestCycle cycle);
642
643     /**
644      * Invoked by {@link #render(IMarkupWriter, IRequestCycle)}after the component renders. This
645      * implementation does nothing.
646      *
647      * @since 2.0.3
648      */

649
650     protected void cleanupAfterRender(IRequestCycle cycle)
651     {
652     }
653
654     public INamespace getNamespace()
655     {
656         return _namespace;
657     }
658
659     public void setNamespace(INamespace namespace)
660     {
661         _namespace = namespace;
662     }
663
664     /**
665      * Returns the body of the component, the element (which may be static HTML or components) that
666      * the component immediately wraps. May return null. Do not modify the returned array. The array
667      * may be padded with nulls.
668      *
669      * @since 2.3
670      * @see #getBodyCount()
671      */

672
673     public IRender[] getBody()
674     {
675         return _body;
676     }
677
678     /**
679      * Returns the active number of elements in the the body, which may be zero.
680      *
681      * @since 2.3
682      * @see #getBody()
683      */

684
685     public int getBodyCount()
686     {
687         return _bodyCount;
688     }
689
690     /**
691      * Empty implementation of
692      * {@link org.apache.tapestry.event.PageRenderListener#pageEndRender(PageEvent)}. This allows
693      * classes to implement {@link org.apache.tapestry.event.PageRenderListener}and only implement
694      * the {@link org.apache.tapestry.event.PageRenderListener#pageBeginRender(PageEvent)}method.
695      *
696      * @since 3.0
697      */

698
699     public void pageEndRender(PageEvent event)
700     {
701     }
702
703     /**
704      * Sets a property of a component.
705      *
706      * @see IComponent
707      * @since 3.0
708      */

709     public void setProperty(String JavaDoc propertyName, Object JavaDoc value)
710     {
711         PropertyUtils.write(this, propertyName, value);
712     }
713
714     /**
715      * Gets a property of a component.
716      *
717      * @see IComponent
718      * @since 3.0
719      */

720     public Object JavaDoc getProperty(String JavaDoc propertyName)
721     {
722         return PropertyUtils.read(this, propertyName);
723     }
724
725     /**
726      * @since 4.0
727      */

728
729     public boolean isRendering()
730     {
731         return _rendering;
732     }
733
734     /**
735      * Returns true if the component has been transitioned into its active state by invoking
736      * {@link #enterActiveState()}
737      *
738      * @since 4.0
739      */

740
741     protected boolean isInActiveState()
742     {
743         return _active;
744     }
745
746     /** @since 4.0 */
747     public void enterActiveState()
748     {
749         _active = true;
750     }
751
752     /** @since 4.0 */
753
754     protected void checkActiveLock()
755     {
756         if (_active)
757             throw new UnsupportedOperationException JavaDoc(TapestryMessages.componentIsLocked(this));
758     }
759
760     public Messages getMessages()
761     {
762         throw new IllegalStateException JavaDoc(TapestryMessages.providedByEnhancement("getMessages"));
763     }
764
765     public IComponentSpecification getSpecification()
766     {
767         throw new IllegalStateException JavaDoc(TapestryMessages.providedByEnhancement("getSpecification"));
768     }
769
770     /**
771      * Returns a localized message.
772      *
773      * @since 3.0
774      * @deprecated To be removed in 4.1. Use {@link #getMessages()} instead.
775      */

776
777     public String JavaDoc getMessage(String JavaDoc key)
778     {
779         return getMessages().getMessage(key);
780     }
781
782     /**
783      * Formats a localized message string, using
784      * {@link Messages#format(java.lang.String, java.lang.Object[])}.
785      *
786      * @param key
787      * the key used to obtain a localized pattern
788      * @param arguments
789      * passed to the formatter
790      * @since 3.0
791      * @deprecated To be removed in 4.1. Use {@link #getMessages()} instead.
792      */

793
794     public String JavaDoc format(String JavaDoc key, Object JavaDoc[] arguments)
795     {
796         return getMessages().format(key, arguments);
797     }
798
799     /**
800      * Convienience method for invoking {@link IMessages#format(String, Object)}
801      *
802      * @since 3.0
803      * @deprecated To be removed in 4.1. Use {@link #getMessages()} instead.
804      */

805
806     public String JavaDoc format(String JavaDoc key, Object JavaDoc argument)
807     {
808         return getMessages().format(key, argument);
809     }
810
811     /**
812      * Convienience method for invoking {@link Messages#format(String, Object, Object)}.
813      *
814      * @since 3.0
815      * @deprecated To be removed in 4.1. Use {@link #getMessages()} instead.
816      */

817
818     public String JavaDoc format(String JavaDoc key, Object JavaDoc argument1, Object JavaDoc argument2)
819     {
820         return getMessages().format(key, argument1, argument2);
821     }
822
823     /**
824      * Convienience method for {@link Messages#format(String, Object, Object, Object)}.
825      *
826      * @since 3.0
827      * @deprecated To be removed in 4.1. Use {@link #getMessages()} instead.
828      */

829
830     public String JavaDoc format(String JavaDoc key, Object JavaDoc argument1, Object JavaDoc argument2, Object JavaDoc argument3)
831     {
832         return getMessages().format(key, argument1, argument2, argument3);
833     }
834
835 }
Popular Tags