001    // Copyright 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.describe;
016    
017    import java.util.Collection;
018    import java.util.Iterator;
019    
020    import org.apache.hivemind.util.Defense;
021    import org.apache.tapestry.IMarkupWriter;
022    
023    /**
024     * Implementation of {@link org.apache.tapestry.describe.DescriptionReceiver} that produces HTML
025     * output using a {@link org.apache.tapestry.IMarkupWriter}.
026     * <p>
027     * TODO: Make {@link #describeAlternate(Object)} exclusive with the other methods
028     * {@link #title(String)}, {@link #property(String, Object)}, etc.
029     * 
030     * @author Howard M. Lewis Ship
031     * @since 4.0
032     */
033    public class HTMLDescriptionReceiver implements RootDescriptionReciever
034    {
035        // Emitted for null values.
036    
037        static final String NULL_VALUE = "<NULL>";
038    
039        private final IMarkupWriter _writer;
040    
041        private boolean _emitDefault = true;
042    
043        private String _title;
044    
045        private String _section;
046    
047        private DescribableStrategy _strategy;
048    
049        private HTMLDescriptionReceiverStyles _styles;
050    
051        private boolean _even = true;
052    
053        public HTMLDescriptionReceiver(IMarkupWriter writer, DescribableStrategy adapter)
054        {
055            this(writer, adapter, new HTMLDescriptionReceiverStyles());
056        }
057    
058        public HTMLDescriptionReceiver(IMarkupWriter writer, DescribableStrategy strategy,
059                HTMLDescriptionReceiverStyles styles)
060        {
061            Defense.notNull(writer, "writer");
062            Defense.notNull(strategy, "strategy");
063            Defense.notNull(styles, "styles");
064    
065            _writer = writer;
066            _strategy = strategy;
067            _styles = styles;
068        }
069    
070        /*
071         * (non-Javadoc)
072         * 
073         * @see org.apache.tapestry.describe.RootDescriptionReciever#describe(java.lang.Object)
074         */
075        public void describe(Object object)
076        {
077            if (object == null)
078            {
079                _writer.print(NULL_VALUE);
080                return;
081            }
082    
083            _strategy.describeObject(object, this);
084    
085            finishUp(object);
086        }
087    
088        public void describeAlternate(Object alternate)
089        {
090            _strategy.describeObject(alternate, this);
091        }
092    
093        public void finishUp()
094        {
095            // When false, a <table> was started, which must be closed.
096    
097            if (!_emitDefault)
098                _writer.end("table");
099    
100            _writer.println();
101    
102            _emitDefault = true;
103            _title = null;
104            _section = null;
105            _even = true;
106        }
107    
108        void finishUp(Object object)
109        {
110            if (_emitDefault)
111            {
112                String value = _title != null ? _title : object.toString();
113    
114                _writer.print(value);
115            }
116    
117            finishUp();
118        }
119    
120        public void title(String title)
121        {
122            Defense.notNull(title, "title");
123    
124            if (_title != null)
125                throw new IllegalStateException(DescribeMessages.setTitleOnce());
126    
127            _title = title;
128        }
129    
130        public void section(String section)
131        {
132            Defense.notNull(section, "section");
133    
134            if (_title == null)
135                throw new IllegalStateException(DescribeMessages.mustSetTitleBeforeSection());
136    
137            _section = section;
138        }
139    
140        private void assertTitleSet()
141        {
142            if (_title == null)
143                throw new IllegalStateException(DescribeMessages.mustSetTitleBeforeProperty());
144        }
145    
146        /**
147         * Invoked to ensure that the section portion has been output, before any properties within the
148         * section are output.
149         */
150    
151        private void emitSection()
152        {
153            if (_emitDefault)
154            {
155                _emitDefault = false;
156    
157                _writer.begin("div");
158                _writer.attribute("class", _styles.getHeaderClass());
159                _writer.print(_title);
160                _writer.end();
161                _writer.println();
162    
163                _writer.begin("table");
164                _writer.attribute("class", _styles.getTableClass());
165                _writer.println();
166    
167                _even = true;
168            }
169    
170            if (_section != null)
171            {
172                _writer.begin("tr");
173                _writer.attribute("class", _styles.getSubheaderClass());
174                _writer.begin("th");
175                _writer.attribute("colspan", 2);
176                _writer.print(_section);
177                _writer.end("tr");
178                _writer.println();
179    
180                _section = null;
181    
182                _even = true;
183            }
184    
185        }
186    
187        private void pair(String key, String value)
188        {
189            assertTitleSet();
190            emitSection();
191    
192            _writer.begin("tr");
193            writeRowClass();
194    
195            _writer.begin("th");
196            _writer.print(key);
197            _writer.end();
198            _writer.begin("td");
199            _writer.print(value);
200            _writer.end("tr");
201            _writer.println();
202    
203        }
204    
205        private void writeRowClass()
206        {
207            _writer.attribute("class", _even ? "even" : "odd");
208            _even = !_even;
209        }
210    
211        public void property(String key, Object value)
212        {
213            Defense.notNull(key, "key");
214    
215            assertTitleSet();
216            emitSection();
217    
218            _writer.begin("tr");
219            writeRowClass();
220    
221            _writer.begin("th");
222            _writer.print(key);
223            _writer.end();
224            _writer.begin("td");
225    
226            describeNested(value);
227    
228            _writer.end("tr");
229            _writer.println();
230        }
231    
232        private void describeNested(Object value)
233        {
234            if (value == null)
235            {
236                _writer.print(NULL_VALUE);
237                return;
238            }
239    
240            new HTMLDescriptionReceiver(_writer, _strategy, _styles).describe(value);
241        }
242    
243        public void property(String key, boolean value)
244        {
245            Defense.notNull(key, "key");
246    
247            // toString is JDK 1.4 and above, so we'll provide our own.
248    
249            pair(key, value ? "true" : "false");
250        }
251    
252        public void property(String key, byte value)
253        {
254            Defense.notNull(key, "key");
255    
256            pair(key, Byte.toString(value));
257        }
258    
259        public void property(String key, short value)
260        {
261            Defense.notNull(key, "key");
262    
263            pair(key, Short.toString(value));
264        }
265    
266        public void property(String key, int value)
267        {
268            Defense.notNull(key, "key");
269    
270            pair(key, Integer.toString(value));
271        }
272    
273        public void property(String key, long value)
274        {
275            Defense.notNull(key, "key");
276    
277            pair(key, Long.toString(value));
278        }
279    
280        public void property(String key, float value)
281        {
282            Defense.notNull(key, "key");
283    
284            pair(key, Float.toString(value));
285        }
286    
287        public void property(String key, double value)
288        {
289            Defense.notNull(key, "key");
290    
291            pair(key, Double.toString(value));
292        }
293    
294        public void property(String key, char value)
295        {
296            Defense.notNull(key, "key");
297    
298            pair(key, Character.toString(value));
299        }
300    
301        public void array(String key, Object[] values)
302        {
303            Defense.notNull(key, "key");
304    
305            assertTitleSet();
306    
307            if (values == null || values.length == 0)
308                return;
309    
310            emitSection();
311    
312            for (int i = 0; i < values.length; i++)
313            {
314                _writer.begin("tr");
315                writeRowClass();
316    
317                _writer.begin("th");
318    
319                if (i == 0)
320                    _writer.print(key);
321    
322                _writer.end();
323    
324                _writer.begin("td");
325    
326                describeNested(values[i]);
327    
328                _writer.end("tr");
329                _writer.println();
330            }
331    
332        }
333    
334        public void collection(String key, Collection values)
335        {
336            Defense.notNull(key, "key");
337    
338            assertTitleSet();
339    
340            if (values == null || values.isEmpty())
341                return;
342    
343            emitSection();
344    
345            Iterator i = values.iterator();
346            boolean first = true;
347    
348            while (i.hasNext())
349            {
350                _writer.begin("tr");
351                writeRowClass();
352    
353                _writer.begin("th");
354    
355                if (first)
356                    _writer.print(key);
357    
358                _writer.end();
359                _writer.begin("td");
360    
361                describeNested(i.next());
362    
363                _writer.end("tr");
364                _writer.println();
365    
366                first = false;
367            }
368        }
369    }