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.html;
018
019import static org.apache.juneau.commons.utils.AssertionUtils.*;
020import static org.apache.juneau.commons.utils.Utils.*;
021
022import java.lang.annotation.*;
023import java.nio.charset.*;
024import java.util.*;
025import java.util.concurrent.*;
026import java.util.concurrent.atomic.*;
027
028import org.apache.juneau.*;
029import org.apache.juneau.commons.collections.*;
030import org.apache.juneau.commons.function.*;
031import org.apache.juneau.commons.reflect.*;
032import org.apache.juneau.html.annotation.*;
033import org.apache.juneau.serializer.*;
034import org.apache.juneau.xml.*;
035
036/**
037 * Serializes POJO models to HTML.
038 *
039 * <h5 class='topic'>Media types</h5>
040 * <p>
041 * Handles <c>Accept</c> types:  <bc>text/html</bc>
042 * <p>
043 * Produces <c>Content-Type</c> types:  <bc>text/html</bc>
044 *
045 * <h5 class='topic'>Description</h5>
046 * <p>
047 * The conversion is as follows...
048 * <ul class='spaced-list'>
049 *    <li>
050 *       {@link Map Maps} (e.g. {@link HashMap}, {@link TreeMap}) and beans are converted to HTML tables with
051 *       'key' and 'value' columns.
052 *    <li>
053 *       {@link Collection Collections} (e.g. {@link HashSet}, {@link LinkedList}) and Java arrays are converted
054 *       to HTML ordered lists.
055 *    <li>
056 *       {@code Collections} of {@code Maps} and beans are converted to HTML tables with keys as headers.
057 *    <li>
058 *       Everything else is converted to text.
059 * </ul>
060 *
061 * <p>
062 * This serializer provides several serialization options.  Typically, one of the predefined <jsf>DEFAULT</jsf>
063 * serializers will be sufficient.
064 * However, custom serializers can be constructed to fine-tune behavior.
065 *
066 * <p>
067 * The {@link HtmlLink} annotation can be used on beans to add hyperlinks to the output.
068 *
069 * <h5 class='topic'>Behavior-specific subclasses</h5>
070 * <p>
071 * The following direct subclasses are provided for convenience:
072 * <ul class='spaced-list'>
073 *    <li>
074 *       {@link Sq} - Default serializer, single quotes.
075 *    <li>
076 *       {@link SqReadable} - Default serializer, single quotes, whitespace added.
077 * </ul>
078 *
079 * <h5 class='section'>Example:</h5>
080 * <p class='bjava'>
081 *    <jc>// Use one of the default serializers to serialize a POJO</jc>
082 *    String <jv>html</jv> = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(<jv>someObject</jv>);
083 *
084 *    <jc>// Create a custom serializer that doesn't use whitespace and newlines</jc>
085 *    HtmlSerializer <jv>serializer</jv> = HtmlSerializer.<jsm>create</jsm>().ws().build();
086 *
087 *    <jc>// Same as above, except uses cloning</jc>
088 *    HtmlSerializer <jv>serializer</jv> = HtmlSerializer.<jsf>DEFAULT</jsf>.copy().ws().build();
089 *
090 *    <jc>// Serialize POJOs to HTML</jc>
091 *
092 *    <jc>// Produces: </jc>
093 *    <jc>// &lt;ul&gt;&lt;li&gt;1&lt;li&gt;2&lt;li&gt;3&lt;/ul&gt;</jc>
094 *    List <jv>list</jv> = JsonList.<jsm>of</jsm>(1, 2, 3);
095 *    String <jv>html</jv> = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(<jv>list</jv>);
096 *
097 *    <jc>// Produces: </jc>
098 *    <jc>//    &lt;table&gt; </jc>
099 *    <jc>//       &lt;tr&gt;&lt;th&gt;firstName&lt;/th&gt;&lt;th&gt;lastName&lt;/th&gt;&lt;/tr&gt; </jc>
100 *    <jc>//       &lt;tr&gt;&lt;td&gt;Bob&lt;/td&gt;&lt;td&gt;Costas&lt;/td&gt;&lt;/tr&gt; </jc>
101 *    <jc>//       &lt;tr&gt;&lt;td&gt;Billy&lt;/td&gt;&lt;td&gt;TheKid&lt;/td&gt;&lt;/tr&gt; </jc>
102 *    <jc>//       &lt;tr&gt;&lt;td&gt;Barney&lt;/td&gt;&lt;td&gt;Miller&lt;/td&gt;&lt;/tr&gt; </jc>
103 *    <jc>//    &lt;/table&gt; </jc>
104 *    <jv>html</jv> = JsonList.<jsm>of</jsm>(
105 *       JsonMap.<jsm>ofJson</jsm>(<js>"{firstName:'Bob',lastName:'Costas'}"</js>),
106 *       JsonMap.<jsm>ofJson</jsm>(<js>"{firstName:'Billy',lastName:'TheKid'}"</js>),
107 *       JsonMap.<jsm>ofJson</jsm>(<js>"{firstName:'Barney',lastName:'Miller'}"</js>)
108 *    );
109 *    String <jv>html</jv> = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(<jv>list</jv>);
110 *
111 *    <jc>// Produces: </jc>
112 *    <jc>//    &lt;table&gt; </jc>
113 *    <jc>//       &lt;tr&gt;&lt;th&gt;key&lt;/th&gt;&lt;th&gt;value&lt;/th&gt;&lt;/tr&gt; </jc>
114 *    <jc>//       &lt;tr&gt;&lt;td&gt;foo&lt;/td&gt;&lt;td&gt;bar&lt;/td&gt;&lt;/tr&gt; </jc>
115 *    <jc>//       &lt;tr&gt;&lt;td&gt;baz&lt;/td&gt;&lt;td&gt;123&lt;/td&gt;&lt;/tr&gt; </jc>
116 *    <jc>//    &lt;/table&gt; </jc>
117 *    Map <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:'bar',baz:123}"</js>);
118 *    String <jv>html</jv> = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(<jv>map</jv>);
119 *
120 *    <jc>// HTML elements can be nested arbitrarily deep</jc>
121 *    <jc>// Produces: </jc>
122 *    <jc>//   &lt;table&gt; </jc>
123 *    <jc>//      &lt;tr&gt;&lt;th&gt;key&lt;/th&gt;&lt;th&gt;value&lt;/th&gt;&lt;/tr&gt; </jc>
124 *    <jc>//      &lt;tr&gt;&lt;td&gt;foo&lt;/td&gt;&lt;td&gt;bar&lt;/td&gt;&lt;/tr&gt; </jc>
125 *    <jc>//      &lt;tr&gt;&lt;td&gt;baz&lt;/td&gt;&lt;td&gt;123&lt;/td&gt;&lt;/tr&gt; </jc>
126 *    <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>
127 *    <jc>//      &lt;tr&gt;&lt;td&gt;someSubMap&lt;/td&gt;&lt;td&gt; </jc>
128 *    <jc>//         &lt;table&gt; </jc>
129 *    <jc>//            &lt;tr&gt;&lt;th&gt;key&lt;/th&gt;&lt;th&gt;value&lt;/th&gt;&lt;/tr&gt; </jc>
130 *    <jc>//            &lt;tr&gt;&lt;td&gt;a&lt;/td&gt;&lt;td&gt;b&lt;/td&gt;&lt;/tr&gt; </jc>
131 *    <jc>//         &lt;/table&gt; </jc>
132 *    <jc>//      &lt;/td&gt;&lt;/tr&gt; </jc>
133 *    <jc>//   &lt;/table&gt; </jc>
134 *    Map <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:'bar',baz:123}"</js>);
135 *    <jv>map</jv>.put(<js>"someNumbers"</js>, JsonList.<jsm>of</jsm>(1, 2, 3));
136 *    <jv>map</jv>.put(<js>"someSubMap"</js>, JsonMap.<jsm>ofJson</jsm>(<js>"{a:'b'}"</js>));
137 *    String <jv>html</jv> = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(<jv>map</jv>);
138 * </p>
139 *
140 * <h5 class='section'>Notes:</h5><ul>
141 *    <li class='note'>This class is thread safe and reusable.
142 * </ul>
143 *
144 * <h5 class='section'>See Also:</h5><ul>
145 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/HtmlBasics">HTML Basics</a>
146
147 * </ul>
148 */
149public class HtmlSerializer extends XmlSerializer implements HtmlMetaProvider {
150   /**
151    * Builder class.
152    */
153   public static class Builder extends XmlSerializer.Builder {
154
155      private static final Cache<HashKey,HtmlSerializer> CACHE = Cache.of(HashKey.class, HtmlSerializer.class).build();
156
157      private boolean addBeanTypesHtml;
158      private boolean addKeyValueTableHeaders;
159      private boolean disableDetectLabelParameters;
160      private boolean disableDetectLinksInStrings;
161      private String labelParameter;
162      private AnchorText uriAnchorText;
163
164      /**
165       * Constructor, default settings.
166       */
167      protected Builder() {
168         produces("text/html");
169         addBeanTypesHtml = env("HtmlSerializer.addBeanTypesHtml", false);
170         addKeyValueTableHeaders = env("HtmlSerializer.addKeyValueTableHeaders", false);
171         disableDetectLabelParameters = env("HtmlSerializer.disableDetectLabelParameters", false);
172         disableDetectLinksInStrings = env("HtmlSerializer.disableDetectLinksInStrings", false);
173         uriAnchorText = env("HtmlSerializer.uriAnchorText", AnchorText.TO_STRING);
174         labelParameter = env("HtmlSerializer.labelParameter", "label");
175      }
176
177      /**
178       * Copy constructor.
179       *
180       * @param copyFrom The builder to copy from.
181       *    <br>Cannot be <jk>null</jk>.
182       */
183      protected Builder(Builder copyFrom) {
184         super(assertArgNotNull("copyFrom", copyFrom));
185         addBeanTypesHtml = copyFrom.addBeanTypesHtml;
186         addKeyValueTableHeaders = copyFrom.addKeyValueTableHeaders;
187         disableDetectLabelParameters = copyFrom.disableDetectLabelParameters;
188         disableDetectLinksInStrings = copyFrom.disableDetectLinksInStrings;
189         labelParameter = copyFrom.labelParameter;
190         uriAnchorText = copyFrom.uriAnchorText;
191      }
192
193      /**
194       * Copy constructor.
195       *
196       * @param copyFrom The bean to copy from.
197       *    <br>Cannot be <jk>null</jk>.
198       */
199      protected Builder(HtmlSerializer copyFrom) {
200         super(assertArgNotNull("copyFrom", copyFrom));
201         addBeanTypesHtml = copyFrom.addBeanTypesHtml;
202         addKeyValueTableHeaders = copyFrom.addKeyValueTableHeaders;
203         disableDetectLabelParameters = ! copyFrom.detectLabelParameters;
204         disableDetectLinksInStrings = ! copyFrom.detectLinksInStrings;
205         labelParameter = copyFrom.labelParameter;
206         uriAnchorText = copyFrom.uriAnchorText;
207      }
208
209      @Override /* Overridden from Builder */
210      public Builder accept(String value) {
211         super.accept(value);
212         return this;
213      }
214
215      @Override /* Overridden from Builder */
216      public Builder addBeanTypes() {
217         super.addBeanTypes();
218         return this;
219      }
220
221      @Override /* Overridden from Builder */
222      public Builder addBeanTypes(boolean value) {
223         super.addBeanTypes(value);
224         return this;
225      }
226
227      /**
228       * Add <js>"_type"</js> properties when needed.
229       *
230       * <p>
231       * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred
232       * through reflection.
233       *
234       * <p>
235       * When present, this value overrides the {@link org.apache.juneau.serializer.Serializer.Builder#addBeanTypes()} setting and is
236       * provided to customize the behavior of specific serializers in a {@link SerializerSet}.
237       *
238       * @return This object.
239       */
240      public Builder addBeanTypesHtml() {
241         return addBeanTypesHtml(true);
242      }
243
244      /**
245       * Same as {@link #addBeanTypesHtml()} but allows you to explicitly specify the value.
246       *
247       * @param value The value for this setting.
248       * @return This object.
249       */
250      public Builder addBeanTypesHtml(boolean value) {
251         addBeanTypesHtml = value;
252         return this;
253      }
254
255      @Override /* Overridden from Builder */
256      public Builder addBeanTypesXml() {
257         super.addBeanTypesXml();
258         return this;
259      }
260
261      @Override /* Overridden from Builder */
262      public Builder addBeanTypesXml(boolean value) {
263         super.addBeanTypesXml(value);
264         return this;
265      }
266
267      /**
268       * <i><l>HtmlSerializer</l> configuration property:&emsp;</i>  Add key/value headers on bean/map tables.
269       *
270       * <p>
271       * When enabled, <bc>key</bc> and <bc>value</bc> column headers are added to tables.
272       *
273       * <h5 class='section'>Example:</h5>
274       * <p class='bjson'>
275       *    <jc>// Our bean class.</jc>
276       *    <jk>public class</jk> MyBean {
277       *       <jk>public</jk> String <jf>f1</jf> = <js>"foo"</js>;
278       *       <jk>public</jk> String <jf>f2</jf> = <js>"bar"</js>;
279       *    }
280       *
281       *  <jc>// Serializer without headers.</jc>
282       *    WriterSerializer <jv>serializer1</jv> = HtmlSerializer.<jsf>DEFAULT</jsf>;
283       *
284       *  <jc>// Serializer with headers.</jc>
285       *    WriterSerializer <jv>serializer2</jv> = HtmlSerializer
286       *       .<jsm>create</jsm>()
287       *       .addKeyValueTableHeaders()
288       *       .build();
289       *
290       *    String <jv>withoutHeaders</jv> = <jv>serializer1</jv>.serialize(<jk>new</jk> MyBean());
291       *    String <jv>withHeaders</jv> = <jv>serializer2</jv>.serialize(<jk>new</jk> MyBean());
292       * </p>
293       *
294       * <p>
295       * The following shows the difference between the two generated outputs:
296       *
297       * <table class='styled'>
298       *    <tr>
299       *       <th><c>withoutHeaders</c></th>
300       *       <th><c>withHeaders</c></th>
301       *    </tr>
302       *    <tr>
303       *       <td>
304       *          <table class='unstyled'>
305       *             <tr><td>f1</td><td>foo</td></tr>
306       *             <tr><td>f2</td><td>bar</td></tr>
307       *          </table>
308       *       </td>
309       *       <td>
310       *          <table class='unstyled'>
311       *             <tr><th>key</th><th>value</th></tr>
312       *             <tr><td>f1</td><td>foo</td></tr>
313       *             <tr><td>f2</td><td>bar</td></tr>
314       *          </table>
315       *       </td>
316       *    </tr>
317       * </table>
318       *
319       * @return This object.
320       */
321      public Builder addKeyValueTableHeaders() {
322         return addKeyValueTableHeaders(true);
323      }
324
325      /**
326       * Same as {@link #addKeyValueTableHeaders()} but allows you to explicitly specify the value.
327       *
328       * @param value The value for this setting.
329       * @return This object.
330       */
331      public Builder addKeyValueTableHeaders(boolean value) {
332         addKeyValueTableHeaders = value;
333         return this;
334      }
335
336      @Override /* Overridden from Builder */
337      public Builder addNamespaceUrisToRoot() {
338         super.addNamespaceUrisToRoot();
339         return this;
340      }
341
342      @Override /* Overridden from Builder */
343      public Builder addNamespaceUrisToRoot(boolean value) {
344         super.addNamespaceUrisToRoot(value);
345         return this;
346      }
347
348      @Override /* Overridden from Builder */
349      public Builder addRootType() {
350         super.addRootType();
351         return this;
352      }
353
354      @Override /* Overridden from Builder */
355      public Builder addRootType(boolean value) {
356         super.addRootType(value);
357         return this;
358      }
359
360      @Override /* Overridden from Builder */
361      public Builder annotations(Annotation...values) {
362         super.annotations(values);
363         return this;
364      }
365
366      @Override /* Overridden from Builder */
367      public Builder apply(AnnotationWorkList work) {
368         super.apply(work);
369         return this;
370      }
371
372      @Override /* Overridden from Builder */
373      public Builder applyAnnotations(Class<?>...from) {
374         super.applyAnnotations(from);
375         return this;
376      }
377
378      @Override /* Overridden from Builder */
379      public Builder applyAnnotations(Object...from) {
380         super.applyAnnotations(from);
381         return this;
382      }
383
384      @Override /* Overridden from Builder */
385      public Builder beanClassVisibility(Visibility value) {
386         super.beanClassVisibility(value);
387         return this;
388      }
389
390      @Override /* Overridden from Builder */
391      public Builder beanConstructorVisibility(Visibility value) {
392         super.beanConstructorVisibility(value);
393         return this;
394      }
395
396      @Override /* Overridden from Builder */
397      public Builder beanContext(BeanContext value) {
398         super.beanContext(value);
399         return this;
400      }
401
402      @Override /* Overridden from Builder */
403      public Builder beanContext(BeanContext.Builder value) {
404         super.beanContext(value);
405         return this;
406      }
407
408      @Override /* Overridden from Builder */
409      public Builder beanDictionary(java.lang.Class<?>...values) {
410         super.beanDictionary(values);
411         return this;
412      }
413
414      @Override /* Overridden from Builder */
415      public Builder beanFieldVisibility(Visibility value) {
416         super.beanFieldVisibility(value);
417         return this;
418      }
419
420      @Override /* Overridden from Builder */
421      public Builder beanInterceptor(Class<?> on, Class<? extends org.apache.juneau.swap.BeanInterceptor<?>> value) {
422         super.beanInterceptor(on, value);
423         return this;
424      }
425
426      @Override /* Overridden from Builder */
427      public Builder beanMapPutReturnsOldValue() {
428         super.beanMapPutReturnsOldValue();
429         return this;
430      }
431
432      @Override /* Overridden from Builder */
433      public Builder beanMethodVisibility(Visibility value) {
434         super.beanMethodVisibility(value);
435         return this;
436      }
437
438      @Override /* Overridden from Builder */
439      public Builder beanProperties(Class<?> beanClass, String properties) {
440         super.beanProperties(beanClass, properties);
441         return this;
442      }
443
444      @Override /* Overridden from Builder */
445      public Builder beanProperties(Map<String,Object> values) {
446         super.beanProperties(values);
447         return this;
448      }
449
450      @Override /* Overridden from Builder */
451      public Builder beanProperties(String beanClassName, String properties) {
452         super.beanProperties(beanClassName, properties);
453         return this;
454      }
455
456      @Override /* Overridden from Builder */
457      public Builder beanPropertiesExcludes(Class<?> beanClass, String properties) {
458         super.beanPropertiesExcludes(beanClass, properties);
459         return this;
460      }
461
462      @Override /* Overridden from Builder */
463      public Builder beanPropertiesExcludes(Map<String,Object> values) {
464         super.beanPropertiesExcludes(values);
465         return this;
466      }
467
468      @Override /* Overridden from Builder */
469      public Builder beanPropertiesExcludes(String beanClassName, String properties) {
470         super.beanPropertiesExcludes(beanClassName, properties);
471         return this;
472      }
473
474      @Override /* Overridden from Builder */
475      public Builder beanPropertiesReadOnly(Class<?> beanClass, String properties) {
476         super.beanPropertiesReadOnly(beanClass, properties);
477         return this;
478      }
479
480      @Override /* Overridden from Builder */
481      public Builder beanPropertiesReadOnly(Map<String,Object> values) {
482         super.beanPropertiesReadOnly(values);
483         return this;
484      }
485
486      @Override /* Overridden from Builder */
487      public Builder beanPropertiesReadOnly(String beanClassName, String properties) {
488         super.beanPropertiesReadOnly(beanClassName, properties);
489         return this;
490      }
491
492      @Override /* Overridden from Builder */
493      public Builder beanPropertiesWriteOnly(Class<?> beanClass, String properties) {
494         super.beanPropertiesWriteOnly(beanClass, properties);
495         return this;
496      }
497
498      @Override /* Overridden from Builder */
499      public Builder beanPropertiesWriteOnly(Map<String,Object> values) {
500         super.beanPropertiesWriteOnly(values);
501         return this;
502      }
503
504      @Override /* Overridden from Builder */
505      public Builder beanPropertiesWriteOnly(String beanClassName, String properties) {
506         super.beanPropertiesWriteOnly(beanClassName, properties);
507         return this;
508      }
509
510      @Override /* Overridden from Builder */
511      public Builder beansRequireDefaultConstructor() {
512         super.beansRequireDefaultConstructor();
513         return this;
514      }
515
516      @Override /* Overridden from Builder */
517      public Builder beansRequireSerializable() {
518         super.beansRequireSerializable();
519         return this;
520      }
521
522      @Override /* Overridden from Builder */
523      public Builder beansRequireSettersForGetters() {
524         super.beansRequireSettersForGetters();
525         return this;
526      }
527
528      @Override /* Overridden from Context.Builder */
529      public HtmlSerializer build() {
530         return cache(CACHE).build(HtmlSerializer.class);
531      }
532
533      @Override /* Overridden from Builder */
534      public Builder cache(Cache<HashKey,? extends org.apache.juneau.Context> value) {
535         super.cache(value);
536         return this;
537      }
538
539      @Override /* Overridden from Context.Builder */
540      public Builder copy() {
541         return new Builder(this);
542      }
543
544      @Override /* Overridden from Builder */
545      public Builder debug() {
546         super.debug();
547         return this;
548      }
549
550      @Override /* Overridden from Builder */
551      public Builder debug(boolean value) {
552         super.debug(value);
553         return this;
554      }
555
556      @Override /* Overridden from Builder */
557      public Builder defaultNamespace(Namespace value) {
558         super.defaultNamespace(value);
559         return this;
560      }
561
562      @Override /* Overridden from Builder */
563      public Builder detectRecursions() {
564         super.detectRecursions();
565         return this;
566      }
567
568      @Override /* Overridden from Builder */
569      public Builder detectRecursions(boolean value) {
570         super.detectRecursions(value);
571         return this;
572      }
573
574      @Override /* Overridden from Builder */
575      public Builder dictionaryOn(Class<?> on, java.lang.Class<?>...values) {
576         super.dictionaryOn(on, values);
577         return this;
578      }
579
580      @Override /* Overridden from Builder */
581      public Builder disableAutoDetectNamespaces() {
582         super.disableAutoDetectNamespaces();
583         return this;
584      }
585
586      @Override /* Overridden from Builder */
587      public Builder disableAutoDetectNamespaces(boolean value) {
588         super.disableAutoDetectNamespaces(value);
589         return this;
590      }
591
592      @Override /* Overridden from Builder */
593      public Builder disableBeansRequireSomeProperties() {
594         super.disableBeansRequireSomeProperties();
595         return this;
596      }
597
598      /**
599       * <i><l>HtmlSerializer</l> configuration property:&emsp;</i>  Dont look for link labels in URIs.
600       *
601       * <p>
602       * 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.
603       *
604       * <p>
605       * The parameter name can be changed via the {@link #labelParameter(String)} property.
606       *
607       * <h5 class='section'>Example:</h5>
608       * <p class='bjson'>
609       *    <jc>// Our bean class with a property containing what looks like a URL.</jc>
610       *    <jk>public class</jk> MyBean {
611       *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org?label=Apache%20Foundation"</js>);
612       *    }
613       *
614       *  <jc>// Serializer with label detection.</jc>
615       *    WriterSerializer <jv>serializer1</jv> = HtmlSerializer
616       *       .<jsm>create</jsm>()
617       *       .addKeyValueTableHeaders()
618       *       .build();
619       *
620       *  <jc>// Serializer without label detection.</jc>
621       *    WriterSerializer <jv>serializer2</jv> = HtmlSerializer
622       *       .<jsm>create</jsm>()
623       *       .addKeyValueTableHeaders()
624       *       .disableDetectLabelParameters()
625       *       .build();
626       *
627       *    String <jv>withLabels</jv> = <jv>serializer1</jv>.serialize(<jk>new</jk> MyBean());
628       *    String <jv>withoutLabels</jv> = <jv>serializer2</jv>.serialize(<jk>new</jk> MyBean());
629       * </p>
630       *
631       * <p>
632       * The following shows the difference between the two generated outputs.
633       * <br>Note that they're both hyperlinks, but the anchor text differs:
634       *
635       * <table class='styled'>
636       *    <tr>
637       *       <th><c>withLabels</c></th>
638       *       <th><c>withoutLabels</c></th>
639       *    </tr>
640       *    <tr>
641       *       <td>
642       *          <table class='unstyled'>
643       *             <tr><th>key</th><th>value</th></tr>
644       *             <tr><td>f1</td><td><a href='http://www.apache.org?label=Apache%20Foundation'>Apache Foundation</a></td></tr>
645       *          </table>
646       *       </td>
647       *       <td>
648       *          <table class='unstyled'>
649       *             <tr><th>key</th><th>value</th></tr>
650       *             <tr><td>f1</td><td><a href='http://www.apache.org?label=Apache%20Foundation'>http://www.apache.org?label=Apache%20Foundation</a></td></tr>
651       *          </table>
652       *       </td>
653       *    </tr>
654       * </table>
655       *
656       * @return This object.
657       */
658      public Builder disableDetectLabelParameters() {
659         return disableDetectLabelParameters(true);
660      }
661
662      /**
663       * Same as {@link #disableDetectLabelParameters()} but allows you to explicitly specify the value.
664       *
665       * @param value The value for this setting.
666       * @return This object.
667       */
668      public Builder disableDetectLabelParameters(boolean value) {
669         disableDetectLabelParameters = value;
670         return this;
671      }
672
673      /**
674       * <i><l>HtmlSerializer</l> configuration property:&emsp;</i>  Don't look for URLs in {@link String Strings}.
675       *
676       * <p>
677       * 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
678       * and make it into a hyperlink based on the rules specified by {@link Builder#uriAnchorText(AnchorText)}.
679       *
680       * <h5 class='section'>Example:</h5>
681       * <p class='bjson'>
682       *    <jc>// Our bean class with a property containing what looks like a URL.</jc>
683       *    <jk>public class</jk> MyBean {
684       *       <jk>public</jk> String <jf>f1</jf> = <js>"http://www.apache.org"</js>;
685       *    }
686       *
687       *  <jc>// Serializer with link detection.</jc>
688       *    WriterSerializer <jv>serializer1</jv> = HtmlSerializer
689       *       .<jsm>create</jsm>()
690       *       .addKeyValueTableHeaders()
691       *       .build();
692       *
693       *  <jc>// Serializer without link detection.</jc>
694       *    WriterSerializer <jv>serializer2</jv> = HtmlSerializer
695       *       .<jsm>create</jsm>()
696       *       .addKeyValueTableHeaders()
697       *       .disableDetectLinksInStrings()
698       *       .build();
699       *
700       *    String <jv>withLinks</jv> = <jv>serializer1</jv>.serialize(<jk>new</jk> MyBean());
701       *    String <jv>withoutLinks</jv> = <jv>serializer2</jv>.serialize(<jk>new</jk> MyBean());
702       * </p>
703       *
704       * <p>
705       * The following shows the difference between the two generated outputs:
706       *
707       * <table class='styled'>
708       *    <tr>
709       *       <th><c>withLinks</c></th>
710       *       <th><c>withoutLinks</c></th>
711       *    </tr>
712       *    <tr>
713       *       <td>
714       *          <table class='unstyled'>
715       *             <tr><th>key</th><th>value</th></tr>
716       *             <tr><td>f1</td><td><a href='http://www.apache.org'>http://www.apache.org</a></td></tr>
717       *          </table>
718       *       </td>
719       *       <td>
720       *          <table class='unstyled'>
721       *             <tr><th>key</th><th>value</th></tr>
722       *             <tr><td>f1</td><td>http://www.apache.org</td></tr>
723       *          </table>
724       *       </td>
725       *    </tr>
726       * </table>
727       *
728       * @return This object.
729       */
730      public Builder disableDetectLinksInStrings() {
731         return disableDetectLinksInStrings(true);
732      }
733
734      /**
735       * Same as {@link #disableDetectLinksInStrings()} but allows you to explicitly specify the value.
736       *
737       * @param value The value for this setting.
738       * @return This object.
739       */
740      public Builder disableDetectLinksInStrings(boolean value) {
741         disableDetectLinksInStrings = value;
742         return this;
743      }
744
745      @Override /* Overridden from Builder */
746      public Builder disableIgnoreMissingSetters() {
747         super.disableIgnoreMissingSetters();
748         return this;
749      }
750
751      @Override /* Overridden from Builder */
752      public Builder disableIgnoreTransientFields() {
753         super.disableIgnoreTransientFields();
754         return this;
755      }
756
757      @Override /* Overridden from Builder */
758      public Builder disableIgnoreUnknownNullBeanProperties() {
759         super.disableIgnoreUnknownNullBeanProperties();
760         return this;
761      }
762
763      @Override /* Overridden from Builder */
764      public Builder disableInterfaceProxies() {
765         super.disableInterfaceProxies();
766         return this;
767      }
768
769      @Override /* Overridden from XmlSerializer.Builder */
770      public Builder disableJsonTags() {
771         super.disableJsonTags();
772         return this;
773      }
774
775      @Override /* Overridden from XmlSerializer.Builder */
776      public Builder disableJsonTags(boolean value) {
777         super.disableJsonTags(value);
778         return this;
779      }
780
781      @Override /* Overridden from Builder */
782      public Builder enableNamespaces() {
783         super.enableNamespaces();
784         return this;
785      }
786
787      @Override /* Overridden from Builder */
788      public Builder enableNamespaces(boolean value) {
789         super.enableNamespaces(value);
790         return this;
791      }
792
793      @Override /* Overridden from Builder */
794      public <T> Builder example(Class<T> pojoClass, String json) {
795         super.example(pojoClass, json);
796         return this;
797      }
798
799      @Override /* Overridden from Builder */
800      public <T> Builder example(Class<T> pojoClass, T o) {
801         super.example(pojoClass, o);
802         return this;
803      }
804
805      @Override /* Overridden from Builder */
806      public Builder fileCharset(Charset value) {
807         super.fileCharset(value);
808         return this;
809      }
810
811      @Override /* Overridden from Builder */
812      public Builder findFluentSetters() {
813         super.findFluentSetters();
814         return this;
815      }
816
817      @Override /* Overridden from Builder */
818      public Builder findFluentSetters(Class<?> on) {
819         super.findFluentSetters(on);
820         return this;
821      }
822
823      @Override /* Overridden from Context.Builder */
824      public HashKey hashKey() {
825         // @formatter:off
826         return HashKey.of(
827            super.hashKey(),
828            addBeanTypesHtml,
829            addKeyValueTableHeaders,
830            disableDetectLabelParameters,
831            disableDetectLinksInStrings,
832            labelParameter,
833            uriAnchorText
834         );
835         // @formatter:on
836      }
837
838      @Override /* Overridden from Builder */
839      public Builder ignoreInvocationExceptionsOnGetters() {
840         super.ignoreInvocationExceptionsOnGetters();
841         return this;
842      }
843
844      @Override /* Overridden from Builder */
845      public Builder ignoreInvocationExceptionsOnSetters() {
846         super.ignoreInvocationExceptionsOnSetters();
847         return this;
848      }
849
850      @Override /* Overridden from Builder */
851      public Builder ignoreRecursions() {
852         super.ignoreRecursions();
853         return this;
854      }
855
856      @Override /* Overridden from Builder */
857      public Builder ignoreRecursions(boolean value) {
858         super.ignoreRecursions(value);
859         return this;
860      }
861
862      @Override /* Overridden from Builder */
863      public Builder ignoreUnknownBeanProperties() {
864         super.ignoreUnknownBeanProperties();
865         return this;
866      }
867
868      @Override /* Overridden from Builder */
869      public Builder ignoreUnknownEnumValues() {
870         super.ignoreUnknownEnumValues();
871         return this;
872      }
873
874      @Override /* Overridden from Builder */
875      public Builder impl(Context value) {
876         super.impl(value);
877         return this;
878      }
879
880      @Override /* Overridden from Builder */
881      public Builder implClass(Class<?> interfaceClass, Class<?> implClass) {
882         super.implClass(interfaceClass, implClass);
883         return this;
884      }
885
886      @Override /* Overridden from Builder */
887      public Builder implClasses(Map<Class<?>,Class<?>> values) {
888         super.implClasses(values);
889         return this;
890      }
891
892      @Override /* Overridden from Builder */
893      public Builder initialDepth(int value) {
894         super.initialDepth(value);
895         return this;
896      }
897
898      @Override /* Overridden from Builder */
899      public Builder interfaceClass(Class<?> on, Class<?> value) {
900         super.interfaceClass(on, value);
901         return this;
902      }
903
904      @Override /* Overridden from Builder */
905      public Builder interfaces(java.lang.Class<?>...value) {
906         super.interfaces(value);
907         return this;
908      }
909
910      @Override /* Overridden from Builder */
911      public Builder keepNullProperties() {
912         super.keepNullProperties();
913         return this;
914      }
915
916      @Override /* Overridden from Builder */
917      public Builder keepNullProperties(boolean value) {
918         super.keepNullProperties(value);
919         return this;
920      }
921
922      /**
923       * <i><l>HtmlSerializer</l> configuration property:&emsp;</i>  Link label parameter name.
924       *
925       * <p>
926       * The parameter name to look for when resolving link labels}.
927       *
928       * @param value
929       *    The new value for this property.
930       *    <br>Cannot be <jk>null</jk>.
931       *    <br>The default is <js>"label"</js>.
932       * @return This object.
933       */
934      public Builder labelParameter(String value) {
935         labelParameter = assertArgNotNull("value", value);
936         return this;
937      }
938
939      @Override /* Overridden from Builder */
940      public Builder listener(Class<? extends org.apache.juneau.serializer.SerializerListener> value) {
941         super.listener(value);
942         return this;
943      }
944
945      @Override /* Overridden from Builder */
946      public Builder locale(Locale value) {
947         super.locale(value);
948         return this;
949      }
950
951      @Override /* Overridden from Builder */
952      public Builder maxDepth(int value) {
953         super.maxDepth(value);
954         return this;
955      }
956
957      @Override /* Overridden from Builder */
958      public Builder maxIndent(int value) {
959         super.maxIndent(value);
960         return this;
961      }
962
963      @Override /* Overridden from Builder */
964      public Builder mediaType(MediaType value) {
965         super.mediaType(value);
966         return this;
967      }
968
969      @Override /* Overridden from Builder */
970      public Builder namespaces(Namespace...values) {
971         super.namespaces(values);
972         return this;
973      }
974
975      @Override /* Overridden from Builder */
976      public Builder notBeanClasses(java.lang.Class<?>...values) {
977         super.notBeanClasses(values);
978         return this;
979      }
980
981      @Override /* Overridden from Builder */
982      public Builder notBeanPackages(String...values) {
983         super.notBeanPackages(values);
984         return this;
985      }
986
987      @Override /* Overridden from Builder */
988      public Builder ns() {
989         super.ns();
990         return this;
991      }
992
993      @Override /* Overridden from Builder */
994      public Builder produces(String value) {
995         super.produces(value);
996         return this;
997      }
998
999      @Override /* Overridden from Builder */
1000      public Builder propertyNamer(Class<?> on, Class<? extends org.apache.juneau.PropertyNamer> value) {
1001         super.propertyNamer(on, value);
1002         return this;
1003      }
1004
1005      @Override /* Overridden from Builder */
1006      public Builder propertyNamer(Class<? extends org.apache.juneau.PropertyNamer> value) {
1007         super.propertyNamer(value);
1008         return this;
1009      }
1010
1011      @Override /* Overridden from Builder */
1012      public Builder quoteChar(char value) {
1013         super.quoteChar(value);
1014         return this;
1015      }
1016
1017      @Override /* Overridden from Builder */
1018      public Builder quoteCharOverride(char value) {
1019         super.quoteCharOverride(value);
1020         return this;
1021      }
1022
1023      @Override /* Overridden from Builder */
1024      public Builder sortCollections() {
1025         super.sortCollections();
1026         return this;
1027      }
1028
1029      @Override /* Overridden from Builder */
1030      public Builder sortCollections(boolean value) {
1031         super.sortCollections(value);
1032         return this;
1033      }
1034
1035      @Override /* Overridden from Builder */
1036      public Builder sortMaps() {
1037         super.sortMaps();
1038         return this;
1039      }
1040
1041      @Override /* Overridden from Builder */
1042      public Builder sortMaps(boolean value) {
1043         super.sortMaps(value);
1044         return this;
1045      }
1046
1047      @Override /* Overridden from Builder */
1048      public Builder sortProperties() {
1049         super.sortProperties();
1050         return this;
1051      }
1052
1053      @Override /* Overridden from Builder */
1054      public Builder sortProperties(java.lang.Class<?>...on) {
1055         super.sortProperties(on);
1056         return this;
1057      }
1058
1059      @Override /* Overridden from Builder */
1060      public Builder sq() {
1061         super.sq();
1062         return this;
1063      }
1064
1065      @Override /* Overridden from Builder */
1066      public Builder stopClass(Class<?> on, Class<?> value) {
1067         super.stopClass(on, value);
1068         return this;
1069      }
1070
1071      @Override /* Overridden from Builder */
1072      public Builder streamCharset(Charset value) {
1073         super.streamCharset(value);
1074         return this;
1075      }
1076
1077      @Override /* Overridden from Builder */
1078      public <T,S> Builder swap(Class<T> normalClass, Class<S> swappedClass, ThrowingFunction<T,S> swapFunction) {
1079         super.swap(normalClass, swappedClass, swapFunction);
1080         return this;
1081      }
1082
1083      @Override /* Overridden from Builder */
1084      public <T,S> Builder swap(Class<T> normalClass, Class<S> swappedClass, ThrowingFunction<T,S> swapFunction, ThrowingFunction<S,T> unswapFunction) {
1085         super.swap(normalClass, swappedClass, swapFunction, unswapFunction);
1086         return this;
1087      }
1088
1089      @Override /* Overridden from Builder */
1090      public Builder swaps(Class<?>...values) {
1091         super.swaps(values);
1092         return this;
1093      }
1094
1095      @Override /* Overridden from Builder */
1096      public Builder swaps(Object...values) {
1097         super.swaps(values);
1098         return this;
1099      }
1100
1101      @Override /* Overridden from Builder */
1102      public Builder textNodeDelimiter(String value) {
1103         super.textNodeDelimiter(value);
1104         return this;
1105      }
1106
1107      @Override /* Overridden from Builder */
1108      public Builder timeZone(TimeZone value) {
1109         super.timeZone(value);
1110         return this;
1111      }
1112
1113      @Override /* Overridden from Builder */
1114      public Builder trimEmptyCollections() {
1115         super.trimEmptyCollections();
1116         return this;
1117      }
1118
1119      @Override /* Overridden from Builder */
1120      public Builder trimEmptyCollections(boolean value) {
1121         super.trimEmptyCollections(value);
1122         return this;
1123      }
1124
1125      @Override /* Overridden from Builder */
1126      public Builder trimEmptyMaps() {
1127         super.trimEmptyMaps();
1128         return this;
1129      }
1130
1131      @Override /* Overridden from Builder */
1132      public Builder trimEmptyMaps(boolean value) {
1133         super.trimEmptyMaps(value);
1134         return this;
1135      }
1136
1137      @Override /* Overridden from Builder */
1138      public Builder trimStrings() {
1139         super.trimStrings();
1140         return this;
1141      }
1142
1143      @Override /* Overridden from Builder */
1144      public Builder trimStrings(boolean value) {
1145         super.trimStrings(value);
1146         return this;
1147      }
1148
1149      @Override /* Overridden from Builder */
1150      public Builder type(Class<? extends org.apache.juneau.Context> value) {
1151         super.type(value);
1152         return this;
1153      }
1154
1155      @Override /* Overridden from Builder */
1156      public Builder typeName(Class<?> on, String value) {
1157         super.typeName(on, value);
1158         return this;
1159      }
1160
1161      @Override /* Overridden from Builder */
1162      public Builder typePropertyName(Class<?> on, String value) {
1163         super.typePropertyName(on, value);
1164         return this;
1165      }
1166
1167      @Override /* Overridden from Builder */
1168      public Builder typePropertyName(String value) {
1169         super.typePropertyName(value);
1170         return this;
1171      }
1172
1173      /**
1174       * <i><l>HtmlSerializer</l> configuration property:&emsp;</i>  Anchor text source.
1175       *
1176       * <p>
1177       * When creating anchor tags (e.g. <code><xt>&lt;a</xt> <xa>href</xa>=<xs>'...'</xs>
1178       * <xt>&gt;</xt>text<xt>&lt;/a&gt;</xt></code>) in HTML, this setting defines what to set the inner text to.
1179       *
1180       * <p>
1181       * The possible values are:
1182       * <ul>
1183       *    <li class='jc'>{@link AnchorText}
1184       *    <ul>
1185       *       <li class='jf'>{@link AnchorText#TO_STRING TO_STRING} (default) - Set to whatever is returned by {@link #toString()} on the object.
1186       *          <br>
1187       *          <h5 class='section'>Example:</h5>
1188       *          <p class='bjson'>
1189       *    <jc>// Our bean class with a URI property.</jc>
1190       *    <jk>public class</jk> MyBean {
1191       *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org?foo=bar#myAnchor"</js>);
1192       *    }
1193       *
1194       *    <jc>// Serializer with TO_STRING anchor text.</jc>
1195       *    WriterSerializer <jv>serializer1</jv> = HtmlSerializer.<jsm>create</jsm>().anchorText(<jsf>TO_STRING</jsf>).build();
1196       *
1197       *    <jc>// Produces: &lt;a href='http://www.apache.org?foo=bar#myAnchor'&gt;http://www.apache.org?foo=bar#myAnchor&lt;/a&gt;</jc>
1198       *    String <jv>html</jv> = <jv>serializer1</jv>.serialize(<jk>new</jk> MyBean());
1199       *          </p>
1200       *       <li class='jf'>{@link AnchorText#PROPERTY_NAME PROPERTY_NAME} - Set to the bean property name.
1201       *          <br>
1202       *          <h5 class='section'>Example:</h5>
1203       *          <p class='bjson'>
1204       *    <jc>// Our bean class with a URI property.</jc>
1205       *    <jk>public class</jk> MyBean {
1206       *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org?foo=bar#myAnchor"</js>);
1207       *    }
1208       *
1209       *    <jc>// Serializer with PROPERTY_NAME anchor text.</jc>
1210       *    WriterSerializer <jv>serializer1</jv> = HtmlSerializer.<jsm>create</jsm>().anchorText(<jsf>PROPERTY_NAME</jsf>).build();
1211       *
1212       *    <jc>// Produces: &lt;a href='http://www.apache.org?foo=bar#myAnchor'&gt;f1&lt;/a&gt;</jc>
1213       *    String <jv>html</jv> = <jv>serializer1</jv>.serialize(<jk>new</jk> MyBean());
1214       *          </p>
1215       *       <li class='jf'>{@link AnchorText#URI URI} - Set to the URI value.
1216       *          <br>
1217       *          <h5 class='section'>Example:</h5>
1218       *          <p class='bjson'>
1219       *    <jc>// Our bean class with a URI property.</jc>
1220       *    <jk>public class</jk> MyBean {
1221       *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org?foo=bar#myAnchor"</js>);
1222       *    }
1223       *
1224       *    <jc>// Serializer with URI anchor text.</jc>
1225       *    WriterSerializer <jv>serializer1</jv> = HtmlSerializer.<jsm>create</jsm>().anchorText(<jsf>URI</jsf>).build();
1226       *
1227       *    <jc>// Produces: &lt;a href='http://www.apache.org?foo=bar#myAnchor'&gt;http://www.apache.org?foo=bar&lt;/a&gt;</jc>
1228       *    String <jv>html</jv> = <jv>serializer1</jv>.serialize(<jk>new</jk> MyBean());
1229       *          </p>
1230       *       <li class='jf'>{@link AnchorText#LAST_TOKEN LAST_TOKEN} - Set to the last token of the URI value.
1231       *          <br>
1232       *          <h5 class='section'>Example:</h5>
1233       *          <p class='bjson'>
1234       *    <jc>// Our bean class with a URI property.</jc>
1235       *    <jk>public class</jk> MyBean {
1236       *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org/foo/bar?baz=qux#myAnchor"</js>);
1237       *    }
1238       *
1239       *    <jc>// Serializer with LAST_TOKEN anchor text.</jc>
1240       *    WriterSerializer <jv>serializer1</jv> = HtmlSerializer.<jsm>create</jsm>().anchorText(<jsf>LAST_TOKEN</jsf>).build();
1241       *
1242       *    <jc>// Produces: &lt;a href='http://www.apache.org/foo/bar?baz=qux#myAnchor'&gt;bar&lt;/a&gt;</jc>
1243       *    String <jv>html</jv> = <jv>serializer1</jv>.serialize(<jk>new</jk> MyBean());
1244       *          </p>
1245       *       <li class='jf'>{@link AnchorText#URI_ANCHOR URI_ANCHOR} - Set to the anchor of the URL.
1246       *          <br>
1247       *          <h5 class='section'>Example:</h5>
1248       *          <p class='bjson'>
1249       *    <jc>// Our bean class with a URI property.</jc>
1250       *    <jk>public class</jk> MyBean {
1251       *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org/foo/bar?baz=qux#myAnchor"</js>);
1252       *    }
1253       *
1254       *    <jc>// Serializer with URI_ANCHOR anchor text.</jc>
1255       *    WriterSerializer <jv>serializer1</jv> = HtmlSerializer.<jsm>create</jsm>().anchorText(<jsf>URI_ANCHOR</jsf>).build();
1256       *
1257       *    <jc>// Produces: &lt;a href='http://www.apache.org/foo/bar?baz=qux#myAnchor'&gt;myAnchor&lt;/a&gt;</jc>
1258       *    String <jv>html</jv> = <jv>serializer1</jv>.serialize(<jk>new</jk> MyBean());
1259       *          </p>
1260       *       <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.
1261       *          <br>
1262       *          <h5 class='section'>Example:</h5>
1263       *          <p class='bjson'>
1264       *    <jc>// Our bean class with a URI property.</jc>
1265       *    <jk>public class</jk> MyBean {
1266       *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"bar/baz"</js>);
1267       *    }
1268       *
1269       *    <jc>// Serializer with CONTEXT_RELATIVE anchor text.</jc>
1270       *    WriterSerializer <jv>serializer1</jv> = HtmlSerializer
1271       *       .<jsm>create</jsm>()
1272       *       .anchorText(<jsf>CONTEXT_RELATIVE</jsf>)
1273       *       .uriResolution(<jsf>ROOT_RELATIVE</jsf>)
1274       *       .uriRelativity(<jsf>RESOURCE</jsf>)
1275       *       .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>)
1276       *       .build();
1277       *
1278       *    <jc>// Produces: &lt;a href&#61;'/myContext/myServlet/bar/baz'&gt;myServlet/bar/baz&lt;/a&gt;</jc>
1279       *    String <jv>html</jv> = <jv>serializer1</jv>.serialize(<jk>new</jk> MyBean());
1280       *          </p>
1281       *       <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.
1282       *          <br>
1283       *          <h5 class='section'>Example:</h5>
1284       *          <p class='bjson'>
1285       *    <jc>// Our bean class with a URI property.</jc>
1286       *    <jk>public class</jk> MyBean {
1287       *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"bar/baz"</js>);
1288       *    }
1289       *
1290       *    <jc>// Serializer with SERVLET_RELATIVE anchor text.</jc>
1291       *    WriterSerializer <jv>serializer1</jv> = HtmlSerializer
1292       *       .<jsm>create</jsm>()
1293       *       .anchorText(<jsf>SERVLET_RELATIVE</jsf>)
1294       *       .uriResolution(<jsf>ROOT_RELATIVE</jsf>)
1295       *       .uriRelativity(<jsf>RESOURCE</jsf>)
1296       *       .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>)
1297       *       .build();
1298       *
1299       *    <jc>// Produces: &lt;a href&#61;'/myContext/myServlet/bar/baz'&gt;bar/baz&lt;/a&gt;</jc>
1300       *    String <jv>html</jv> = <jv>serializer1</jv>.serialize(<jk>new</jk> MyBean());
1301       *          </p>
1302       *       <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.
1303       *          <br>
1304       *          <h5 class='section'>Example:</h5>
1305       *          <p class='bjson'>
1306       *    <jc>// Our bean class with a URI property.</jc>
1307       *    <jk>public class</jk> MyBean {
1308       *       <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"bar/baz"</js>);
1309       *    }
1310       *
1311       *    <jc>// Serializer with PATH_RELATIVE anchor text.</jc>
1312       *    WriterSerializer <jv>serializer1</jv> = HtmlSerializer
1313       *       .<jsm>create</jsm>()
1314       *       .anchorText(<jsf>PATH_RELATIVE</jsf>)
1315       *       .uriResolution(<jsf>ROOT_RELATIVE</jsf>)
1316       *       .uriRelativity(<jsf>PATH_INFO</jsf>)
1317       *       .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>)
1318       *       .build();
1319       *
1320       *    <jc>// Produces: &lt;a href&#61;'/myContext/myServlet/foo/bar/baz'&gt;bar/baz&lt;/a&gt;</jc>
1321       *    String <jv>html</jv> = <jv>serializer1</jv>.serialize(<jk>new</jk> MyBean());
1322       *          </p>
1323       *    </ul>
1324       * </ul>
1325       *
1326       * @param value
1327       *    The new value for this property.
1328       *    <br>Cannot be <jk>null</jk>.
1329       *    <br>The default is {@link AnchorText#TO_STRING}.
1330       * @return This object.
1331       */
1332      public Builder uriAnchorText(AnchorText value) {
1333         uriAnchorText = assertArgNotNull("value", value);
1334         return this;
1335      }
1336
1337      @Override /* Overridden from Builder */
1338      public Builder uriContext(UriContext value) {
1339         super.uriContext(value);
1340         return this;
1341      }
1342
1343      @Override /* Overridden from Builder */
1344      public Builder uriRelativity(UriRelativity value) {
1345         super.uriRelativity(value);
1346         return this;
1347      }
1348
1349      @Override /* Overridden from Builder */
1350      public Builder uriResolution(UriResolution value) {
1351         super.uriResolution(value);
1352         return this;
1353      }
1354
1355      @Override /* Overridden from Builder */
1356      public Builder useEnumNames() {
1357         super.useEnumNames();
1358         return this;
1359      }
1360
1361      @Override /* Overridden from Builder */
1362      public Builder useJavaBeanIntrospector() {
1363         super.useJavaBeanIntrospector();
1364         return this;
1365      }
1366
1367      @Override /* Overridden from Builder */
1368      public Builder useWhitespace() {
1369         super.useWhitespace();
1370         return this;
1371      }
1372
1373      @Override /* Overridden from Builder */
1374      public Builder useWhitespace(boolean value) {
1375         super.useWhitespace(value);
1376         return this;
1377      }
1378
1379      @Override /* Overridden from Builder */
1380      public Builder ws() {
1381         super.ws();
1382         return this;
1383      }
1384   }
1385
1386   /** Default serializer, single quotes. */
1387   public static class Sq extends HtmlSerializer {
1388
1389      /**
1390       * Constructor.
1391       *
1392       * @param builder The builder for this object.
1393       */
1394      public Sq(Builder builder) {
1395         super(builder.quoteChar('\''));
1396      }
1397   }
1398
1399   /** Default serializer, single quotes, whitespace added. */
1400   public static class SqReadable extends HtmlSerializer {
1401
1402      /**
1403       * Constructor.
1404       *
1405       * @param builder The builder for this object.
1406       */
1407      public SqReadable(Builder builder) {
1408         super(builder.quoteChar('\'').useWhitespace());
1409      }
1410   }
1411
1412   /** Default serializer, all default settings. */
1413   public static final HtmlSerializer DEFAULT = new HtmlSerializer(create());
1414
1415   /** Default serializer, single quotes. */
1416   public static final HtmlSerializer DEFAULT_SQ = new HtmlSerializer.Sq(create());
1417   /** Default serializer, single quotes, whitespace added. */
1418   public static final HtmlSerializer DEFAULT_SQ_READABLE = new HtmlSerializer.SqReadable(create());
1419
1420   /** Default serializer, single quotes, simplified (no JSON type tags on strings). */
1421   public static final HtmlSerializer DEFAULT_SIMPLE_SQ = new HtmlSerializer(create().sq().disableJsonTags());
1422
1423   /**
1424    * Creates a new builder for this object.
1425    *
1426    * @return A new builder.
1427    */
1428   public static Builder create() {
1429      return new Builder();
1430   }
1431
1432   protected final boolean addBeanTypesHtml;
1433   protected final boolean addKeyValueTableHeaders;
1434   protected final boolean detectLabelParameters;
1435   protected final boolean detectLinksInStrings;
1436   protected final AnchorText uriAnchorText;
1437   protected final String labelParameter;
1438
1439   private final Map<ClassMeta<?>,HtmlClassMeta> htmlClassMetas = new ConcurrentHashMap<>();
1440   private final Map<BeanPropertyMeta,HtmlBeanPropertyMeta> htmlBeanPropertyMetas = new ConcurrentHashMap<>();
1441
1442   private final AtomicReference<HtmlSchemaSerializer> schemaSerializer = new AtomicReference<>();
1443
1444   /**
1445    * Constructor.
1446    *
1447    * @param builder The builder for this object.
1448    */
1449   public HtmlSerializer(Builder builder) {
1450      super(builder);
1451      addBeanTypesHtml = builder.addBeanTypesHtml;
1452      addKeyValueTableHeaders = builder.addKeyValueTableHeaders;
1453      detectLabelParameters = ! builder.disableDetectLabelParameters;
1454      detectLinksInStrings = ! builder.disableDetectLinksInStrings;
1455      labelParameter = builder.labelParameter;
1456      uriAnchorText = builder.uriAnchorText;
1457   }
1458
1459   @Override /* Overridden from Context */
1460   public Builder copy() {
1461      return new Builder(this);
1462   }
1463
1464   @Override /* Overridden from Context */
1465   public HtmlSerializerSession.Builder createSession() {
1466      return HtmlSerializerSession.create(this);
1467   }
1468
1469   @Override /* Overridden from HtmlMetaProvider */
1470   public HtmlBeanPropertyMeta getHtmlBeanPropertyMeta(BeanPropertyMeta bpm) {
1471      if (bpm == null)
1472         return HtmlBeanPropertyMeta.DEFAULT;
1473      HtmlBeanPropertyMeta m = htmlBeanPropertyMetas.get(bpm);
1474      if (m == null) {
1475         m = new HtmlBeanPropertyMeta(bpm.getDelegateFor(), this.getAnnotationProvider(), this);
1476         htmlBeanPropertyMetas.put(bpm, m);
1477      }
1478      return m;
1479   }
1480
1481   @Override /* Overridden from HtmlMetaProvider */
1482   public HtmlClassMeta getHtmlClassMeta(ClassMeta<?> cm) {
1483      HtmlClassMeta m = htmlClassMetas.get(cm);
1484      if (m == null) {
1485         m = new HtmlClassMeta(cm, this);
1486         htmlClassMetas.put(cm, m);
1487      }
1488      return m;
1489   }
1490
1491   /**
1492    * Returns the schema serializer.
1493    *
1494    * @return The schema serializer.
1495    */
1496   public HtmlSerializer getSchemaSerializer() {
1497      HtmlSchemaSerializer result = schemaSerializer.get();
1498      if (result == null) {
1499         result = HtmlSchemaSerializer.create().beanContext(getBeanContext()).build();
1500         if (! schemaSerializer.compareAndSet(null, result)) {
1501            result = schemaSerializer.get();
1502         }
1503      }
1504      return result;
1505   }
1506
1507   @Override /* Overridden from Context */
1508   public HtmlSerializerSession getSession() { return createSession().build(); }
1509
1510   /**
1511    * Link label parameter name.
1512    *
1513    * @see Builder#labelParameter(String)
1514    * @return
1515    *    The parameter name to look for when resolving link labels.
1516    */
1517   protected final String getLabelParameter() { return labelParameter; }
1518
1519   /**
1520    * Anchor text source.
1521    *
1522    * @see Builder#uriAnchorText(AnchorText)
1523    * @return
1524    *    When creating anchor tags (e.g. <code><xt>&lt;a</xt> <xa>href</xa>=<xs>'...'</xs>
1525    *    <xt>&gt;</xt>text<xt>&lt;/a&gt;</xt></code>) in HTML, this setting defines what to set the inner text to.
1526    */
1527   protected final AnchorText getUriAnchorText() { return uriAnchorText; }
1528
1529   /**
1530    * Add <js>"_type"</js> properties when needed.
1531    *
1532    * @see Builder#addBeanTypesHtml()
1533    * @return
1534    *    <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred
1535    *    through reflection.
1536    */
1537   @Override
1538   protected final boolean isAddBeanTypes() { return addBeanTypesHtml || super.isAddBeanTypes(); }
1539
1540   /**
1541    * Add key/value headers on bean/map tables.
1542    *
1543    * @see Builder#addKeyValueTableHeaders()
1544    * @return
1545    *    <jk>true</jk> if <bc>key</bc> and <bc>value</bc> column headers are added to tables.
1546    */
1547   protected final boolean isAddKeyValueTableHeaders() { return addKeyValueTableHeaders; }
1548
1549   /**
1550    * Look for link labels in URIs.
1551    *
1552    * @see Builder#disableDetectLabelParameters()
1553    * @return
1554    *    <jk>true</jk> if we should look for URL label parameters (e.g. <js>"?label=foobar"</js>).
1555    */
1556   protected final boolean isDetectLabelParameters() { return detectLabelParameters; }
1557
1558   /**
1559    * Look for URLs in {@link String Strings}.
1560    *
1561    * @see Builder#disableDetectLinksInStrings()
1562    * @return
1563    *    <jk>true</jk> if we should automatically convert strings to URLs if they look like a URL.
1564    */
1565   protected final boolean isDetectLinksInStrings() { return detectLinksInStrings; }
1566
1567   @Override /* Overridden from XmlSerializer */
1568   protected FluentMap<String,Object> properties() {
1569      return super.properties()
1570         .a("addBeanTypesHtml", addBeanTypesHtml)
1571         .a("addKeyValueTableHeaders", addKeyValueTableHeaders)
1572         .a("detectLabelParameters", detectLabelParameters)
1573         .a("detectLinksInStrings", detectLinksInStrings)
1574         .a("labelParameter", labelParameter)
1575         .a("uriAnchorText", uriAnchorText);
1576   }
1577}