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