001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  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 distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.html;
014
015import java.util.*;
016import java.util.concurrent.*;
017
018import org.apache.juneau.*;
019import org.apache.juneau.annotation.*;
020import org.apache.juneau.collections.*;
021import org.apache.juneau.html.annotation.*;
022import org.apache.juneau.serializer.*;
023import org.apache.juneau.xml.*;
024
025/**
026 * Serializes POJO models to HTML.
027 *
028 * <h5 class='topic'>Media types</h5>
029 *
030 * Handles <c>Accept</c> types:  <bc>text/html</bc>
031 * <p>
032 * Produces <c>Content-Type</c> types:  <bc>text/html</bc>
033 *
034 * <h5 class='topic'>Description</h5>
035 *
036 * The conversion is as follows...
037 * <ul class='spaced-list'>
038 *    <li>
039 *       {@link Map Maps} (e.g. {@link HashMap}, {@link TreeMap}) and beans are converted to HTML tables with
040 *       'key' and 'value' columns.
041 *    <li>
042 *       {@link Collection Collections} (e.g. {@link HashSet}, {@link LinkedList}) and Java arrays are converted
043 *       to HTML ordered lists.
044 *    <li>
045 *       {@code Collections} of {@code Maps} and beans are converted to HTML tables with keys as headers.
046 *    <li>
047 *       Everything else is converted to text.
048 * </ul>
049 *
050 * <p>
051 * This serializer provides several serialization options.  Typically, one of the predefined <jsf>DEFAULT</jsf>
052 * serializers will be sufficient.
053 * However, custom serializers can be constructed to fine-tune behavior.
054 *
055 * <p>
056 * The {@link HtmlLink} annotation can be used on beans to add hyperlinks to the output.
057 *
058 * <h5 class='topic'>Behavior-specific subclasses</h5>
059 *
060 * The following direct subclasses are provided for convenience:
061 * <ul class='spaced-list'>
062 *    <li>
063 *       {@link Sq} - Default serializer, single quotes.
064 *    <li>
065 *       {@link SqReadable} - Default serializer, single quotes, whitespace added.
066 * </ul>
067 *
068 * <h5 class='section'>Example:</h5>
069 * <p class='bcode w800'>
070 *    <jc>// Use one of the default serializers to serialize a POJO</jc>
071 *    String html = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(someObject);
072 *
073 *    <jc>// Create a custom serializer that doesn't use whitespace and newlines</jc>
074 *    HtmlSerializer serializer = <jk>new</jk> HtmlSerializerBuider().ws().build();
075 *
076 *    <jc>// Same as above, except uses cloning</jc>
077 *    HtmlSerializer serializer = HtmlSerializer.<jsf>DEFAULT</jsf>.builder().ws().build();
078 *
079 *    <jc>// Serialize POJOs to HTML</jc>
080 *
081 *    <jc>// Produces: </jc>
082 *    <jc>// &lt;ul&gt;&lt;li&gt;1&lt;li&gt;2&lt;li&gt;3&lt;/ul&gt;</jc>
083 *    List l = OList.<jsm>of</jsm>(1, 2, 3);
084 *    String html = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(l);
085 *
086 *    <jc>// Produces: </jc>
087 *    <jc>//    &lt;table&gt; </jc>
088 *    <jc>//       &lt;tr&gt;&lt;th&gt;firstName&lt;/th&gt;&lt;th&gt;lastName&lt;/th&gt;&lt;/tr&gt; </jc>
089 *    <jc>//       &lt;tr&gt;&lt;td&gt;Bob&lt;/td&gt;&lt;td&gt;Costas&lt;/td&gt;&lt;/tr&gt; </jc>
090 *    <jc>//       &lt;tr&gt;&lt;td&gt;Billy&lt;/td&gt;&lt;td&gt;TheKid&lt;/td&gt;&lt;/tr&gt; </jc>
091 *    <jc>//       &lt;tr&gt;&lt;td&gt;Barney&lt;/td&gt;&lt;td&gt;Miller&lt;/td&gt;&lt;/tr&gt; </jc>
092 *    <jc>//    &lt;/table&gt; </jc>
093 *    l = OList.<jsm>of</jsm>(
094 *       OMap.<jsm>ofJson</jsm>(<js>"{firstName:'Bob',lastName:'Costas'}"</js>),
095 *       OMap.<jsm>ofJson</jsm>(<js>"{firstName:'Billy',lastName:'TheKid'}"</js>),
096 *       OMap.<jsm>ofJson</jsm>(<js>"{firstName:'Barney',lastName:'Miller'}"</js>)
097 *    );
098 *    String html = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(l);
099 *
100 *    <jc>// Produces: </jc>
101 *    <jc>//    &lt;table&gt; </jc>
102 *    <jc>//       &lt;tr&gt;&lt;th&gt;key&lt;/th&gt;&lt;th&gt;value&lt;/th&gt;&lt;/tr&gt; </jc>
103 *    <jc>//       &lt;tr&gt;&lt;td&gt;foo&lt;/td&gt;&lt;td&gt;bar&lt;/td&gt;&lt;/tr&gt; </jc>
104 *    <jc>//       &lt;tr&gt;&lt;td&gt;baz&lt;/td&gt;&lt;td&gt;123&lt;/td&gt;&lt;/tr&gt; </jc>
105 *    <jc>//    &lt;/table&gt; </jc>
106 *    Map m = OMap.<jsm>ofJson</jsm>(<js>"{foo:'bar',baz:123}"</js>);
107 *    String html = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(m);
108 *
109 *    <jc>// HTML elements can be nested arbitrarily deep</jc>
110 *    <jc>// Produces: </jc>
111 *    <jc>//   &lt;table&gt; </jc>
112 *    <jc>//      &lt;tr&gt;&lt;th&gt;key&lt;/th&gt;&lt;th&gt;value&lt;/th&gt;&lt;/tr&gt; </jc>
113 *    <jc>//      &lt;tr&gt;&lt;td&gt;foo&lt;/td&gt;&lt;td&gt;bar&lt;/td&gt;&lt;/tr&gt; </jc>
114 *    <jc>//      &lt;tr&gt;&lt;td&gt;baz&lt;/td&gt;&lt;td&gt;123&lt;/td&gt;&lt;/tr&gt; </jc>
115 *    <jc>//      &lt;tr&gt;&lt;td&gt;someNumbers&lt;/td&gt;&lt;td&gt;&lt;ul&gt;&lt;li&gt;1&lt;li&gt;2&lt;li&gt;3&lt;/ul&gt;&lt;/td&gt;&lt;/tr&gt; </jc>
116 *    <jc>//      &lt;tr&gt;&lt;td&gt;someSubMap&lt;/td&gt;&lt;td&gt; </jc>
117 *    <jc>//         &lt;table&gt; </jc>
118 *    <jc>//            &lt;tr&gt;&lt;th&gt;key&lt;/th&gt;&lt;th&gt;value&lt;/th&gt;&lt;/tr&gt; </jc>
119 *    <jc>//            &lt;tr&gt;&lt;td&gt;a&lt;/td&gt;&lt;td&gt;b&lt;/td&gt;&lt;/tr&gt; </jc>
120 *    <jc>//         &lt;/table&gt; </jc>
121 *    <jc>//      &lt;/td&gt;&lt;/tr&gt; </jc>
122 *    <jc>//   &lt;/table&gt; </jc>
123 *    Map m = OMap.<jsm>ofJson</jsm>(<js>"{foo:'bar',baz:123}"</js>);
124 *    m.put(<js>"someNumbers"</js>, OList.<jsm>of</jsm>(1, 2, 3));
125 *    m.put(<js>"someSubMap"</js>, OMap.<jsm>ofJson</jsm>(<js>"{a:'b'}"</js>));
126 *    String html = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(m);
127 * </p>
128 */
129@ConfigurableContext
130public class HtmlSerializer extends XmlSerializer implements HtmlMetaProvider, HtmlCommon {
131
132   //-------------------------------------------------------------------------------------------------------------------
133   // Configurable properties
134   //-------------------------------------------------------------------------------------------------------------------
135
136   static final String PREFIX = "HtmlSerializer";
137
138   /**
139    * Configuration property:  Add <js>"_type"</js> properties when needed.
140    *
141    * <h5 class='section'>Property:</h5>
142    * <ul class='spaced-list'>
143    *    <li><b>ID:</b>  {@link org.apache.juneau.html.HtmlSerializer#HTML_addBeanTypes HTML_addBeanTypes}
144    *    <li><b>Name:</b>  <js>"HtmlSerializer.addBeanTypes.b"</js>
145    *    <li><b>Data type:</b>  <jk>boolean</jk>
146    *    <li><b>System property:</b>  <c>HtmlSerializer.addBeanTypes</c>
147    *    <li><b>Environment variable:</b>  <c>HTMLSERIALIZER_ADDBEANTYPES</c>
148    *    <li><b>Default:</b>  <jk>false</jk>
149    *    <li><b>Session property:</b>  <jk>false</jk>
150    *    <li><b>Annotations:</b>
151    *       <ul>
152    *          <li class='ja'>{@link org.apache.juneau.html.annotation.HtmlConfig#addBeanTypes()}
153    *       </ul>
154    *    <li><b>Methods:</b>
155    *       <ul>
156    *          <li class='jm'>{@link org.apache.juneau.html.HtmlSerializerBuilder#addBeanTypes()}
157    *       </ul>
158    * </ul>
159    *
160    * <h5 class='section'>Description:</h5>
161    * <p>
162    * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred
163    * through reflection.
164    *
165    * <p>
166    * When present, this value overrides the {@link #SERIALIZER_addBeanTypes} setting and is
167    * provided to customize the behavior of specific serializers in a {@link SerializerGroup}.
168    */
169   public static final String HTML_addBeanTypes = PREFIX + ".addBeanTypes.b";
170
171   /**
172    * Configuration property:  Add key/value headers on bean/map tables.
173    *
174    * <h5 class='section'>Property:</h5>
175    * <ul class='spaced-list'>
176    *    <li><b>ID:</b>  {@link org.apache.juneau.html.HtmlSerializer#HTML_addKeyValueTableHeaders HTML_addKeyValueTableHeaders}
177    *    <li><b>Name:</b>  <js>"HtmlSerializer.addKeyValueTableHeaders.b"</js>
178    *    <li><b>Data type:</b>  <jk>boolean</jk>
179    *    <li><b>System property:</b>  <c>HtmlSerializer.addKeyValueTableHeaders</c>
180    *    <li><b>Environment variable:</b>  <c>HTMLSERIALIZER_ADDKEYVALUETABLEHEADERS</c>
181    *    <li><b>Default:</b>  <jk>false</jk>
182    *    <li><b>Session property:</b>  <jk>false</jk>
183    *    <li><b>Annotations:</b>
184    *       <ul>
185    *          <li class='ja'>{@link org.apache.juneau.html.annotation.Html#noTableHeaders()}
186    *       </ul>
187    *    <li><b>Methods:</b>
188    *       <ul>
189    *          <li class='jm'>{@link org.apache.juneau.html.HtmlSerializerBuilder#addKeyValueTableHeaders()}
190    *       </ul>
191    * </ul>
192    *
193    * <p>
194    * When enabled, <bc>key</bc> and <bc>value</bc> column headers are added to tables.
195    *
196    * <h5 class='section'>Example:</h5>
197    * <p class='bcode w800'>
198    *    <jc>// Our bean class.</jc>
199    *    <jk>public class</jk> MyBean {
200    *       <jk>public</jk> String <jf>f1</jf> = <js>"foo"</js>;
201    *       <jk>public</jk> String <jf>f2</jf> = <js>"bar"</js>;
202    *    }
203    *
204    *  <jc>// Serializer without headers.</jc>
205    *    WriterSerializer s1 = HtmlSerializer.<jsf>DEFAULT</jsf>;
206    *
207    *  <jc>// Serializer with headers.</jc>
208    *    WriterSerializer s2 = HtmlSerializer
209    *       .<jsm>create</jsm>()
210    *       .addKeyValueTableHeaders()
211    *       .build();
212    *
213    *    String withoutHeaders = s1.serialize(<jk>new</jk> MyBean());
214    *    String withHeaders = s2.serialize(<jk>new</jk> MyBean());
215    * </p>
216    *
217    * <p>
218    * The following shows the difference between the two generated outputs:
219    *
220    * <table class='styled'>
221    *    <tr>
222    *       <th><c>withoutHeaders</c></th>
223    *       <th><c>withHeaders</c></th>
224    *    </tr>
225    *    <tr>
226    *       <td>
227    *          <table class='unstyled'>
228    *             <tr><td>f1</td><td>foo</td></tr>
229    *             <tr><td>f2</td><td>bar</td></tr>
230    *          </table>
231    *       </td>
232    *       <td>
233    *          <table class='unstyled'>
234    *             <tr><th>key</th><th>value</th></tr>
235    *             <tr><td>f1</td><td>foo</td></tr>
236    *             <tr><td>f2</td><td>bar</td></tr>
237    *          </table>
238    *       </td>
239    *    </tr>
240    * </table>
241    */
242   public static final String HTML_addKeyValueTableHeaders = PREFIX + ".addKeyValueTableHeaders.b";
243
244   /**
245    * Configuration property:  Look for URLs in {@link java.lang.String Strings}.
246    *
247    * <h5 class='section'>Property:</h5>
248    * <ul class='spaced-list'>
249    *    <li><b>ID:</b>  {@link org.apache.juneau.html.HtmlSerializer#HTML_detectLinksInStrings HTML_detectLinksInStrings}
250    *    <li><b>Name:</b>  <js>"HtmlSerializer.detectLinksInStrings.b"</js>
251    *    <li><b>Data type:</b>  <jk>boolean</jk>
252    *    <li><b>System property:</b>  <c>HtmlSerializer.detectLinksInStrings</c>
253    *    <li><b>Environment variable:</b>  <c>HTMLSERIALIZER_DETECTLINKSINSTRINGS</c>
254    *    <li><b>Default:</b>  <jk>true</jk>
255    *    <li><b>Session property:</b>  <jk>false</jk>
256    *    <li><b>Annotations:</b>
257    *       <ul>
258    *          <li class='ja'>{@link org.apache.juneau.html.annotation.HtmlConfig#detectLinksInStrings()}
259    *       </ul>
260    *    <li><b>Methods:</b>
261    *       <ul>
262    *          <li class='jm'>{@link org.apache.juneau.html.HtmlSerializerBuilder#dontDetectLinksInStrings()}
263    *       </ul>
264    * </ul>
265    *
266    * <h5 class='section'>Description:</h5>
267    * <p>
268    * If a string looks like a URL (i.e. starts with <js>"http://"</js> or <js>"https://"</js>, then treat it like a URL
269    * and make it into a hyperlink based on the rules specified by {@link #HTML_uriAnchorText}.
270    *
271    * <h5 class='section'>Example:</h5>
272    * <p class='bcode w800'>
273    *    <jc>// Our bean class with a property containing what looks like a URL.</jc>
274    *    <jk>public class</jk> MyBean {
275    *       <jk>public</jk> String <jf>f1</jf> = <js>"http://www.apache.org"</js>;
276    *    }
277    *
278    *  <jc>// Serializer with link detection.</jc>
279    *    WriterSerializer s1 = HtmlSerializer
280    *       .<jsm>create</jsm>()
281    *       .addKeyValueTableHeaders()
282    *       .build();
283    *
284    *  <jc>// Serializer without link detection.</jc>
285    *    WriterSerializer s2 = HtmlSerializer
286    *       .<jsm>create</jsm>()
287    *       .addKeyValueTableHeaders()
288    *       .detectLinksInStrings(<jk>false</jk>)
289    *       .build();
290    *
291    *    String withLinks = s1.serialize(<jk>new</jk> MyBean());
292    *    String withoutLinks = s2.serialize(<jk>new</jk> MyBean());
293    * </p>
294    *
295    * <p>
296    * The following shows the difference between the two generated outputs:
297    *
298    * <table class='styled'>
299    *    <tr>
300    *       <th><c>withLinks</c></th>
301    *       <th><c>withoutLinks</c></th>
302    *    </tr>
303    *    <tr>
304    *       <td>
305    *          <table class='unstyled'>
306    *             <tr><th>key</th><th>value</th></tr>
307    *             <tr><td>f1</td><td><a href='http://www.apache.org'>http://www.apache.org</a></td></tr>
308    *          </table>
309    *       </td>
310    *       <td>
311    *          <table class='unstyled'>
312    *             <tr><th>key</th><th>value</th></tr>
313    *             <tr><td>f1</td><td>http://www.apache.org</td></tr>
314    *          </table>
315    *       </td>
316    *    </tr>
317    * </table>
318    */
319   public static final String HTML_detectLinksInStrings = PREFIX + ".detectLinksInStrings.b";
320
321   /**
322    * Configuration property:  Link label parameter name.
323    *
324    * <h5 class='section'>Property:</h5>
325    * <ul class='spaced-list'>
326    *    <li><b>ID:</b>  {@link org.apache.juneau.html.HtmlSerializer#HTML_labelParameter HTML_labelParameter}
327    *    <li><b>Name:</b>  <js>"HtmlSerializer.labelParameter.s"</js>
328    *    <li><b>Data type:</b>  <c>String</c>
329    *    <li><b>System property:</b>  <c>HtmlSerializer.labelParameter</c>
330    *    <li><b>Environment variable:</b>  <c>HTMLSERIALIZER_LABELPARAMETER</c>
331    *    <li><b>Default:</b>  <js>"label"</js>
332    *    <li><b>Session property:</b>  <jk>false</jk>
333    *    <li><b>Annotations:</b>
334    *       <ul>
335    *          <li class='ja'>{@link org.apache.juneau.html.annotation.HtmlConfig#labelParameter()}
336    *       </ul>
337    *    <li><b>Methods:</b>
338    *       <ul>
339    *          <li class='jm'>{@link org.apache.juneau.html.HtmlSerializerBuilder#labelParameter(String)}
340    *       </ul>
341    * </ul>
342    *
343    * <p>
344    * The parameter name to look for when resolving link labels via {@link #HTML_detectLabelParameters}.
345    *
346    * <ul class='seealso'>
347    *    <li class='jf'>{@link #HTML_detectLabelParameters}
348    * </ul>
349    */
350   public static final String HTML_labelParameter = PREFIX + ".labelParameter.s";
351
352   /**
353    * Configuration property:  Look for link labels in URIs.
354    *
355    * <h5 class='section'>Property:</h5>
356    * <ul class='spaced-list'>
357    *    <li><b>ID:</b>  {@link org.apache.juneau.html.HtmlSerializer#HTML_detectLabelParameters HTML_detectLabelParameters}
358    *    <li><b>Name:</b>  <js>"HtmlSerializer.detectLabelParameters.b"</js>
359    *    <li><b>Data type:</b>  <jk>boolean</jk>
360    *    <li><b>System property:</b>  <c>HtmlSerializer.detectLabelParameters</c>
361    *    <li><b>Environment variable:</b>  <c>HTMLSERIALIZER_DETECTLABELPARAMETERS</c>
362    *    <li><b>Default:</b>  <jk>true</jk>
363    *    <li><b>Session property:</b>  <jk>false</jk>
364    *    <li><b>Annotations:</b>
365    *       <ul>
366    *          <li class='ja'>{@link org.apache.juneau.html.annotation.HtmlConfig#detectLabelParameters()}
367    *       </ul>
368    *    <li><b>Methods:</b>
369    *       <ul>
370    *          <li class='jm'>{@link org.apache.juneau.html.HtmlSerializerBuilder#dontDetectLabelParameters()}
371    *       </ul>
372    * </ul>
373    *
374    * <h5 class='section'>Description:</h5>
375    * <p>
376    * If the URL has a label parameter (e.g. <js>"?label=foobar"</js>), then use that as the anchor text of the link.
377    *
378    * <p>
379    * The parameter name can be changed via the {@link #HTML_labelParameter} property.
380    *
381    * <h5 class='section'>Example:</h5>
382    * <p class='bcode w800'>
383    *    <jc>// Our bean class with a property containing what looks like a URL.</jc>
384    *    <jk>public class</jk> MyBean {
385    *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org?label=Apache%20Foundation"</js>);
386    *    }
387    *
388    *  <jc>// Serializer with label detection.</jc>
389    *    WriterSerializer s1 = HtmlSerializer
390    *       .<jsm>create</jsm>()
391    *       .addKeyValueTableHeaders()
392    *       .build();
393    *
394    *  <jc>// Serializer without label detection.</jc>
395    *    WriterSerializer s2 = HtmlSerializer
396    *       .<jsm>create</jsm>()
397    *       .addKeyValueTableHeaders()
398    *       .lookForLabelParameters(<jk>false</jk>)
399    *       .build();
400    *
401    *    String withLabels = s1.serialize(<jk>new</jk> MyBean());
402    *    String withoutLabels = s2.serialize(<jk>new</jk> MyBean());
403    * </p>
404    *
405    * <p>
406    * The following shows the difference between the two generated outputs.
407    * <br>Note that they're both hyperlinks, but the anchor text differs:
408    *
409    * <table class='styled'>
410    *    <tr>
411    *       <th><c>withLabels</c></th>
412    *       <th><c>withoutLabels</c></th>
413    *    </tr>
414    *    <tr>
415    *       <td>
416    *          <table class='unstyled'>
417    *             <tr><th>key</th><th>value</th></tr>
418    *             <tr><td>f1</td><td><a href='http://www.apache.org?label=Apache%20Foundation'>Apache Foundation</a></td></tr>
419    *          </table>
420    *       </td>
421    *       <td>
422    *          <table class='unstyled'>
423    *             <tr><th>key</th><th>value</th></tr>
424    *             <tr><td>f1</td><td><a href='http://www.apache.org?label=Apache%20Foundation'>http://www.apache.org?label=Apache%20Foundation</a></td></tr>
425    *          </table>
426    *       </td>
427    *    </tr>
428    * </table>
429    */
430   public static final String HTML_detectLabelParameters = PREFIX + ".detectLabelParameters.b";
431
432   /**
433    * Configuration property:  Anchor text source.
434    *
435    * <h5 class='section'>Property:</h5>
436    * <ul class='spaced-list'>
437    *    <li><b>ID:</b>  {@link org.apache.juneau.html.HtmlSerializer#HTML_uriAnchorText HTML_uriAnchorText}
438    *    <li><b>Name:</b>  <js>"HtmlSerializer.uriAnchorText.s"</js>
439    *    <li><b>Data type:</b>  {@link org.apache.juneau.html.AnchorText}
440    *    <li><b>System property:</b>  <c>HtmlSerializer.uriAnchorText</c>
441    *    <li><b>Environment variable:</b>  <c>HTMLSERIALIZER_URIANCHORTEXT</c>
442    *    <li><b>Default:</b>  <js>"TO_STRING"</js>
443    *    <li><b>Session property:</b>  <jk>false</jk>
444    *    <li><b>Annotations:</b>
445    *       <ul>
446    *          <li class='ja'>{@link org.apache.juneau.html.annotation.Html#anchorText()}
447    *          <li class='ja'>{@link org.apache.juneau.html.annotation.HtmlConfig#uriAnchorText()}
448    *       </ul>
449    *    <li><b>Methods:</b>
450    *       <ul>
451    *          <li class='jm'>{@link org.apache.juneau.html.HtmlSerializerBuilder#uriAnchorText(AnchorText)}
452    *       </ul>
453    * </ul>
454    *
455    * <h5 class='section'>Description:</h5>
456    * <p>
457    * When creating anchor tags (e.g. <code><xt>&lt;a</xt> <xa>href</xa>=<xs>'...'</xs>
458    * <xt>&gt;</xt>text<xt>&lt;/a&gt;</xt></code>) in HTML, this setting defines what to set the inner text to.
459    *
460    * <p>
461    * The possible values are:
462    * <ul>
463    *    <li class='jc'>{@link AnchorText}
464    *    <ul>
465    *       <li class='jf'>{@link AnchorText#TO_STRING TO_STRING} (default) - Set to whatever is returned by {@link #toString()} on the object.
466    *          <br>
467    *          <h5 class='section'>Example:</h5>
468    *          <p class='bcode w800'>
469    *    <jc>// Our bean class with a URI property.</jc>
470    *    <jk>public class</jk> MyBean {
471    *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org?foo=bar#myAnchor"</js>);
472    *    }
473    *
474    *    <jc>// Serializer with TO_STRING anchor text.</jc>
475    *    WriterSerializer s1 = HtmlSerializer.<jsm>create</jsm>().anchorText(<jsf>TO_STRING</jsf>).build();
476    *
477    *    <jc>// Produces: &lt;a href='http://www.apache.org?foo=bar#myAnchor'&gt;http://www.apache.org?foo=bar#myAnchor&lt;/a&gt;</jc>
478    *    String html = s1.serialize(<jk>new</jk> MyBean());
479    *          </p>
480    *       <li class='jf'>{@link AnchorText#PROPERTY_NAME PROPERTY_NAME} - Set to the bean property name.
481    *          <br>
482    *          <h5 class='section'>Example:</h5>
483    *          <p class='bcode w800'>
484    *    <jc>// Our bean class with a URI property.</jc>
485    *    <jk>public class</jk> MyBean {
486    *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org?foo=bar#myAnchor"</js>);
487    *    }
488    *
489    *    <jc>// Serializer with PROPERTY_NAME anchor text.</jc>
490    *    WriterSerializer s1 = HtmlSerializer.<jsm>create</jsm>().anchorText(<jsf>PROPERTY_NAME</jsf>).build();
491    *
492    *    <jc>// Produces: &lt;a href='http://www.apache.org?foo=bar#myAnchor'&gt;f1&lt;/a&gt;</jc>
493    *    String html = s1.serialize(<jk>new</jk> MyBean());
494    *          </p>
495    *       <li class='jf'>{@link AnchorText#URI URI} - Set to the URI value.
496    *          <br>
497    *          <h5 class='section'>Example:</h5>
498    *          <p class='bcode w800'>
499    *    <jc>// Our bean class with a URI property.</jc>
500    *    <jk>public class</jk> MyBean {
501    *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org?foo=bar#myAnchor"</js>);
502    *    }
503    *
504    *    <jc>// Serializer with URI anchor text.</jc>
505    *    WriterSerializer s1 = HtmlSerializer.<jsm>create</jsm>().anchorText(<jsf>URI</jsf>).build();
506    *
507    *    <jc>// Produces: &lt;a href='http://www.apache.org?foo=bar#myAnchor'&gt;http://www.apache.org?foo=bar&lt;/a&gt;</jc>
508    *    String html = s1.serialize(<jk>new</jk> MyBean());
509    *          </p>
510    *       <li class='jf'>{@link AnchorText#LAST_TOKEN LAST_TOKEN} - Set to the last token of the URI value.
511    *          <br>
512    *          <h5 class='section'>Example:</h5>
513    *          <p class='bcode w800'>
514    *    <jc>// Our bean class with a URI property.</jc>
515    *    <jk>public class</jk> MyBean {
516    *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org/foo/bar?baz=qux#myAnchor"</js>);
517    *    }
518    *
519    *    <jc>// Serializer with LAST_TOKEN anchor text.</jc>
520    *    WriterSerializer s1 = HtmlSerializer.<jsm>create</jsm>().anchorText(<jsf>LAST_TOKEN</jsf>).build();
521    *
522    *    <jc>// Produces: &lt;a href='http://www.apache.org/foo/bar?baz=qux#myAnchor'&gt;bar&lt;/a&gt;</jc>
523    *    String html = s1.serialize(<jk>new</jk> MyBean());
524    *          </p>
525    *       <li class='jf'>{@link AnchorText#URI_ANCHOR URI_ANCHOR} - Set to the anchor of the URL.
526    *          <br>
527    *          <h5 class='section'>Example:</h5>
528    *          <p class='bcode w800'>
529    *    <jc>// Our bean class with a URI property.</jc>
530    *    <jk>public class</jk> MyBean {
531    *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org/foo/bar?baz=qux#myAnchor"</js>);
532    *    }
533    *
534    *    <jc>// Serializer with URI_ANCHOR anchor text.</jc>
535    *    WriterSerializer s1 = HtmlSerializer.<jsm>create</jsm>().anchorText(<jsf>URI_ANCHOR</jsf>).build();
536    *
537    *    <jc>// Produces: &lt;a href='http://www.apache.org/foo/bar?baz=qux#myAnchor'&gt;myAnchor&lt;/a&gt;</jc>
538    *    String html = s1.serialize(<jk>new</jk> MyBean());
539    *          </p>
540    *       <li class='jf'>{@link AnchorText#CONTEXT_RELATIVE CONTEXT_RELATIVE} - Same as {@link AnchorText#TO_STRING TO_STRING} but assumes it's a context-relative path.
541    *          <br>
542    *          <h5 class='section'>Example:</h5>
543    *          <p class='bcode w800'>
544    *    <jc>// Our bean class with a URI property.</jc>
545    *    <jk>public class</jk> MyBean {
546    *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"bar/baz"</js>);
547    *    }
548    *
549    *    <jc>// Serializer with CONTEXT_RELATIVE anchor text.</jc>
550    *    WriterSerializer s1 = HtmlSerializer
551    *       .<jsm>create</jsm>()
552    *       .anchorText(<jsf>CONTEXT_RELATIVE</jsf>)
553    *       .uriResolution(<jsf>ROOT_RELATIVE</jsf>)
554    *       .uriRelativity(<jsf>RESOURCE</jsf>)
555    *       .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>)
556    *       .build();
557    *
558    *    <jc>// Produces: &lt;a href&#61;'/myContext/myServlet/bar/baz'&gt;myServlet/bar/baz&lt;/a&gt;</jc>
559    *    String html = s1.serialize(<jk>new</jk> MyBean());
560    *          </p>
561    *       <li class='jf'>{@link AnchorText#SERVLET_RELATIVE SERVLET_RELATIVE} - Same as {@link AnchorText#TO_STRING TO_STRING} but assumes it's a servlet-relative path.
562    *          <br>
563    *          <h5 class='section'>Example:</h5>
564    *          <p class='bcode w800'>
565    *    <jc>// Our bean class with a URI property.</jc>
566    *    <jk>public class</jk> MyBean {
567    *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"bar/baz"</js>);
568    *    }
569    *
570    *    <jc>// Serializer with SERVLET_RELATIVE anchor text.</jc>
571    *    WriterSerializer s1 = HtmlSerializer
572    *       .<jsm>create</jsm>()
573    *       .anchorText(<jsf>SERVLET_RELATIVE</jsf>)
574    *       .uriResolution(<jsf>ROOT_RELATIVE</jsf>)
575    *       .uriRelativity(<jsf>RESOURCE</jsf>)
576    *       .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>)
577    *       .build();
578    *
579    *    <jc>// Produces: &lt;a href&#61;'/myContext/myServlet/bar/baz'&gt;bar/baz&lt;/a&gt;</jc>
580    *    String html = s1.serialize(<jk>new</jk> MyBean());
581    *          </p>
582    *       <li class='jf'>{@link AnchorText#PATH_RELATIVE PATH_RELATIVE} - Same as {@link AnchorText#TO_STRING TO_STRING} but assumes it's a path-relative path.
583    *          <br>
584    *          <h5 class='section'>Example:</h5>
585    *          <p class='bcode w800'>
586    *    <jc>// Our bean class with a URI property.</jc>
587    *    <jk>public class</jk> MyBean {
588    *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"bar/baz"</js>);
589    *    }
590    *
591    *    <jc>// Serializer with PATH_RELATIVE anchor text.</jc>
592    *    WriterSerializer s1 = HtmlSerializer
593    *       .<jsm>create</jsm>()
594    *       .anchorText(<jsf>PATH_RELATIVE</jsf>)
595    *       .uriResolution(<jsf>ROOT_RELATIVE</jsf>)
596    *       .uriRelativity(<jsf>PATH_INFO</jsf>)
597    *       .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>)
598    *       .build();
599    *
600    *    <jc>// Produces: &lt;a href&#61;'/myContext/myServlet/foo/bar/baz'&gt;bar/baz&lt;/a&gt;</jc>
601    *    String html = s1.serialize(<jk>new</jk> MyBean());
602    *          </p>
603    *    </ul>
604    * </ul>
605    */
606   public static final String HTML_uriAnchorText = PREFIX + ".uriAnchorText.s";
607
608
609   //-------------------------------------------------------------------------------------------------------------------
610   // Predefined instances
611   //-------------------------------------------------------------------------------------------------------------------
612
613   /** Default serializer, all default settings. */
614   public static final HtmlSerializer DEFAULT = new HtmlSerializer(PropertyStore.DEFAULT);
615
616   /** Default serializer, single quotes. */
617   public static final HtmlSerializer DEFAULT_SQ = new HtmlSerializer.Sq(PropertyStore.DEFAULT);
618
619   /** Default serializer, single quotes, whitespace added. */
620   public static final HtmlSerializer DEFAULT_SQ_READABLE = new HtmlSerializer.SqReadable(PropertyStore.DEFAULT);
621
622
623   //-------------------------------------------------------------------------------------------------------------------
624   // Predefined subclasses
625   //-------------------------------------------------------------------------------------------------------------------
626
627   /** Default serializer, single quotes. */
628   public static class Sq extends HtmlSerializer {
629
630      /**
631       * Constructor.
632       *
633       * @param ps The property store containing all the settings for this object.
634       */
635      public Sq(PropertyStore ps) {
636         super(
637            ps.builder()
638               .setDefault(WSERIALIZER_quoteChar, '\'')
639               .build()
640         );
641      }
642   }
643
644   /** Default serializer, single quotes, whitespace added. */
645   public static class SqReadable extends HtmlSerializer {
646
647      /**
648       * Constructor.
649       *
650       * @param ps The property store containing all the settings for this object.
651       */
652      public SqReadable(PropertyStore ps) {
653         super(
654            ps.builder()
655               .setDefault(WSERIALIZER_quoteChar, '\'')
656               .setDefault(WSERIALIZER_useWhitespace, true)
657               .build()
658         );
659      }
660   }
661
662
663   //-------------------------------------------------------------------------------------------------------------------
664   // Instance
665   //-------------------------------------------------------------------------------------------------------------------
666
667   private final AnchorText uriAnchorText;
668   private final boolean
669      detectLabelParameters,
670      detectLinksInStrings,
671      addKeyValueTableHeaders,
672      addBeanTypes;
673   private final String labelParameter;
674   private final Map<ClassMeta<?>,HtmlClassMeta> htmlClassMetas = new ConcurrentHashMap<>();
675   private final Map<BeanPropertyMeta,HtmlBeanPropertyMeta> htmlBeanPropertyMetas = new ConcurrentHashMap<>();
676
677   private volatile HtmlSchemaSerializer schemaSerializer;
678
679   /**
680    * Constructor.
681    *
682    * @param ps
683    *    The property store containing all the settings for this object.
684    */
685   public HtmlSerializer(PropertyStore ps) {
686      this(ps, "text/html", (String)null);
687   }
688
689   /**
690    * Constructor.
691    *
692    * @param ps
693    *    The property store containing all the settings for this object.
694    * @param produces
695    *    The media type that this serializer produces.
696    * @param accept
697    *    The accept media types that the serializer can handle.
698    *    <p>
699    *    Can contain meta-characters per the <c>media-type</c> specification of
700    *    {@doc ExtRFC2616.section14.1}
701    *    <p>
702    *    If empty, then assumes the only media type supported is <c>produces</c>.
703    *    <p>
704    *    For example, if this serializer produces <js>"application/json"</js> but should handle media types of
705    *    <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be:
706    *    <p class='bcode w800'>
707    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>);
708    *    </p>
709    *    <br>...or...
710    *    <p class='bcode w800'>
711    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*&#8203;/json"</js>);
712    *    </p>
713    * <p>
714    * The accept value can also contain q-values.
715    */
716   public HtmlSerializer(PropertyStore ps, String produces, String accept) {
717      super(ps, produces, accept);
718      uriAnchorText = getProperty(HTML_uriAnchorText, AnchorText.class, AnchorText.TO_STRING);
719      detectLabelParameters = getBooleanProperty(HTML_detectLabelParameters, true);
720      detectLinksInStrings = getBooleanProperty(HTML_detectLinksInStrings, true);
721      labelParameter = getStringProperty(HTML_labelParameter, "label");
722      addKeyValueTableHeaders = getBooleanProperty(HTML_addKeyValueTableHeaders, false);
723      addBeanTypes = getBooleanProperty(HTML_addBeanTypes, getBooleanProperty(SERIALIZER_addBeanTypes, false));
724   }
725
726   @Override /* Context */
727   public HtmlSerializerBuilder builder() {
728      return new HtmlSerializerBuilder(getPropertyStore());
729   }
730
731   /**
732    * Instantiates a new clean-slate {@link HtmlSerializerBuilder} object.
733    *
734    * <p>
735    * This is equivalent to simply calling <code><jk>new</jk> HtmlSerializerBuilder()</code>.
736    *
737    * <p>
738    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
739    * the settings of the object called on.
740    *
741    * @return A new {@link HtmlSerializerBuilder} object.
742    */
743   public static HtmlSerializerBuilder create() {
744      return new HtmlSerializerBuilder();
745   }
746
747   @Override /* Serializer */
748   public HtmlSerializerSession createSession() {
749      return createSession(createDefaultSessionArgs());
750   }
751
752   @Override /* Serializer */
753   public HtmlSerializerSession createSession(SerializerSessionArgs args) {
754      return new HtmlSerializerSession(this, args);
755   }
756
757   @Override /* XmlSerializer */
758   public HtmlSerializer getSchemaSerializer() {
759      if (schemaSerializer == null)
760         schemaSerializer = builder().build(HtmlSchemaSerializer.class);
761      return schemaSerializer;
762   }
763
764   //-----------------------------------------------------------------------------------------------------------------
765   // Extended metadata
766   //-----------------------------------------------------------------------------------------------------------------
767
768   @Override /* HtmlMetaProvider */
769   public HtmlClassMeta getHtmlClassMeta(ClassMeta<?> cm) {
770      HtmlClassMeta m = htmlClassMetas.get(cm);
771      if (m == null) {
772         m = new HtmlClassMeta(cm, this);
773         htmlClassMetas.put(cm, m);
774      }
775      return m;
776   }
777
778   @Override /* HtmlMetaProvider */
779   public HtmlBeanPropertyMeta getHtmlBeanPropertyMeta(BeanPropertyMeta bpm) {
780      if (bpm == null)
781         return HtmlBeanPropertyMeta.DEFAULT;
782      HtmlBeanPropertyMeta m = htmlBeanPropertyMetas.get(bpm);
783      if (m == null) {
784         m = new HtmlBeanPropertyMeta(bpm.getDelegateFor(), this);
785         htmlBeanPropertyMetas.put(bpm, m);
786      }
787      return m;
788   }
789
790   //-----------------------------------------------------------------------------------------------------------------
791   // Properties
792   //-----------------------------------------------------------------------------------------------------------------
793
794   /**
795    * Add <js>"_type"</js> properties when needed.
796    *
797    * @see #HTML_addBeanTypes
798    * @return
799    *    <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred
800    *    through reflection.
801    */
802   @Override
803   protected final boolean isAddBeanTypes() {
804      return addBeanTypes;
805   }
806
807   /**
808    * Add key/value headers on bean/map tables.
809    *
810    * @see #HTML_addKeyValueTableHeaders
811    * @return
812    *    <jk>true</jk> if <bc>key</bc> and <bc>value</bc> column headers are added to tables.
813    */
814   protected final boolean isAddKeyValueTableHeaders() {
815      return addKeyValueTableHeaders;
816   }
817
818   /**
819    * Look for link labels in URIs.
820    *
821    * @see #HTML_detectLabelParameters
822    * @return
823    *    <jk>true</jk> if we should look for URL label parameters (e.g. <js>"?label=foobar"</js>).
824    */
825   protected final boolean isDetectLabelParameters() {
826      return detectLabelParameters;
827   }
828
829   /**
830    * Look for URLs in {@link String Strings}.
831    *
832    * @see #HTML_detectLinksInStrings
833    * @return
834    *    <jk>true</jk> if we should automatically convert strings to URLs if they look like a URL.
835    */
836   protected final boolean isDetectLinksInStrings() {
837      return detectLinksInStrings;
838   }
839
840   /**
841    * Link label parameter name.
842    *
843    * @see #HTML_labelParameter
844    * @return
845    *    The parameter name to look for when resolving link labels via {@link #HTML_detectLabelParameters}.
846    */
847   protected final String getLabelParameter() {
848      return labelParameter;
849   }
850
851   /**
852    * Anchor text source.
853    *
854    * @see #HTML_uriAnchorText
855    * @return
856    *    When creating anchor tags (e.g. <code><xt>&lt;a</xt> <xa>href</xa>=<xs>'...'</xs>
857    *    <xt>&gt;</xt>text<xt>&lt;/a&gt;</xt></code>) in HTML, this setting defines what to set the inner text to.
858    */
859   protected final AnchorText getUriAnchorText() {
860      return uriAnchorText;
861   }
862
863   //-----------------------------------------------------------------------------------------------------------------
864   // Other methods
865   //-----------------------------------------------------------------------------------------------------------------
866
867   @Override /* Context */
868   public OMap toMap() {
869      return super.toMap()
870         .a("HtmlSerializer", new DefaultFilteringOMap()
871            .a("uriAnchorText", uriAnchorText)
872            .a("detectLabelParameters", detectLabelParameters)
873            .a("detectLinksInStrings", detectLinksInStrings)
874            .a("labelParameter", labelParameter)
875            .a("addKeyValueTableHeaders", addKeyValueTableHeaders)
876            .a("addBeanTypes", addBeanTypes)
877         );
878   }
879}