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.collections.JsonMap.*;
020import static org.apache.juneau.common.utils.ThrowableUtils.*;
021import static org.apache.juneau.common.utils.Utils.*;
022import static org.apache.juneau.internal.CollectionUtils.*;
023
024import java.lang.annotation.*;
025import java.nio.charset.*;
026import java.util.*;
027import java.util.function.*;
028import java.util.regex.*;
029
030import org.apache.juneau.*;
031import org.apache.juneau.collections.*;
032import org.apache.juneau.common.utils.*;
033import org.apache.juneau.internal.*;
034import org.apache.juneau.svl.*;
035import org.apache.juneau.utils.*;
036import org.apache.juneau.xml.*;
037
038/**
039 * Serializes POJOs to HTTP responses as HTML documents.
040 *
041 * <h5 class='topic'>Media types</h5>
042 * <p>
043 * Handles <c>Accept</c> types:  <bc>text/html</bc>
044 * <p>
045 * Produces <c>Content-Type</c> types:  <bc>text/html</bc>
046 *
047 * <h5 class='topic'>Description</h5>
048 * <p>
049 * Same as {@link HtmlSerializer}, except wraps the response in <code><xt>&lt;html&gt;</code>,
050 * <code><xt>&lt;head&gt;</code>, and <code><xt>&lt;body&gt;</code> tags so that it can be rendered in a browser.
051 *
052 * <p>
053 * Configurable properties are typically specified via <ja>@HtmlDocConfig</ja>.
054 *
055 * <h5 class='section'>Example:</h5>
056 * <p class='bjava'>
057 *    <ja>@Rest</ja>(
058 *       messages=<js>"nls/AddressBookResource"</js>,
059 *       title=<js>"$L{title}"</js>,
060 *       description=<js>"$L{description}"</js>
061 *    )
062 *    <ja>@HtmlDocConfig</ja>(
063 *       navlinks={
064 *          <js>"api: servlet:/api"</js>,
065 *          <js>"doc: doc"</js>
066 *       }
067 *    )
068 *    <jk>public class</jk> AddressBookResource <jk>extends</jk> BasicRestServlet {
069 * </p>
070 *
071 * <p>
072 * The <c>$L{...}</c> variable represent localized strings pulled from the resource bundle identified by the
073 * <c>messages</c> annotation.
074 * <br>These variables are replaced at runtime based on the HTTP request locale.
075 * <br>Several built-in runtime variable types are defined, and the API can be extended to include user-defined variables.
076 *
077 * <h5 class='section'>Notes:</h5><ul>
078 *    <li class='note'>This class is thread safe and reusable.
079 * </ul>
080 *
081 * <h5 class='section'>See Also:</h5><ul>
082 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/HtmlBasics">HTML Basics</a>
083
084 * </ul>
085 */
086public class HtmlDocSerializer extends HtmlStrippedDocSerializer {
087
088   //-------------------------------------------------------------------------------------------------------------------
089   // Static
090   //-------------------------------------------------------------------------------------------------------------------
091
092   private static final String[] EMPTY_ARRAY = {};
093
094   /** Default serializer, all default settings. */
095   public static final HtmlDocSerializer DEFAULT = new HtmlDocSerializer(create());
096
097   /**
098    * Creates a new builder for this object.
099    *
100    * @return A new builder.
101    */
102   public static Builder create() {
103      return new Builder();
104   }
105
106   //-------------------------------------------------------------------------------------------------------------------
107   // Builder
108   //-------------------------------------------------------------------------------------------------------------------
109
110   /**
111    * Builder class.
112    */
113   public static class Builder extends HtmlStrippedDocSerializer.Builder {
114
115      private static final Cache<HashKey,HtmlDocSerializer> CACHE = Cache.of(HashKey.class, HtmlDocSerializer.class).build();
116
117      List<String> aside, footer, head, header, nav, navlinks, script, style, stylesheet;
118      AsideFloat asideFloat;
119      String noResultsMessage;
120      boolean nowrap, resolveBodyVars;
121      Class<? extends HtmlDocTemplate> template;
122      List<Class<? extends HtmlWidget>> widgets;
123
124      /**
125       * Constructor, default settings.
126       */
127      protected Builder() {
128         produces("text/html");
129         accept("text/html");
130         asideFloat = AsideFloat.RIGHT;
131         noResultsMessage = "<p>no results</p>";
132         template = BasicHtmlDocTemplate.class;
133      }
134
135      /**
136       * Copy constructor.
137       *
138       * @param copyFrom The bean to copy from.
139       */
140      protected Builder(HtmlDocSerializer copyFrom) {
141         super(copyFrom);
142         aside = copy(copyFrom.aside);
143         footer = copy(copyFrom.footer);
144         head = copy(copyFrom.head);
145         header = copy(copyFrom.header);
146         nav = copy(copyFrom.nav);
147         navlinks = copy(copyFrom.navlinks);
148         script = copy(copyFrom.script);
149         style = copy(copyFrom.style);
150         stylesheet = copy(copyFrom.stylesheet);
151         asideFloat = copyFrom.asideFloat;
152         noResultsMessage = copyFrom.noResultsMessage;
153         nowrap = copyFrom.nowrap;
154         resolveBodyVars = copyFrom.resolveBodyVars;
155         template = copyFrom.template;
156         widgets = copy(copyFrom.widgets);
157      }
158
159      /**
160       * Copy constructor.
161       *
162       * @param copyFrom The builder to copy from.
163       */
164      protected Builder(Builder copyFrom) {
165         super(copyFrom);
166         aside = copy(copyFrom.aside);
167         footer = copy(copyFrom.footer);
168         head = copy(copyFrom.head);
169         header = copy(copyFrom.header);
170         nav = copy(copyFrom.nav);
171         navlinks = copy(copyFrom.navlinks);
172         script = copy(copyFrom.script);
173         style = copy(copyFrom.style);
174         stylesheet = copy(copyFrom.stylesheet);
175         asideFloat = copyFrom.asideFloat;
176         noResultsMessage = copyFrom.noResultsMessage;
177         nowrap = copyFrom.nowrap;
178         resolveBodyVars = copyFrom.resolveBodyVars;
179         template = copyFrom.template;
180         widgets = copy(copyFrom.widgets);
181      }
182
183      @Override /* Context.Builder */
184      public Builder copy() {
185         return new Builder(this);
186      }
187
188      @Override /* Context.Builder */
189      public HtmlDocSerializer build() {
190         return cache(CACHE).build(HtmlDocSerializer.class);
191      }
192
193      @Override /* Context.Builder */
194      public HashKey hashKey() {
195         return HashKey.of(
196            super.hashKey(),
197            aside,
198            footer,
199            head,
200            header,
201            nav,
202            navlinks,
203            script,
204            style,
205            stylesheet,
206            asideFloat,
207            noResultsMessage,
208            nowrap,
209            resolveBodyVars,
210            template,
211            widgets
212         );
213      }
214
215      //-----------------------------------------------------------------------------------------------------------------
216      // Properties
217      //-----------------------------------------------------------------------------------------------------------------
218
219      /**
220       * Aside section contents.
221       *
222       * <p>
223       * Allows you to specify the contents of the aside section on the HTML page.
224       * The aside section floats on the right of the page for providing content supporting the serialized content of
225       * the page.
226       *
227       * <p>
228       * By default, the aside section is empty.
229       *
230       * <h5 class='section'>Example:</h5>
231       * <p class='bjava'>
232       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
233       *       .<jsm>create</jsm>()
234       *       .aside(
235       *          <js>"&lt;ul&gt;"</js>,
236       *          <js>" &lt;li&gt;Item 1"</js>,
237       *          <js>" &lt;li&gt;Item 2"</js>,
238       *          <js>" &lt;li&gt;Item 3"</js>,
239       *          <js>"&lt;/ul&gt;"</js>
240       *       )
241       *       .build();
242       * </p>
243       *
244       * <h5 class='section'>Notes:</h5><ul>
245       *    <li class='note'>
246       *       Format: HTML
247       *    <li class='note'>
248       *       Supports <a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerSvlVariables">SVL Variables</a>
249       *       (e.g. <js>"$L{my.localized.variable}"</js>).
250       *    <li class='note'>
251       *       A value of <js>"NONE"</js> can be used to force no value.
252       *    <li class='note'>
253       *       The parent value can be included by adding the literal <js>"INHERIT"</js> as a value.
254       *    <li class='note'>
255       *       Multiple values are combined with newlines into a single string.
256       *    <li class='note'>
257       *       On methods, this value is inherited from the <ja>@HtmlDocConfig</ja> annotation on the servlet/resource class.
258       *    <li class='note'>
259       *       On servlet/resource classes, this value is inherited from the <ja>@HtmlDocConfig</ja> annotation on the
260       *       parent class.
261       * </ul>
262       *
263       * @param value
264       *    The new value for this property.
265       * @return This object.
266       */
267      public Builder aside(String...value) {
268         aside = merge(aside, value);
269         return this;
270      }
271
272      /**
273       * Returns the list of aside section contents.
274       *
275       * <p>
276       * Gives access to the inner list if you need to make more than simple additions via {@link #aside(String...)}.
277       *
278       * @return The list of aside section contents.
279       * @see #aside(String...)
280       */
281      public List<String> aside() {
282         if (aside == null)
283            aside = list();
284         return aside;
285      }
286
287      /**
288       * Float aside section contents.
289       *
290       * <p>
291       * Allows you to position the aside contents of the page around the main contents.
292       *
293       * <p>
294       * By default, the aside section is floated to the right.
295       *
296       * <h5 class='section'>Example:</h5>
297       * <p class='bjava'>
298       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
299       *       .<jsm>create</jsm>()
300       *       .aside(
301       *          <js>"&lt;ul&gt;"</js>,
302       *          <js>" &lt;li&gt;Item 1"</js>,
303       *          <js>" &lt;li&gt;Item 2"</js>,
304       *          <js>" &lt;li&gt;Item 3"</js>,
305       *          <js>"&lt;/ul&gt;"</js>
306       *       )
307       *       .asideFloat(<jsf>RIGHT</jsf>)
308       *       .build();
309       * </p>
310       *
311       * @param value
312       *    The new value for this property.
313       * @return This object.
314       */
315      public Builder asideFloat(AsideFloat value) {
316         asideFloat = value;
317         return this;
318      }
319
320      /**
321       * Footer section contents.
322       *
323       * <p>
324       * Allows you to specify the contents of the footer section on the HTML page.
325       *
326       * <p>
327       * By default, the footer section is empty.
328       *
329       * <h5 class='section'>Example:</h5>
330       * <p class='bjava'>
331       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
332       *       .<jsm>create</jsm>()
333       *       .footer(
334       *          <js>"&lt;b&gt;This interface is great!&lt;/b&gt;"</js>
335       *       )
336       *       .build();
337       * </p>
338       *
339       * @param value
340       *    The new value for this property.
341       * @return This object.
342       */
343      public Builder footer(String...value) {
344         footer = merge(footer, value);
345         return this;
346      }
347
348      /**
349       * Returns the list of footer section contents.
350       *
351       * <p>
352       * Gives access to the inner list if you need to make more than simple additions via {@link #footer(String...)}.
353       *
354       * @return The list of footer section contents.
355       * @see #footer(String...)
356       */
357      public List<String> footer() {
358         if (footer == null)
359            footer = list();
360         return footer;
361      }
362
363      /**
364       * Additional head section content.
365       *
366       * <p>
367       * Adds the specified HTML content to the head section of the page.
368       *
369       * <h5 class='section'>Example:</h5>
370       * <p class='bjava'>
371       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
372       *       .<jsm>create</jsm>()
373       *       .head(
374       *          <js>"&lt;link rel='icon' href='$U{servlet:/htdocs/mypageicon.ico}'&gt;"</js>
375       *       )
376       *       .build();
377       * </p>
378       *
379       * @param value
380       *    The new value for this property.
381       * @return This object.
382       */
383      public Builder head(String...value) {
384         head = merge(head, value);
385         return this;
386      }
387
388      /**
389       * Returns the list of head section contents.
390       *
391       * <p>
392       * Gives access to the inner list if you need to make more than simple additions via {@link #head(String...)}.
393       *
394       * @return The list of head section contents.
395       * @see #head(String...)
396       */
397      public List<String> head() {
398         if (head == null)
399            head = list();
400         return head;
401      }
402
403      /**
404       * Header section contents.
405       *
406       * <p>
407       * Allows you to override the contents of the header section on the HTML page.
408       * The header section normally contains the title and description at the top of the page.
409       *
410       * <h5 class='section'>Example:</h5>
411       * <p class='bjava'>
412       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
413       *       .<jsm>create</jsm>()
414       *       .header(
415       *          <js>"&lt;h1&gt;My own header&lt;/h1&gt;"</js>
416       *       )
417       *       .build()
418       * </p>
419       *
420       * @param value
421       *    The new value for this property.
422       * @return This object.
423       */
424      public Builder header(String...value) {
425         header = merge(header, value);
426         return this;
427      }
428
429      /**
430       * Returns the list of header section contents.
431       *
432       * <p>
433       * Gives access to the inner list if you need to make more than simple additions via {@link #header(String...)}.
434       *
435       * @return The list of header section contents.
436       * @see #header(String...)
437       */
438      public List<String> header() {
439         if (header == null)
440            header = list();
441         return header;
442      }
443
444      /**
445       * Nav section contents.
446       *
447       * <p>
448       * Allows you to override the contents of the nav section on the HTML page.
449       * The nav section normally contains the page links at the top of the page.
450       *
451       * <h5 class='section'>Example:</h5>
452       * <p class='bjava'>
453       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
454       *       .<jsm>create</jsm>()
455       *       .nav(
456       *          <js>"&lt;p class='special-navigation'&gt;This is my special navigation content&lt;/p&gt;"</js>
457       *       )
458       *       .build()
459       * </p>
460       *
461       * <p>
462       * When this property is specified, the {@link Builder#navlinks(String...)} property is ignored.
463       *
464       * @param value
465       *    The new value for this property.
466       * @return This object.
467       */
468      public Builder nav(String...value) {
469         nav = merge(nav, value);
470         return this;
471      }
472
473      /**
474       * Returns the list of nav section contents.
475       *
476       * <p>
477       * Gives access to the inner list if you need to make more than simple additions via {@link #nav(String...)}.
478       *
479       * @return The list of nav section contents.
480       * @see #nav(String...)
481       */
482      public List<String> nav() {
483         if (nav == null)
484            nav = list();
485         return nav;
486      }
487
488      /**
489       * Page navigation links.
490       *
491       * <p>
492       * Adds a list of hyperlinks immediately under the title and description but above the content of the page.
493       *
494       * <p>
495       * This can be used to provide convenient hyperlinks when viewing the REST interface from a browser.
496       *
497       * <p>
498       * The value is an array of strings with two possible values:
499       * <ul>
500       *    <li>A key-value pair representing a hyperlink label and href:
501       *       <br><js>"google: http://google.com"</js>
502       *    <li>Arbitrary HTML.
503       * </ul>
504       *
505       * <p>
506       * Relative URLs are considered relative to the servlet path.
507       * For example, if the servlet path is <js>"http://localhost/myContext/myServlet"</js>, and the
508       * URL is <js>"foo"</js>, the link becomes <js>"http://localhost/myContext/myServlet/foo"</js>.
509       * Absolute (<js>"/myOtherContext/foo"</js>) and fully-qualified (<js>"http://localhost2/foo"</js>) URLs
510       * can also be used in addition to various other protocols specified by {@link UriResolver} such as
511       * <js>"servlet:/..."</js>.
512       *
513       * <h5 class='section'>Example:</h5>
514       * <p class='bjava'>
515       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
516       *       .<jsm>create</jsm>()
517       *       .navlinks(
518       *          <js>"api: servlet:/api"</js>,
519       *          <js>"stats: servlet:/stats"</js>,
520       *          <js>"doc: doc"</js>
521       *       )
522       *       .build();
523       * </p>
524       *
525       * @param value
526       *    The new value for this property.
527       * @return This object.
528       */
529      public Builder navlinks(String...value) {
530         navlinks = mergeNavLinks(navlinks, value);
531         return this;
532      }
533
534      /**
535       * Returns the list of navlinks section contents.
536       *
537       * <p>
538       * Gives access to the inner list if you need to make more than simple additions via {@link #navlinks(String...)}.
539       *
540       * @return The list of navlinks section contents.
541       * @see #navlinks(String...)
542       */
543      public List<String> navlinks() {
544         if (navlinks == null)
545            navlinks = list();
546         return navlinks;
547      }
548
549      /**
550       * No-results message.
551       *
552       * <p>
553       * Allows you to specify the string message used when trying to serialize an empty array or empty list.
554       *
555       * <h5 class='section'>Example:</h5>
556       * <p class='bjava'>
557       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
558       *       .<jsm>create</jsm>()
559       *       .noResultsMessage(<js>"&lt;b&gt;This interface is great!&lt;/b&gt;"</js>)
560       *       .build();
561       * </p>
562       *
563       * <p>
564       * A value of <js>"NONE"</js> can be used to represent no value to differentiate it from an empty string.
565       *
566       * @param value
567       *    The new value for this property.
568       * @return This object.
569       */
570      public Builder noResultsMessage(String value) {
571         noResultsMessage = value;
572         return this;
573      }
574
575      /**
576       * Prevent word wrap on page.
577       *
578       * <p>
579       * Adds <js>"* {white-space:nowrap}"</js> to the CSS instructions on the page to prevent word wrapping.
580       *
581       * @return This object.
582       */
583      public Builder nowrap() {
584         return nowrap(true);
585      }
586
587      /**
588       * Same as {@link #nowrap()} but allows you to explicitly specify the boolean value.
589       *
590       * @param value
591       *    The new value for this property.
592       * @return This object.
593       * @see #nowrap()
594       */
595      public Builder nowrap(boolean value) {
596         nowrap = value;
597         return this;
598      }
599
600      /**
601       * Resolve $ variables in serialized POJO.
602       *
603       * @return This object.
604       */
605      public Builder resolveBodyVars() {
606         return resolveBodyVars(true);
607      }
608
609      /**
610       * Same as {@link #resolveBodyVars()} but allows you to explicitly specify the boolean value.
611       *
612       * @param value
613       *    The new value for this property.
614       * @return This object.
615       * @see #nowrap()
616       */
617      public Builder resolveBodyVars(boolean value) {
618         resolveBodyVars = value;
619         return this;
620      }
621
622      /**
623       * Adds the specified Javascript code to the HTML page.
624       *
625       * <p>
626       * A shortcut on <ja>@Rest</ja> is also provided for this setting:
627       * <p class='bjava'>
628       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
629       *       .<jsm>create</jsm>()
630       *       .script(<js>"alert('hello!');"</js>)
631       *       .build();
632       * </p>
633       *
634       * @param value
635       *    The value to add to this property.
636       * @return This object.
637       */
638      public Builder script(String...value) {
639         script = merge(script, value);
640         return this;
641      }
642
643      /**
644       * Returns the list of page script contents.
645       *
646       * <p>
647       * Gives access to the inner list if you need to make more than simple additions via {@link #script(String...)}.
648       *
649       * @return The list of page script contents.
650       * @see #script(String...)
651       */
652      public List<String> script() {
653         if (script == null)
654            script = list();
655         return script;
656      }
657
658      /**
659       * Adds the specified CSS instructions to the HTML page.
660       *
661       * <p class='bjava'>
662       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
663       *       .<jsm>create</jsm>()
664       *       .style(
665       *          <js>"h3 { color: red; }"</js>,
666       *          <js>"h5 { font-weight: bold; }"</js>
667       *       )
668       *       .build();
669       * </p>
670       *
671       * @param value
672       *    The value to add to this property.
673       * @return This object.
674       */
675      public Builder style(String...value) {
676         style = merge(style, value);
677         return this;
678      }
679
680      /**
681       * Returns the list of page style contents.
682       *
683       * <p>
684       * Gives access to the inner list if you need to make more than simple additions via {@link #style(String...)}.
685       *
686       * @return The list of page style contents.
687       * @see #style(String...)
688       */
689      public List<String> style() {
690         if (style == null)
691            style = list();
692         return style;
693      }
694
695      /**
696       * Adds to the list of stylesheet URLs.
697       *
698       * <p>
699       * Note that this stylesheet is controlled by the <code><ja>@Rest</ja>.stylesheet()</code> annotation.
700       *
701       * @param value
702       *    The value to add to this property.
703       * @return This object.
704       */
705      public Builder stylesheet(String...value) {
706         stylesheet = merge(stylesheet, value);
707         return this;
708      }
709
710      /**
711       * Returns the list of stylesheet URLs.
712       *
713       * <p>
714       * Gives access to the inner list if you need to make more than simple additions via {@link #stylesheet(String...)}.
715       *
716       * @return The list of stylesheet URLs.
717       * @see #stylesheet(String...)
718       */
719      public List<String> stylesheet() {
720         if (stylesheet == null)
721            stylesheet = list();
722         return stylesheet;
723      }
724
725      /**
726       * HTML document template.
727       *
728       * <p>
729       * Specifies the template to use for serializing the page.
730       *
731       * <p>
732       * By default, the {@link BasicHtmlDocTemplate} class is used to construct the contents of the HTML page, but
733       * can be overridden with your own custom implementation class.
734       *
735       * <h5 class='section'>Example:</h5>
736       * <p class='bjava'>
737       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
738       *       .<jsm>create</jsm>()
739       *       .template(MySpecialDocTemplate.<jk>class</jk>)
740       *       .build();
741       * </p>
742       *
743       * @param value
744       *    The new value for this property.
745       * @return This object.
746       */
747      public Builder template(Class<? extends HtmlDocTemplate> value) {
748         template = value;
749         return this;
750      }
751
752      /**
753       * HTML Widgets.
754       *
755       * <p>
756       * Defines widgets that can be used in conjunction with string variables of the form <js>"$W{name}"</js>to quickly
757       * generate arbitrary replacement text.
758       *
759       * Widgets resolve the following variables:
760       * <ul class='spaced-list'>
761       *    <li><js>"$W{name}"</js> - Contents returned by {@link HtmlWidget#getHtml(VarResolverSession)}.
762       *    <li><js>"$W{name.script}"</js> - Contents returned by {@link HtmlWidget#getScript(VarResolverSession)}.
763       *       <br>The script contents are automatically inserted into the <xt>&lt;head/script&gt;</xt> section
764       *           in the HTML page.
765       *    <li><js>"$W{name.style}"</js> - Contents returned by {@link HtmlWidget#getStyle(VarResolverSession)}.
766       *       <br>The styles contents are automatically inserted into the <xt>&lt;head/style&gt;</xt> section
767       *           in the HTML page.
768       * </ul>
769       *
770       * <p>
771       * The following examples shows how to associate a widget with a REST method and then have it rendered in the links
772       * and aside section of the page:
773       *
774       * <p class='bjava'>
775       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
776       *       .<jsm>create</jsm>()
777       *       .widgets(
778       *          MyWidget.<jk>class</jk>
779       *       )
780       *       .navlinks(
781       *          <js>"$W{MyWidget}"</js>
782       *       )
783       *       .aside(
784       *          <js>"Check out this widget:  $W{MyWidget}"</js>
785       *       )
786       *       .build();
787       * </p>
788       *
789       * <h5 class='section'>Notes:</h5><ul>
790       *    <li class='note'>
791       *       Widgets are inherited from super classes, but can be overridden by reusing the widget name.
792       * </ul>
793       *
794       * <h5 class='section'>See Also:</h5><ul>
795       *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/HtmlWidgets">Widgets</a>
796       * </ul>
797       *
798       * @param values The values to add to this setting.
799       * @return This object.
800       */
801      @SuppressWarnings("unchecked")
802      public Builder widgets(Class<? extends HtmlWidget>...values) {
803         addAll(widgets(), values);
804         return this;
805      }
806
807      /**
808       * Returns the list of page widgets.
809       *
810       * <p>
811       * Gives access to the inner list if you need to make more than simple additions via {@link #widgets(Class...)}.
812       *
813       * @return The list of page widgets.
814       * @see #widgets(Class...)
815       */
816      public List<Class<? extends HtmlWidget>> widgets() {
817         if (widgets == null)
818            widgets = list();
819         return widgets;
820      }
821      @Override /* Overridden from Builder */
822      public Builder annotations(Annotation...values) {
823         super.annotations(values);
824         return this;
825      }
826
827      @Override /* Overridden from Builder */
828      public Builder apply(AnnotationWorkList work) {
829         super.apply(work);
830         return this;
831      }
832
833      @Override /* Overridden from Builder */
834      public Builder applyAnnotations(Object...from) {
835         super.applyAnnotations(from);
836         return this;
837      }
838
839      @Override /* Overridden from Builder */
840      public Builder applyAnnotations(Class<?>...from) {
841         super.applyAnnotations(from);
842         return this;
843      }
844
845      @Override /* Overridden from Builder */
846      public Builder cache(Cache<HashKey,? extends org.apache.juneau.Context> value) {
847         super.cache(value);
848         return this;
849      }
850
851      @Override /* Overridden from Builder */
852      public Builder debug() {
853         super.debug();
854         return this;
855      }
856
857      @Override /* Overridden from Builder */
858      public Builder debug(boolean value) {
859         super.debug(value);
860         return this;
861      }
862
863      @Override /* Overridden from Builder */
864      public Builder impl(Context value) {
865         super.impl(value);
866         return this;
867      }
868
869      @Override /* Overridden from Builder */
870      public Builder type(Class<? extends org.apache.juneau.Context> value) {
871         super.type(value);
872         return this;
873      }
874
875      @Override /* Overridden from Builder */
876      public Builder beanClassVisibility(Visibility value) {
877         super.beanClassVisibility(value);
878         return this;
879      }
880
881      @Override /* Overridden from Builder */
882      public Builder beanConstructorVisibility(Visibility value) {
883         super.beanConstructorVisibility(value);
884         return this;
885      }
886
887      @Override /* Overridden from Builder */
888      public Builder beanContext(BeanContext value) {
889         super.beanContext(value);
890         return this;
891      }
892
893      @Override /* Overridden from Builder */
894      public Builder beanContext(BeanContext.Builder value) {
895         super.beanContext(value);
896         return this;
897      }
898
899      @Override /* Overridden from Builder */
900      public Builder beanDictionary(java.lang.Class<?>...values) {
901         super.beanDictionary(values);
902         return this;
903      }
904
905      @Override /* Overridden from Builder */
906      public Builder beanFieldVisibility(Visibility value) {
907         super.beanFieldVisibility(value);
908         return this;
909      }
910
911      @Override /* Overridden from Builder */
912      public Builder beanInterceptor(Class<?> on, Class<? extends org.apache.juneau.swap.BeanInterceptor<?>> value) {
913         super.beanInterceptor(on, value);
914         return this;
915      }
916
917      @Override /* Overridden from Builder */
918      public Builder beanMapPutReturnsOldValue() {
919         super.beanMapPutReturnsOldValue();
920         return this;
921      }
922
923      @Override /* Overridden from Builder */
924      public Builder beanMethodVisibility(Visibility value) {
925         super.beanMethodVisibility(value);
926         return this;
927      }
928
929      @Override /* Overridden from Builder */
930      public Builder beanProperties(Map<String,Object> values) {
931         super.beanProperties(values);
932         return this;
933      }
934
935      @Override /* Overridden from Builder */
936      public Builder beanProperties(Class<?> beanClass, String properties) {
937         super.beanProperties(beanClass, properties);
938         return this;
939      }
940
941      @Override /* Overridden from Builder */
942      public Builder beanProperties(String beanClassName, String properties) {
943         super.beanProperties(beanClassName, properties);
944         return this;
945      }
946
947      @Override /* Overridden from Builder */
948      public Builder beanPropertiesExcludes(Map<String,Object> values) {
949         super.beanPropertiesExcludes(values);
950         return this;
951      }
952
953      @Override /* Overridden from Builder */
954      public Builder beanPropertiesExcludes(Class<?> beanClass, String properties) {
955         super.beanPropertiesExcludes(beanClass, properties);
956         return this;
957      }
958
959      @Override /* Overridden from Builder */
960      public Builder beanPropertiesExcludes(String beanClassName, String properties) {
961         super.beanPropertiesExcludes(beanClassName, properties);
962         return this;
963      }
964
965      @Override /* Overridden from Builder */
966      public Builder beanPropertiesReadOnly(Map<String,Object> values) {
967         super.beanPropertiesReadOnly(values);
968         return this;
969      }
970
971      @Override /* Overridden from Builder */
972      public Builder beanPropertiesReadOnly(Class<?> beanClass, String properties) {
973         super.beanPropertiesReadOnly(beanClass, properties);
974         return this;
975      }
976
977      @Override /* Overridden from Builder */
978      public Builder beanPropertiesReadOnly(String beanClassName, String properties) {
979         super.beanPropertiesReadOnly(beanClassName, properties);
980         return this;
981      }
982
983      @Override /* Overridden from Builder */
984      public Builder beanPropertiesWriteOnly(Map<String,Object> values) {
985         super.beanPropertiesWriteOnly(values);
986         return this;
987      }
988
989      @Override /* Overridden from Builder */
990      public Builder beanPropertiesWriteOnly(Class<?> beanClass, String properties) {
991         super.beanPropertiesWriteOnly(beanClass, properties);
992         return this;
993      }
994
995      @Override /* Overridden from Builder */
996      public Builder beanPropertiesWriteOnly(String beanClassName, String properties) {
997         super.beanPropertiesWriteOnly(beanClassName, properties);
998         return this;
999      }
1000
1001      @Override /* Overridden from Builder */
1002      public Builder beansRequireDefaultConstructor() {
1003         super.beansRequireDefaultConstructor();
1004         return this;
1005      }
1006
1007      @Override /* Overridden from Builder */
1008      public Builder beansRequireSerializable() {
1009         super.beansRequireSerializable();
1010         return this;
1011      }
1012
1013      @Override /* Overridden from Builder */
1014      public Builder beansRequireSettersForGetters() {
1015         super.beansRequireSettersForGetters();
1016         return this;
1017      }
1018
1019      @Override /* Overridden from Builder */
1020      public Builder dictionaryOn(Class<?> on, java.lang.Class<?>...values) {
1021         super.dictionaryOn(on, values);
1022         return this;
1023      }
1024
1025      @Override /* Overridden from Builder */
1026      public Builder disableBeansRequireSomeProperties() {
1027         super.disableBeansRequireSomeProperties();
1028         return this;
1029      }
1030
1031      @Override /* Overridden from Builder */
1032      public Builder disableIgnoreMissingSetters() {
1033         super.disableIgnoreMissingSetters();
1034         return this;
1035      }
1036
1037      @Override /* Overridden from Builder */
1038      public Builder disableIgnoreTransientFields() {
1039         super.disableIgnoreTransientFields();
1040         return this;
1041      }
1042
1043      @Override /* Overridden from Builder */
1044      public Builder disableIgnoreUnknownNullBeanProperties() {
1045         super.disableIgnoreUnknownNullBeanProperties();
1046         return this;
1047      }
1048
1049      @Override /* Overridden from Builder */
1050      public Builder disableInterfaceProxies() {
1051         super.disableInterfaceProxies();
1052         return this;
1053      }
1054
1055      @Override /* Overridden from Builder */
1056      public <T> Builder example(Class<T> pojoClass, T o) {
1057         super.example(pojoClass, o);
1058         return this;
1059      }
1060
1061      @Override /* Overridden from Builder */
1062      public <T> Builder example(Class<T> pojoClass, String json) {
1063         super.example(pojoClass, json);
1064         return this;
1065      }
1066
1067      @Override /* Overridden from Builder */
1068      public Builder findFluentSetters() {
1069         super.findFluentSetters();
1070         return this;
1071      }
1072
1073      @Override /* Overridden from Builder */
1074      public Builder findFluentSetters(Class<?> on) {
1075         super.findFluentSetters(on);
1076         return this;
1077      }
1078
1079      @Override /* Overridden from Builder */
1080      public Builder ignoreInvocationExceptionsOnGetters() {
1081         super.ignoreInvocationExceptionsOnGetters();
1082         return this;
1083      }
1084
1085      @Override /* Overridden from Builder */
1086      public Builder ignoreInvocationExceptionsOnSetters() {
1087         super.ignoreInvocationExceptionsOnSetters();
1088         return this;
1089      }
1090
1091      @Override /* Overridden from Builder */
1092      public Builder ignoreUnknownBeanProperties() {
1093         super.ignoreUnknownBeanProperties();
1094         return this;
1095      }
1096
1097      @Override /* Overridden from Builder */
1098      public Builder ignoreUnknownEnumValues() {
1099         super.ignoreUnknownEnumValues();
1100         return this;
1101      }
1102
1103      @Override /* Overridden from Builder */
1104      public Builder implClass(Class<?> interfaceClass, Class<?> implClass) {
1105         super.implClass(interfaceClass, implClass);
1106         return this;
1107      }
1108
1109      @Override /* Overridden from Builder */
1110      public Builder implClasses(Map<Class<?>,Class<?>> values) {
1111         super.implClasses(values);
1112         return this;
1113      }
1114
1115      @Override /* Overridden from Builder */
1116      public Builder interfaceClass(Class<?> on, Class<?> value) {
1117         super.interfaceClass(on, value);
1118         return this;
1119      }
1120
1121      @Override /* Overridden from Builder */
1122      public Builder interfaces(java.lang.Class<?>...value) {
1123         super.interfaces(value);
1124         return this;
1125      }
1126
1127      @Override /* Overridden from Builder */
1128      public Builder locale(Locale value) {
1129         super.locale(value);
1130         return this;
1131      }
1132
1133      @Override /* Overridden from Builder */
1134      public Builder mediaType(MediaType value) {
1135         super.mediaType(value);
1136         return this;
1137      }
1138
1139      @Override /* Overridden from Builder */
1140      public Builder notBeanClasses(java.lang.Class<?>...values) {
1141         super.notBeanClasses(values);
1142         return this;
1143      }
1144
1145      @Override /* Overridden from Builder */
1146      public Builder notBeanPackages(String...values) {
1147         super.notBeanPackages(values);
1148         return this;
1149      }
1150
1151      @Override /* Overridden from Builder */
1152      public Builder propertyNamer(Class<? extends org.apache.juneau.PropertyNamer> value) {
1153         super.propertyNamer(value);
1154         return this;
1155      }
1156
1157      @Override /* Overridden from Builder */
1158      public Builder propertyNamer(Class<?> on, Class<? extends org.apache.juneau.PropertyNamer> value) {
1159         super.propertyNamer(on, value);
1160         return this;
1161      }
1162
1163      @Override /* Overridden from Builder */
1164      public Builder sortProperties() {
1165         super.sortProperties();
1166         return this;
1167      }
1168
1169      @Override /* Overridden from Builder */
1170      public Builder sortProperties(java.lang.Class<?>...on) {
1171         super.sortProperties(on);
1172         return this;
1173      }
1174
1175      @Override /* Overridden from Builder */
1176      public Builder stopClass(Class<?> on, Class<?> value) {
1177         super.stopClass(on, value);
1178         return this;
1179      }
1180
1181      @Override /* Overridden from Builder */
1182      public <T, S> Builder swap(Class<T> normalClass, Class<S> swappedClass, ThrowingFunction<T,S> swapFunction) {
1183         super.swap(normalClass, swappedClass, swapFunction);
1184         return this;
1185      }
1186
1187      @Override /* Overridden from Builder */
1188      public <T, S> Builder swap(Class<T> normalClass, Class<S> swappedClass, ThrowingFunction<T,S> swapFunction, ThrowingFunction<S,T> unswapFunction) {
1189         super.swap(normalClass, swappedClass, swapFunction, unswapFunction);
1190         return this;
1191      }
1192
1193      @Override /* Overridden from Builder */
1194      public Builder swaps(Object...values) {
1195         super.swaps(values);
1196         return this;
1197      }
1198
1199      @Override /* Overridden from Builder */
1200      public Builder swaps(Class<?>...values) {
1201         super.swaps(values);
1202         return this;
1203      }
1204
1205      @Override /* Overridden from Builder */
1206      public Builder timeZone(TimeZone value) {
1207         super.timeZone(value);
1208         return this;
1209      }
1210
1211      @Override /* Overridden from Builder */
1212      public Builder typeName(Class<?> on, String value) {
1213         super.typeName(on, value);
1214         return this;
1215      }
1216
1217      @Override /* Overridden from Builder */
1218      public Builder typePropertyName(String value) {
1219         super.typePropertyName(value);
1220         return this;
1221      }
1222
1223      @Override /* Overridden from Builder */
1224      public Builder typePropertyName(Class<?> on, String value) {
1225         super.typePropertyName(on, value);
1226         return this;
1227      }
1228
1229      @Override /* Overridden from Builder */
1230      public Builder useEnumNames() {
1231         super.useEnumNames();
1232         return this;
1233      }
1234
1235      @Override /* Overridden from Builder */
1236      public Builder useJavaBeanIntrospector() {
1237         super.useJavaBeanIntrospector();
1238         return this;
1239      }
1240
1241      @Override /* Overridden from Builder */
1242      public Builder detectRecursions() {
1243         super.detectRecursions();
1244         return this;
1245      }
1246
1247      @Override /* Overridden from Builder */
1248      public Builder detectRecursions(boolean value) {
1249         super.detectRecursions(value);
1250         return this;
1251      }
1252
1253      @Override /* Overridden from Builder */
1254      public Builder ignoreRecursions() {
1255         super.ignoreRecursions();
1256         return this;
1257      }
1258
1259      @Override /* Overridden from Builder */
1260      public Builder ignoreRecursions(boolean value) {
1261         super.ignoreRecursions(value);
1262         return this;
1263      }
1264
1265      @Override /* Overridden from Builder */
1266      public Builder initialDepth(int value) {
1267         super.initialDepth(value);
1268         return this;
1269      }
1270
1271      @Override /* Overridden from Builder */
1272      public Builder maxDepth(int value) {
1273         super.maxDepth(value);
1274         return this;
1275      }
1276
1277      @Override /* Overridden from Builder */
1278      public Builder accept(String value) {
1279         super.accept(value);
1280         return this;
1281      }
1282
1283      @Override /* Overridden from Builder */
1284      public Builder addBeanTypes() {
1285         super.addBeanTypes();
1286         return this;
1287      }
1288
1289      @Override /* Overridden from Builder */
1290      public Builder addBeanTypes(boolean value) {
1291         super.addBeanTypes(value);
1292         return this;
1293      }
1294
1295      @Override /* Overridden from Builder */
1296      public Builder addRootType() {
1297         super.addRootType();
1298         return this;
1299      }
1300
1301      @Override /* Overridden from Builder */
1302      public Builder addRootType(boolean value) {
1303         super.addRootType(value);
1304         return this;
1305      }
1306
1307      @Override /* Overridden from Builder */
1308      public Builder keepNullProperties() {
1309         super.keepNullProperties();
1310         return this;
1311      }
1312
1313      @Override /* Overridden from Builder */
1314      public Builder keepNullProperties(boolean value) {
1315         super.keepNullProperties(value);
1316         return this;
1317      }
1318
1319      @Override /* Overridden from Builder */
1320      public Builder listener(Class<? extends org.apache.juneau.serializer.SerializerListener> value) {
1321         super.listener(value);
1322         return this;
1323      }
1324
1325      @Override /* Overridden from Builder */
1326      public Builder produces(String value) {
1327         super.produces(value);
1328         return this;
1329      }
1330
1331      @Override /* Overridden from Builder */
1332      public Builder sortCollections() {
1333         super.sortCollections();
1334         return this;
1335      }
1336
1337      @Override /* Overridden from Builder */
1338      public Builder sortCollections(boolean value) {
1339         super.sortCollections(value);
1340         return this;
1341      }
1342
1343      @Override /* Overridden from Builder */
1344      public Builder sortMaps() {
1345         super.sortMaps();
1346         return this;
1347      }
1348
1349      @Override /* Overridden from Builder */
1350      public Builder sortMaps(boolean value) {
1351         super.sortMaps(value);
1352         return this;
1353      }
1354
1355      @Override /* Overridden from Builder */
1356      public Builder trimEmptyCollections() {
1357         super.trimEmptyCollections();
1358         return this;
1359      }
1360
1361      @Override /* Overridden from Builder */
1362      public Builder trimEmptyCollections(boolean value) {
1363         super.trimEmptyCollections(value);
1364         return this;
1365      }
1366
1367      @Override /* Overridden from Builder */
1368      public Builder trimEmptyMaps() {
1369         super.trimEmptyMaps();
1370         return this;
1371      }
1372
1373      @Override /* Overridden from Builder */
1374      public Builder trimEmptyMaps(boolean value) {
1375         super.trimEmptyMaps(value);
1376         return this;
1377      }
1378
1379      @Override /* Overridden from Builder */
1380      public Builder trimStrings() {
1381         super.trimStrings();
1382         return this;
1383      }
1384
1385      @Override /* Overridden from Builder */
1386      public Builder trimStrings(boolean value) {
1387         super.trimStrings(value);
1388         return this;
1389      }
1390
1391      @Override /* Overridden from Builder */
1392      public Builder uriContext(UriContext value) {
1393         super.uriContext(value);
1394         return this;
1395      }
1396
1397      @Override /* Overridden from Builder */
1398      public Builder uriRelativity(UriRelativity value) {
1399         super.uriRelativity(value);
1400         return this;
1401      }
1402
1403      @Override /* Overridden from Builder */
1404      public Builder uriResolution(UriResolution value) {
1405         super.uriResolution(value);
1406         return this;
1407      }
1408
1409      @Override /* Overridden from Builder */
1410      public Builder fileCharset(Charset value) {
1411         super.fileCharset(value);
1412         return this;
1413      }
1414
1415      @Override /* Overridden from Builder */
1416      public Builder maxIndent(int value) {
1417         super.maxIndent(value);
1418         return this;
1419      }
1420
1421      @Override /* Overridden from Builder */
1422      public Builder quoteChar(char value) {
1423         super.quoteChar(value);
1424         return this;
1425      }
1426
1427      @Override /* Overridden from Builder */
1428      public Builder quoteCharOverride(char value) {
1429         super.quoteCharOverride(value);
1430         return this;
1431      }
1432
1433      @Override /* Overridden from Builder */
1434      public Builder sq() {
1435         super.sq();
1436         return this;
1437      }
1438
1439      @Override /* Overridden from Builder */
1440      public Builder streamCharset(Charset value) {
1441         super.streamCharset(value);
1442         return this;
1443      }
1444
1445      @Override /* Overridden from Builder */
1446      public Builder useWhitespace() {
1447         super.useWhitespace();
1448         return this;
1449      }
1450
1451      @Override /* Overridden from Builder */
1452      public Builder useWhitespace(boolean value) {
1453         super.useWhitespace(value);
1454         return this;
1455      }
1456
1457      @Override /* Overridden from Builder */
1458      public Builder ws() {
1459         super.ws();
1460         return this;
1461      }
1462
1463      @Override /* Overridden from Builder */
1464      public Builder addBeanTypesXml() {
1465         super.addBeanTypesXml();
1466         return this;
1467      }
1468
1469      @Override /* Overridden from Builder */
1470      public Builder addBeanTypesXml(boolean value) {
1471         super.addBeanTypesXml(value);
1472         return this;
1473      }
1474
1475      @Override /* Overridden from Builder */
1476      public Builder addNamespaceUrisToRoot() {
1477         super.addNamespaceUrisToRoot();
1478         return this;
1479      }
1480
1481      @Override /* Overridden from Builder */
1482      public Builder addNamespaceUrisToRoot(boolean value) {
1483         super.addNamespaceUrisToRoot(value);
1484         return this;
1485      }
1486
1487      @Override /* Overridden from Builder */
1488      public Builder defaultNamespace(Namespace value) {
1489         super.defaultNamespace(value);
1490         return this;
1491      }
1492
1493      @Override /* Overridden from Builder */
1494      public Builder disableAutoDetectNamespaces() {
1495         super.disableAutoDetectNamespaces();
1496         return this;
1497      }
1498
1499      @Override /* Overridden from Builder */
1500      public Builder disableAutoDetectNamespaces(boolean value) {
1501         super.disableAutoDetectNamespaces(value);
1502         return this;
1503      }
1504
1505      @Override /* Overridden from Builder */
1506      public Builder enableNamespaces() {
1507         super.enableNamespaces();
1508         return this;
1509      }
1510
1511      @Override /* Overridden from Builder */
1512      public Builder enableNamespaces(boolean value) {
1513         super.enableNamespaces(value);
1514         return this;
1515      }
1516
1517      @Override /* Overridden from Builder */
1518      public Builder namespaces(Namespace...values) {
1519         super.namespaces(values);
1520         return this;
1521      }
1522
1523      @Override /* Overridden from Builder */
1524      public Builder ns() {
1525         super.ns();
1526         return this;
1527      }
1528
1529      @Override /* Overridden from Builder */
1530      public Builder addBeanTypesHtml() {
1531         super.addBeanTypesHtml();
1532         return this;
1533      }
1534
1535      @Override /* Overridden from Builder */
1536      public Builder addBeanTypesHtml(boolean value) {
1537         super.addBeanTypesHtml(value);
1538         return this;
1539      }
1540
1541      @Override /* Overridden from Builder */
1542      public Builder addKeyValueTableHeaders() {
1543         super.addKeyValueTableHeaders();
1544         return this;
1545      }
1546
1547      @Override /* Overridden from Builder */
1548      public Builder addKeyValueTableHeaders(boolean value) {
1549         super.addKeyValueTableHeaders(value);
1550         return this;
1551      }
1552
1553      @Override /* Overridden from Builder */
1554      public Builder disableDetectLabelParameters() {
1555         super.disableDetectLabelParameters();
1556         return this;
1557      }
1558
1559      @Override /* Overridden from Builder */
1560      public Builder disableDetectLabelParameters(boolean value) {
1561         super.disableDetectLabelParameters(value);
1562         return this;
1563      }
1564
1565      @Override /* Overridden from Builder */
1566      public Builder disableDetectLinksInStrings() {
1567         super.disableDetectLinksInStrings();
1568         return this;
1569      }
1570
1571      @Override /* Overridden from Builder */
1572      public Builder disableDetectLinksInStrings(boolean value) {
1573         super.disableDetectLinksInStrings(value);
1574         return this;
1575      }
1576
1577      @Override /* Overridden from Builder */
1578      public Builder labelParameter(String value) {
1579         super.labelParameter(value);
1580         return this;
1581      }
1582
1583      @Override /* Overridden from Builder */
1584      public Builder uriAnchorText(AnchorText value) {
1585         super.uriAnchorText(value);
1586         return this;
1587      }
1588      //-----------------------------------------------------------------------------------------------------------------
1589      // Helpers
1590      //-----------------------------------------------------------------------------------------------------------------
1591
1592      private static <T> List<T> copy(List<T> s) {
1593         return s == null || s.isEmpty() ? null : copyOf(s);
1594      }
1595
1596      private static <T> List<T> copy(T[] s) {
1597         return s.length == 0 ? null : list(s);
1598      }
1599
1600      private List<String> merge(List<String> old, String[] newValues) {
1601         List<String> x = Utils.listOfSize(newValues.length);
1602         for (String s : newValues) {
1603            if ("NONE".equals(s)) {
1604               if (old != null)
1605                  old.clear();
1606            } else if ("INHERIT".equals(s)) {
1607               if (old != null)
1608                  x.addAll(old);
1609            } else {
1610               x.add(s);
1611            }
1612         }
1613         return x;
1614      }
1615
1616      private List<String> mergeNavLinks(List<String> old, String[] newValues) {
1617         List<String> x = Utils.listOfSize(newValues.length);
1618         for (String s : newValues) {
1619            if ("NONE".equals(s)) {
1620               if (old != null)
1621                  old.clear();
1622            } else if ("INHERIT".equals(s)) {
1623               if (old != null)
1624                  x.addAll(old);
1625            } else if (s.indexOf('[') != -1 && INDEXED_LINK_PATTERN.matcher(s).matches()) {
1626               Matcher lm = INDEXED_LINK_PATTERN.matcher(s);
1627               lm.matches();
1628               String key = lm.group(1);
1629               int index = Math.min(x.size(), Integer.parseInt(lm.group(2)));
1630               String remainder = lm.group(3);
1631               x.add(index, key.isEmpty() ? remainder : key + ":" + remainder);
1632            } else {
1633               x.add(s);
1634            }
1635         }
1636         return x;
1637      }
1638
1639      private static final Pattern INDEXED_LINK_PATTERN = Pattern.compile("(?s)(\\S*)\\[(\\d+)\\]\\:(.*)");
1640   }
1641
1642   //-------------------------------------------------------------------------------------------------------------------
1643   // Instance
1644   //-------------------------------------------------------------------------------------------------------------------
1645
1646   final String[] style, stylesheet, script, navlinks, head, header, nav, aside, footer;
1647   final AsideFloat asideFloat;
1648   final String noResultsMessage;
1649   final boolean nowrap, resolveBodyVars;
1650   final Class<? extends HtmlDocTemplate> template;
1651   final List<Class<? extends HtmlWidget>> widgets;
1652
1653   private final HtmlWidgetMap widgetMap;
1654   private final HtmlWidget[] widgetArray;
1655   private final HtmlDocTemplate templateBean;
1656
1657   private volatile HtmlSchemaDocSerializer schemaSerializer;
1658
1659   /**
1660    * Constructor.
1661    *
1662    * @param builder The builder for this object.
1663    */
1664   public HtmlDocSerializer(Builder builder) {
1665      super(builder);
1666      style = builder.style != null ? toArray(builder.style) : EMPTY_ARRAY;
1667      stylesheet = builder.stylesheet != null ? toArray(builder.stylesheet) : EMPTY_ARRAY;
1668      script = builder.script != null ? toArray(builder.script) : EMPTY_ARRAY;
1669      head = builder.head != null ? toArray(builder.head) : EMPTY_ARRAY;
1670      header = builder.header != null ? toArray(builder.header) : EMPTY_ARRAY;
1671      nav = builder.nav != null ? toArray(builder.nav) : EMPTY_ARRAY;
1672      aside = builder.aside != null ? toArray(builder.aside) : EMPTY_ARRAY;
1673      footer = builder.footer != null ? toArray(builder.footer) : EMPTY_ARRAY;
1674      navlinks = builder.navlinks != null ? toArray(builder.navlinks) : EMPTY_ARRAY;
1675      asideFloat = builder.asideFloat;
1676      noResultsMessage = builder.noResultsMessage;
1677      nowrap = builder.nowrap;
1678      resolveBodyVars = builder.resolveBodyVars;
1679      template = builder.template;
1680      widgets = builder.widgets == null ? Collections.emptyList() : copyOf(builder.widgets);
1681
1682      templateBean = newInstance(template);
1683      widgetMap = new HtmlWidgetMap();
1684      widgets.stream().map(this::newInstance).forEach(x -> widgetMap.append(x));
1685      widgetArray = Utils.array(widgetMap.values(), HtmlWidget.class);
1686   }
1687
1688   @Override /* Context */
1689   public Builder copy() {
1690      return new Builder(this);
1691   }
1692
1693   @Override /* Context */
1694   public HtmlDocSerializerSession.Builder createSession() {
1695      return HtmlDocSerializerSession.create(this);
1696   }
1697
1698   @Override /* Context */
1699   public HtmlDocSerializerSession getSession() {
1700      return createSession().build();
1701   }
1702
1703   @Override /* XmlSerializer */
1704   public HtmlSerializer getSchemaSerializer() {
1705      if (schemaSerializer == null)
1706         schemaSerializer = HtmlSchemaDocSerializer.create().beanContext(getBeanContext()).build();
1707      return schemaSerializer;
1708   }
1709
1710   //-----------------------------------------------------------------------------------------------------------------
1711   // Properties
1712   //-----------------------------------------------------------------------------------------------------------------
1713
1714   /**
1715    * Aside section contents.
1716    *
1717    * @see Builder#aside(String...)
1718    * @return
1719    *    The overridden contents of the aside section on the HTML page.
1720    */
1721   protected final String[] getAside() {
1722      return aside;
1723   }
1724
1725   /**
1726    * Float side section contents.
1727    *
1728    * @see Builder#asideFloat(AsideFloat)
1729    * @return
1730    *    How to float the aside contents on the page.
1731    */
1732   protected final AsideFloat getAsideFloat() {
1733      return asideFloat;
1734   }
1735
1736   /**
1737    * Footer section contents.
1738    *
1739    * @see Builder#footer(String...)
1740    * @return
1741    *    The overridden contents of the footer section on the HTML page.
1742    */
1743   protected final String[] getFooter() {
1744      return footer;
1745   }
1746
1747   /**
1748    * Additional head section content.
1749    *
1750    * @see Builder#head(String...)
1751    * @return
1752    *    HTML content to add to the head section of the HTML page.
1753    */
1754   protected final String[] getHead() {
1755      return head;
1756   }
1757
1758   /**
1759    * Header section contents.
1760    *
1761    * @see Builder#header(String...)
1762    * @return
1763    *    The overridden contents of the header section on the HTML page.
1764    */
1765   protected final String[] getHeader() {
1766      return header;
1767   }
1768
1769   /**
1770    * Nav section contents.
1771    *
1772    * @see Builder#nav(String...)
1773    * @return
1774    *    The overridden contents of the nav section on the HTML page.
1775    */
1776   protected final String[] getNav() {
1777      return nav;
1778   }
1779
1780   /**
1781    * Page navigation links.
1782    *
1783    * @see Builder#navlinks(String...)
1784    * @return
1785    *    Navigation links to add to the HTML page.
1786    */
1787   protected final String[] getNavlinks() {
1788      return navlinks;
1789   }
1790
1791   /**
1792    * No-results message.
1793    *
1794    * @see Builder#noResultsMessage(String)
1795    * @return
1796    *    The message used when serializing an empty array or empty list.
1797    */
1798   protected final String getNoResultsMessage() {
1799      return noResultsMessage;
1800   }
1801
1802   /**
1803    * Prevent word wrap on page.
1804    *
1805    * @see Builder#nowrap()
1806    * @return
1807    *    <jk>true</jk> if <js>"* {white-space:nowrap}"</js> shoudl be added to the CSS instructions on the page to prevent word wrapping.
1808    */
1809   protected final boolean isNowrap() {
1810      return nowrap;
1811   }
1812
1813   /**
1814    * Javascript code.
1815    *
1816    * @see Builder#script(String...)
1817    * @return
1818    *    Arbitrary Javascript to add to the HTML page.
1819    */
1820   protected final String[] getScript() {
1821      return script;
1822   }
1823
1824   /**
1825    * CSS style code.
1826    *
1827    * @see Builder#style(String...)
1828    * @return
1829    *    The CSS instructions to add to the HTML page.
1830    */
1831   protected final String[] getStyle() {
1832      return style;
1833   }
1834
1835   /**
1836    * Stylesheet import URLs.
1837    *
1838    * @see Builder#stylesheet(String...)
1839    * @return
1840    *    The link to the stylesheet of the HTML page.
1841    */
1842   protected final String[] getStylesheet() {
1843      return stylesheet;
1844   }
1845
1846   /**
1847    * HTML document template.
1848    *
1849    * @see Builder#template(Class)
1850    * @return
1851    *    The template to use for serializing the page.
1852    */
1853   protected final HtmlDocTemplate getTemplate() {
1854      return templateBean;
1855   }
1856
1857   /**
1858    * HTML widgets.
1859    *
1860    * @see Builder#widgets(Class...)
1861    * @return
1862    *    Widgets defined on this serializers.
1863    */
1864   protected final HtmlWidgetMap getWidgets() {
1865      return widgetMap;
1866   }
1867
1868   /**
1869    * Performs an action on all widgets defined on this serializer.
1870    *
1871    * @param action The action to perform.
1872    * @return This object.
1873    */
1874   protected final HtmlDocSerializer forEachWidget(Consumer<HtmlWidget> action) {
1875      for (HtmlWidget w : widgetArray)
1876         action.accept(w);
1877      return this;
1878   }
1879
1880   //-----------------------------------------------------------------------------------------------------------------
1881   // Other methods
1882   //-----------------------------------------------------------------------------------------------------------------
1883
1884   private String[] toArray(List<String> x) {
1885      return x.toArray(new String[x.size()]);
1886   }
1887
1888   private <T> T newInstance(Class<T> c) {
1889      try {
1890         return c.getDeclaredConstructor().newInstance();
1891      } catch (Exception e) {
1892         throw asRuntimeException(e);
1893      }
1894   }
1895
1896   @Override /* Context */
1897   protected JsonMap properties() {
1898      return filteredMap()
1899         .append("header", header)
1900         .append("nav", nav)
1901         .append("navlinks", navlinks)
1902         .append("aside", aside)
1903         .append("asideFloat", asideFloat)
1904         .append("footer", footer)
1905         .append("style", style)
1906         .append("head", head)
1907         .append("stylesheet", stylesheet)
1908         .append("nowrap", nowrap)
1909         .append("template", template)
1910         .append("noResultsMessage", noResultsMessage)
1911         .append("widgets", widgets);
1912   }
1913}