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