001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall.html;
018
019import static org.apache.juneau.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021import static org.apache.juneau.commons.utils.ThrowableUtils.*;
022
023import java.util.*;
024import java.util.concurrent.atomic.*;
025import java.util.function.*;
026import java.util.regex.*;
027
028import org.apache.juneau.commons.collections.*;
029import org.apache.juneau.commons.svl.*;
030import org.apache.juneau.marshall.*;
031
032/**
033 * Serializes POJOs to HTTP responses as HTML documents.
034 *
035 * <h5 class='topic'>Media types</h5>
036 * <p>
037 * Handles <c>Accept</c> types:  <bc>text/html</bc>
038 * <p>
039 * Produces <c>Content-Type</c> types:  <bc>text/html</bc>
040 *
041 * <h5 class='topic'>Description</h5>
042 * <p>
043 * Same as {@link HtmlSerializer}, except wraps the response in <code><xt>&lt;html&gt;</xt></code>,
044 * <code><xt>&lt;head&gt;</xt></code>, and <code><xt>&lt;body&gt;</xt></code> tags so that it can be rendered in a browser.
045 *
046 * <p>
047 * Configurable properties are typically specified via <ja>@HtmlDocConfig</ja>.
048 *
049 * <h5 class='section'>Example:</h5>
050 * <p class='bjava'>
051 *    <ja>@Rest</ja>(
052 *       messages=<js>"nls/AddressBookResource"</js>,
053 *       title=<js>"$L{title}"</js>,
054 *       description=<js>"$L{description}"</js>
055 *    )
056 *    <ja>@HtmlDocConfig</ja>(
057 *       navlinks={
058 *          <js>"api: servlet:/api"</js>,
059 *          <js>"doc: doc"</js>
060 *       }
061 *    )
062 *    <jk>public class</jk> AddressBookResource <jk>extends</jk> BasicRestServlet {
063 * </p>
064 *
065 * <p>
066 * The <c>$L{...}</c> variable represent localized strings pulled from the resource bundle identified by the
067 * <c>messages</c> annotation.
068 * <br>These variables are replaced at runtime based on the HTTP request locale.
069 * <br>Several built-in runtime variable types are defined, and the API can be extended to include user-defined variables.
070 *
071 * <h5 class='section'>Notes:</h5><ul>
072 *    <li class='note'>This class is thread safe and reusable.
073 * </ul>
074 *
075 * <h5 class='section'>See Also:</h5><ul>
076 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/HtmlSupport">HTML</a>
077
078 * </ul>
079 */
080@SuppressWarnings({
081   "java:S110", // Inheritance depth acceptable for this class hierarchy
082   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
083   "java:S9149" // Per-format static factories intentionally shadow the parent's.
084})
085public class HtmlDocSerializer extends HtmlStrippedDocSerializer {
086
087   /**
088    * Builder class.
089    */
090   @SuppressWarnings({
091      "java:S119" // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name.
092   })
093   public abstract static class Builder<SELF extends Builder<SELF>> extends HtmlStrippedDocSerializer.Builder<SELF> {
094
095      private static final Cache<HashKey,HtmlDocSerializer> CACHE = Cache.of(HashKey.class, HtmlDocSerializer.class).build();
096
097      private static final Pattern INDEXED_LINK_PATTERN = Pattern.compile("(?s)(\\S*)\\[(\\d+)\\]\\:(.*)");
098
099      private static <T> List<T> copy(List<T> s) {
100         return s == null || s.isEmpty() ? null : cp(s);
101      }
102
103      private static <T> List<T> copy(T[] s) {
104         return s.length == 0 ? null : l(s);
105      }
106
107      List<String> aside;
108   List<String> footer;
109   List<String> head;
110   List<String> header;
111   List<String> nav;
112   List<String> navlinks;
113   List<String> script;
114   List<String> style;
115   List<String> stylesheet;
116      AsideFloat asideFloat;
117      String noResultsMessage;
118
119      boolean nowrap;
120      boolean resolveBodyVars;
121
122      Class<? extends HtmlDocTemplate> template;
123
124      List<Class<? extends HtmlWidget>> widgets;
125
126      /**
127       * Constructor, default settings.
128       */
129      protected Builder() {
130         produces("text/html");
131         accept("text/html");
132         asideFloat = AsideFloat.RIGHT;
133         noResultsMessage = "<p>no results</p>";
134         template = BasicHtmlDocTemplate.class;
135      }
136
137      /**
138       * Copy constructor.
139       *
140       * @param copyFrom The builder to copy from.
141       */
142      protected Builder(Builder<?> copyFrom) {
143         super(copyFrom);
144         aside = copy(copyFrom.aside);
145         asideFloat = copyFrom.asideFloat;
146         footer = copy(copyFrom.footer);
147         head = copy(copyFrom.head);
148         header = copy(copyFrom.header);
149         nav = copy(copyFrom.nav);
150         navlinks = copy(copyFrom.navlinks);
151         noResultsMessage = copyFrom.noResultsMessage;
152         nowrap = copyFrom.nowrap;
153         resolveBodyVars = copyFrom.resolveBodyVars;
154         script = copy(copyFrom.script);
155         style = copy(copyFrom.style);
156         stylesheet = copy(copyFrom.stylesheet);
157         template = copyFrom.template;
158         widgets = copy(copyFrom.widgets);
159      }
160
161      /**
162       * Copy constructor.
163       *
164       * @param copyFrom The bean to copy from.
165       */
166      protected Builder(HtmlDocSerializer copyFrom) {
167         super(copyFrom);
168         aside = copy(copyFrom.aside);
169         asideFloat = copyFrom.asideFloat;
170         footer = copy(copyFrom.footer);
171         head = copy(copyFrom.head);
172         header = copy(copyFrom.header);
173         nav = copy(copyFrom.nav);
174         navlinks = copy(copyFrom.navlinks);
175         noResultsMessage = copyFrom.noResultsMessage;
176         nowrap = copyFrom.nowrap;
177         resolveBodyVars = copyFrom.resolveBodyVars;
178         script = copy(copyFrom.script);
179         style = copy(copyFrom.style);
180         stylesheet = copy(copyFrom.stylesheet);
181         template = copyFrom.template;
182         widgets = copy(copyFrom.widgets);
183      }
184
185      /**
186       * Returns the list of aside section contents.
187       *
188       * <p>
189       * Gives access to the inner list if you need to make more than simple additions via {@link #aside(String...)}.
190       *
191       * @return The list of aside section contents.
192       * @see #aside(String...)
193       */
194      public List<String> aside() {
195         if (aside == null)
196            aside = list();
197         return aside;
198      }
199
200      /**
201       * Aside section contents.
202       *
203       * <p>
204       * Allows you to specify the contents of the aside section on the HTML page.
205       * The aside section floats on the right of the page for providing content supporting the serialized content of
206       * the page.
207       *
208       * <p>
209       * By default, the aside section is empty.
210       *
211       * <h5 class='section'>Example:</h5>
212       * <p class='bjava'>
213       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
214       *       .<jsm>create</jsm>()
215       *       .aside(
216       *          <js>"&lt;ul&gt;"</js>,
217       *          <js>" &lt;li&gt;Item 1"</js>,
218       *          <js>" &lt;li&gt;Item 2"</js>,
219       *          <js>" &lt;li&gt;Item 3"</js>,
220       *          <js>"&lt;/ul&gt;"</js>
221       *       )
222       *       .build();
223       * </p>
224       *
225       * <h5 class='section'>Notes:</h5><ul>
226       *    <li class='note'>
227       *       Format: HTML
228       *    <li class='note'>
229       *       Supports <a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerSvlVariables">REST Server SVL Variables</a>
230       *       (e.g. <js>"$L{my.localized.variable}"</js>).
231       *    <li class='note'>
232       *       A value of <js>"NONE"</js> can be used to force no value.
233       *    <li class='note'>
234       *       The parent value can be included by adding the literal <js>"INHERIT"</js> as a value.
235       *    <li class='note'>
236       *       Multiple values are combined with newlines into a single string.
237       *    <li class='note'>
238       *       On methods, this value is inherited from the <ja>@HtmlDocConfig</ja> annotation on the servlet/resource class.
239       *    <li class='note'>
240       *       On servlet/resource classes, this value is inherited from the <ja>@HtmlDocConfig</ja> annotation on the
241       *       parent class.
242       * </ul>
243       *
244       * @param value
245       *    The new value for this property.
246       * @return This object.
247       */
248      public SELF aside(String...value) {
249         aside = merge(aside, value);
250         return self();
251      }
252
253      /**
254       * Float aside section contents.
255       *
256       * <p>
257       * Allows you to position the aside contents of the page around the main contents.
258       *
259       * <p>
260       * By default, the aside section is floated to the right.
261       *
262       * <h5 class='section'>Example:</h5>
263       * <p class='bjava'>
264       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
265       *       .<jsm>create</jsm>()
266       *       .aside(
267       *          <js>"&lt;ul&gt;"</js>,
268       *          <js>" &lt;li&gt;Item 1"</js>,
269       *          <js>" &lt;li&gt;Item 2"</js>,
270       *          <js>" &lt;li&gt;Item 3"</js>,
271       *          <js>"&lt;/ul&gt;"</js>
272       *       )
273       *       .asideFloat(<jsf>RIGHT</jsf>)
274       *       .build();
275       * </p>
276       *
277       * @param value
278       *    The new value for this property.
279       * @return This object.
280       */
281      public SELF asideFloat(AsideFloat value) {
282         asideFloat = value;
283         return self();
284      }
285
286      @Override /* Overridden from Context.Builder<?> */
287      public HtmlDocSerializer build() {
288         return cache(CACHE).build(HtmlDocSerializer.class);
289      }
290
291      @Override /* Overridden from Context.Builder<?> */
292      public abstract SELF copy();
293
294      /**
295       * Returns the list of footer section contents.
296       *
297       * <p>
298       * Gives access to the inner list if you need to make more than simple additions via {@link #footer(String...)}.
299       *
300       * @return The list of footer section contents.
301       * @see #footer(String...)
302       */
303      public List<String> footer() {
304         if (footer == null)
305            footer = list();
306         return footer;
307      }
308
309      /**
310       * Footer section contents.
311       *
312       * <p>
313       * Allows you to specify the contents of the footer section on the HTML page.
314       *
315       * <p>
316       * By default, the footer section is empty.
317       *
318       * <h5 class='section'>Example:</h5>
319       * <p class='bjava'>
320       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
321       *       .<jsm>create</jsm>()
322       *       .footer(
323       *          <js>"&lt;b&gt;This interface is great!&lt;/b&gt;"</js>
324       *       )
325       *       .build();
326       * </p>
327       *
328       * @param value
329       *    The new value for this property.
330       * @return This object.
331       */
332      public SELF footer(String...value) {
333         footer = merge(footer, value);
334         return self();
335      }
336
337      @Override /* Overridden from Context.Builder<?> */
338      public HashKey hashKey() {
339         // @formatter:off
340         return HashKey.of(
341            super.hashKey(),
342            aside,
343            footer,
344            head,
345            header,
346            nav,
347            navlinks,
348            script,
349            style,
350            stylesheet,
351            asideFloat,
352            noResultsMessage,
353            nowrap,
354            resolveBodyVars,
355            template,
356            widgets
357         );
358         // @formatter:on
359      }
360
361      /**
362       * Returns the list of head section contents.
363       *
364       * <p>
365       * Gives access to the inner list if you need to make more than simple additions via {@link #head(String...)}.
366       *
367       * @return The list of head section contents.
368       * @see #head(String...)
369       */
370      public List<String> head() {
371         if (head == null)
372            head = list();
373         return head;
374      }
375
376      /**
377       * Additional head section content.
378       *
379       * <p>
380       * Adds the specified HTML content to the head section of the page.
381       *
382       * <h5 class='section'>Example:</h5>
383       * <p class='bjava'>
384       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
385       *       .<jsm>create</jsm>()
386       *       .head(
387       *          <js>"&lt;link rel='icon' href='$U{servlet:/htdocs/mypageicon.ico}'&gt;"</js>
388       *       )
389       *       .build();
390       * </p>
391       *
392       * @param value
393       *    The new value for this property.
394       * @return This object.
395       */
396      public SELF head(String...value) {
397         head = merge(head, value);
398         return self();
399      }
400
401      /**
402       * Returns the list of header section contents.
403       *
404       * <p>
405       * Gives access to the inner list if you need to make more than simple additions via {@link #header(String...)}.
406       *
407       * @return The list of header section contents.
408       * @see #header(String...)
409       */
410      public List<String> header() {
411         if (header == null)
412            header = list();
413         return header;
414      }
415
416      /**
417       * Header section contents.
418       *
419       * <p>
420       * Allows you to override the contents of the header section on the HTML page.
421       * The header section normally contains the title and description at the top of the page.
422       *
423       * <h5 class='section'>Example:</h5>
424       * <p class='bjava'>
425       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
426       *       .<jsm>create</jsm>()
427       *       .header(
428       *          <js>"&lt;h1&gt;My own header&lt;/h1&gt;"</js>
429       *       )
430       *       .build()
431       * </p>
432       *
433       * @param value
434       *    The new value for this property.
435       * @return This object.
436       */
437      public SELF header(String...value) {
438         header = merge(header, value);
439         return self();
440      }
441
442      /**
443       * Returns the list of nav section contents.
444       *
445       * <p>
446       * Gives access to the inner list if you need to make more than simple additions via {@link #nav(String...)}.
447       *
448       * @return The list of nav section contents.
449       * @see #nav(String...)
450       */
451      public List<String> nav() {
452         if (nav == null)
453            nav = list();
454         return nav;
455      }
456
457      /**
458       * Nav section contents.
459       *
460       * <p>
461       * Allows you to override the contents of the nav section on the HTML page.
462       * The nav section normally contains the page links at the top of the page.
463       *
464       * <h5 class='section'>Example:</h5>
465       * <p class='bjava'>
466       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
467       *       .<jsm>create</jsm>()
468       *       .nav(
469       *          <js>"&lt;p class='special-navigation'&gt;This is my special navigation content&lt;/p&gt;"</js>
470       *       )
471       *       .build()
472       * </p>
473       *
474       * <p>
475       * When this property is specified, the {@link Builder#navlinks(String...)} property is ignored.
476       *
477       * @param value
478       *    The new value for this property.
479       * @return This object.
480       */
481      public SELF nav(String...value) {
482         nav = merge(nav, value);
483         return self();
484      }
485
486      /**
487       * Returns the list of navlinks section contents.
488       *
489       * <p>
490       * Gives access to the inner list if you need to make more than simple additions via {@link #navlinks(String...)}.
491       *
492       * @return The list of navlinks section contents.
493       * @see #navlinks(String...)
494       */
495      public List<String> navlinks() {
496         if (navlinks == null)
497            navlinks = list();
498         return navlinks;
499      }
500
501      /**
502       * Page navigation links.
503       *
504       * <p>
505       * Adds a list of hyperlinks immediately under the title and description but above the content of the page.
506       *
507       * <p>
508       * This can be used to provide convenient hyperlinks when viewing the REST interface from a browser.
509       *
510       * <p>
511       * The value is an array of strings with two possible values:
512       * <ul>
513       *    <li>A key-value pair representing a hyperlink label and href:
514       *       <br><js>"google: http://google.com"</js>
515       *    <li>Arbitrary HTML.
516       * </ul>
517       *
518       * <p>
519       * Relative URLs are considered relative to the servlet path.
520       * For example, if the servlet path is <js>"http://localhost/myContext/myServlet"</js>, and the
521       * URL is <js>"foo"</js>, the link becomes <js>"http://localhost/myContext/myServlet/foo"</js>.
522       * Absolute (<js>"/myOtherContext/foo"</js>) and fully-qualified (<js>"http://localhost2/foo"</js>) URLs
523       * can also be used in addition to various other protocols specified by {@link UriResolver} such as
524       * <js>"servlet:/..."</js>.
525       *
526       * <h5 class='section'>Example:</h5>
527       * <p class='bjava'>
528       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
529       *       .<jsm>create</jsm>()
530       *       .navlinks(
531       *          <js>"api: servlet:/api"</js>,
532       *          <js>"stats: servlet:/stats"</js>,
533       *          <js>"doc: doc"</js>
534       *       )
535       *       .build();
536       * </p>
537       *
538       * @param value
539       *    The new value for this property.
540       * @return This object.
541       */
542      public SELF navlinks(String...value) {
543         navlinks = mergeNavLinks(navlinks, value);
544         return self();
545      }
546
547      /**
548       * No-results message.
549       *
550       * <p>
551       * Allows you to specify the string message used when trying to serialize an empty array or empty list.
552       *
553       * <h5 class='section'>Example:</h5>
554       * <p class='bjava'>
555       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
556       *       .<jsm>create</jsm>()
557       *       .noResultsMessage(<js>"&lt;b&gt;This interface is great!&lt;/b&gt;"</js>)
558       *       .build();
559       * </p>
560       *
561       * <p>
562       * A value of <js>"NONE"</js> can be used to represent no value to differentiate it from an empty string.
563       *
564       * @param value
565       *    The new value for this property.
566       * @return This object.
567       */
568      public SELF noResultsMessage(String value) {
569         noResultsMessage = value;
570         return self();
571      }
572
573      /**
574       * Prevent word wrap on page.
575       *
576       * <p>
577       * Adds <js>"* {white-space:nowrap}"</js> to the CSS instructions on the page to prevent word wrapping.
578       *
579       * @return This object.
580       */
581      public SELF nowrap() {
582         return nowrap(true);
583      }
584
585      /**
586       * Same as {@link #nowrap()} but allows you to explicitly specify the boolean value.
587       *
588       * @param value
589       *    The new value for this property.
590       * @return This object.
591       * @see #nowrap()
592       */
593      public SELF nowrap(boolean value) {
594         nowrap = value;
595         return self();
596      }
597
598      /**
599       * Resolve $ variables in serialized POJO.
600       *
601       * @return This object.
602       */
603      public SELF resolveBodyVars() {
604         return resolveBodyVars(true);
605      }
606
607      /**
608       * Same as {@link #resolveBodyVars()} but allows you to explicitly specify the boolean value.
609       *
610       * @param value
611       *    The new value for this property.
612       * @return This object.
613       * @see #nowrap()
614       */
615      public SELF resolveBodyVars(boolean value) {
616         resolveBodyVars = value;
617         return self();
618      }
619
620      /**
621       * Returns the list of page script contents.
622       *
623       * <p>
624       * Gives access to the inner list if you need to make more than simple additions via {@link #script(String...)}.
625       *
626       * @return The list of page script contents.
627       * @see #script(String...)
628       */
629      public List<String> script() {
630         if (script == null)
631            script = list();
632         return script;
633      }
634
635      /**
636       * Adds the specified Javascript code to the HTML page.
637       *
638       * <p>
639       * A shortcut on <ja>@Rest</ja> is also provided for this setting:
640       * <p class='bjava'>
641       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
642       *       .<jsm>create</jsm>()
643       *       .script(<js>"alert('hello!');"</js>)
644       *       .build();
645       * </p>
646       *
647       * @param value
648       *    The value to add to this property.
649       * @return This object.
650       */
651      public SELF script(String...value) {
652         script = merge(script, value);
653         return self();
654      }
655
656      /**
657       * Returns the list of page style contents.
658       *
659       * <p>
660       * Gives access to the inner list if you need to make more than simple additions via {@link #style(String...)}.
661       *
662       * @return The list of page style contents.
663       * @see #style(String...)
664       */
665      public List<String> style() {
666         if (style == null)
667            style = list();
668         return style;
669      }
670
671      /**
672       * Adds the specified CSS instructions to the HTML page.
673       *
674       * <p class='bjava'>
675       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
676       *       .<jsm>create</jsm>()
677       *       .style(
678       *          <js>"h3 { color: red; }"</js>,
679       *          <js>"h5 { font-weight: bold; }"</js>
680       *       )
681       *       .build();
682       * </p>
683       *
684       * @param value
685       *    The value to add to this property.
686       * @return This object.
687       */
688      public SELF style(String...value) {
689         style = merge(style, value);
690         return self();
691      }
692
693      /**
694       * Returns the list of stylesheet URLs.
695       *
696       * <p>
697       * Gives access to the inner list if you need to make more than simple additions via {@link #stylesheet(String...)}.
698       *
699       * @return The list of stylesheet URLs.
700       * @see #stylesheet(String...)
701       */
702      public List<String> stylesheet() {
703         if (stylesheet == null)
704            stylesheet = list();
705         return stylesheet;
706      }
707
708      /**
709       * Adds to the list of stylesheet URLs.
710       *
711       * <p>
712       * Note that this stylesheet is controlled by the <code><ja>@Rest</ja>.stylesheet()</code> annotation.
713       *
714       * @param value
715       *    The value to add to this property.
716       * @return This object.
717       */
718      public SELF stylesheet(String...value) {
719         stylesheet = merge(stylesheet, value);
720         return self();
721      }
722
723      /**
724       * HTML document template.
725       *
726       * <p>
727       * Specifies the template to use for serializing the page.
728       *
729       * <p>
730       * By default, the {@link BasicHtmlDocTemplate} class is used to construct the contents of the HTML page, but
731       * can be overridden with your own custom implementation class.
732       *
733       * <h5 class='section'>Example:</h5>
734       * <p class='bjava'>
735       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
736       *       .<jsm>create</jsm>()
737       *       .template(MySpecialDocTemplate.<jk>class</jk>)
738       *       .build();
739       * </p>
740       *
741       * @param value
742       *    The new value for this property.
743       * @return This object.
744       */
745      public SELF template(Class<? extends HtmlDocTemplate> value) {
746         template = value;
747         return self();
748      }
749
750
751      /**
752       * Returns the list of page widgets.
753       *
754       * <p>
755       * Gives access to the inner list if you need to make more than simple additions via {@link #widgets(Class...)}.
756       *
757       * @return The list of page widgets.
758       * @see #widgets(Class...)
759       */
760      public List<Class<? extends HtmlWidget>> widgets() {
761         if (widgets == null)
762            widgets = list();
763         return widgets;
764      }
765
766      /**
767       * HTML Widgets.
768       *
769       * <p>
770       * Defines widgets that can be used in conjunction with string variables of the form <js>"$W{name}"</js>to quickly
771       * generate arbitrary replacement text.
772       *
773       * Widgets resolve the following variables:
774       * <ul class='spaced-list'>
775       *    <li><js>"$W{name}"</js> - Contents returned by {@link HtmlWidget#getHtml(VarResolverSession)}.
776       *    <li><js>"$W{name.script}"</js> - Contents returned by {@link HtmlWidget#getScript(VarResolverSession)}.
777       *       <br>The script contents are automatically inserted into the <xt>&lt;head/script&gt;</xt> section
778       *           in the HTML page.
779       *    <li><js>"$W{name.style}"</js> - Contents returned by {@link HtmlWidget#getStyle(VarResolverSession)}.
780       *       <br>The styles contents are automatically inserted into the <xt>&lt;head/style&gt;</xt> section
781       *           in the HTML page.
782       * </ul>
783       *
784       * <p>
785       * The following examples shows how to associate a widget with a REST method and then have it rendered in the links
786       * and aside section of the page:
787       *
788       * <p class='bjava'>
789       *    WriterSerializer <jv>serializer</jv> = HtmlDocSerializer
790       *       .<jsm>create</jsm>()
791       *       .widgets(
792       *          MyWidget.<jk>class</jk>
793       *       )
794       *       .navlinks(
795       *          <js>"$W{MyWidget}"</js>
796       *       )
797       *       .aside(
798       *          <js>"Check out this widget:  $W{MyWidget}"</js>
799       *       )
800       *       .build();
801       * </p>
802       *
803       * <h5 class='section'>Notes:</h5><ul>
804       *    <li class='note'>
805       *       Widgets are inherited from super classes, but can be overridden by reusing the widget name.
806       * </ul>
807       *
808       * <h5 class='section'>See Also:</h5><ul>
809       *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/HtmlWidgets">Widgets</a>
810       * </ul>
811       *
812       * @param values The values to add to this setting.
813       * @return This object.
814       */
815      @SuppressWarnings({
816         "unchecked" // Varargs method requires unchecked cast
817      })
818      public SELF widgets(Class<? extends HtmlWidget>...values) {
819         addAll(widgets(), values);
820         return self();
821      }
822
823      private static List<String> merge(List<String> old, String[] newValues) {
824         List<String> x = listOfSize(newValues.length);
825         for (var s : newValues) {
826            if (eq(s, "NONE")) {
827               if (nn(old))
828                  old.clear();
829            } else if (eq(s, "INHERIT")) {
830               if (nn(old))
831                  x.addAll(old);
832            } else {
833               x.add(s);
834            }
835         }
836         return x;
837      }
838
839      @SuppressWarnings({
840         "java:S3776" // Cognitive complexity acceptable for nav link merging with various input formats
841      })
842      private static List<String> mergeNavLinks(List<String> old, String[] newValues) {
843         List<String> x = listOfSize(newValues.length);
844         for (var s : newValues) {
845            if (eq(s, "NONE")) {
846               if (nn(old))
847                  old.clear();
848            } else if (eq(s, "INHERIT")) {
849               if (nn(old))
850                  x.addAll(old);
851            } else if (s.indexOf('[') != -1 && INDEXED_LINK_PATTERN.matcher(s).matches()) {
852               Matcher lm = INDEXED_LINK_PATTERN.matcher(s);
853               lm.matches();
854               String key = lm.group(1);
855               int index = Math.min(x.size(), Integer.parseInt(lm.group(2)));
856               String remainder = lm.group(3);
857               x.add(index, key.isEmpty() ? remainder : key + ":" + remainder);
858            } else {
859               x.add(s);
860            }
861         }
862         return x;
863      }
864   }
865
866   /**
867    * Concrete default builder leaf for the non-subclassed {@link HtmlDocSerializer#create()} / {@link HtmlDocSerializer#copy()} path.
868    */
869   public static final class DefaultBuilder extends Builder<DefaultBuilder> {
870
871      DefaultBuilder() {}
872
873      DefaultBuilder(HtmlDocSerializer copyFrom) {
874         super(copyFrom);
875      }
876
877      DefaultBuilder(Builder<?> copyFrom) {
878         super(copyFrom);
879      }
880
881      @Override /* Overridden from Context.Builder<?> */
882      public DefaultBuilder copy() {
883         return new DefaultBuilder(this);
884      }
885   }
886
887   private static final String[] EMPTY_ARRAY = {};
888
889   /** Default serializer, all default settings. */
890   public static final HtmlDocSerializer DEFAULT = new HtmlDocSerializer(create());
891
892   /**
893    * Creates a new builder for this object.
894    *
895    * @return A new builder.
896    */
897   @SuppressWarnings({
898      "java:S1452" // Builder<?> wildcard return intentional; callers chain via fluent API without needing the concrete type
899   })
900   public static Builder<?> create() {
901      return new DefaultBuilder();
902   }
903
904final String[] style;
905final String[] stylesheet;
906final String[] script;
907final String[] navlinks;
908final String[] head;
909final String[] header;
910final String[] nav;
911final String[] aside;
912final String[] footer;
913final AsideFloat asideFloat;
914   final String noResultsMessage;
915   final boolean nowrap;
916   final boolean resolveBodyVars;
917   final Class<? extends HtmlDocTemplate> template;
918   final List<Class<? extends HtmlWidget>> widgets;
919
920   private final HtmlWidgetMap widgetMap;
921   private final HtmlWidget[] widgetArray;
922   private final HtmlDocTemplate templateBean;
923
924   private final AtomicReference<HtmlSchemaDocSerializer> schemaSerializer = new AtomicReference<>();
925
926   /**
927    * Constructor.
928    *
929    * @param builder The builder for this object.
930    */
931   public HtmlDocSerializer(Builder<?> builder) {
932      super(builder);
933      aside = nn(builder.aside) ? toArray(builder.aside) : EMPTY_ARRAY;
934      asideFloat = builder.asideFloat;
935      footer = nn(builder.footer) ? toArray(builder.footer) : EMPTY_ARRAY;
936      head = nn(builder.head) ? toArray(builder.head) : EMPTY_ARRAY;
937      header = nn(builder.header) ? toArray(builder.header) : EMPTY_ARRAY;
938      nav = nn(builder.nav) ? toArray(builder.nav) : EMPTY_ARRAY;
939      navlinks = nn(builder.navlinks) ? toArray(builder.navlinks) : EMPTY_ARRAY;
940      noResultsMessage = builder.noResultsMessage;
941      nowrap = builder.nowrap;
942      resolveBodyVars = builder.resolveBodyVars;
943      script = nn(builder.script) ? toArray(builder.script) : EMPTY_ARRAY;
944      style = nn(builder.style) ? toArray(builder.style) : EMPTY_ARRAY;
945      stylesheet = nn(builder.stylesheet) ? toArray(builder.stylesheet) : EMPTY_ARRAY;
946      template = builder.template;
947      widgets = builder.widgets == null ? Collections.emptyList() : cp(builder.widgets);
948
949      templateBean = newInstance(template);
950      widgetMap = new HtmlWidgetMap();
951      widgets.stream().map(this::newInstance).forEach(widgetMap::append);
952      widgetArray = array(widgetMap.values(), HtmlWidget.class);
953   }
954
955   @Override /* Overridden from Context */
956   public Builder<?> copy() {
957      return new DefaultBuilder(this);
958   }
959
960   @Override /* Overridden from Context */
961   public HtmlDocSerializerSession.Builder<?> createSession() {
962      return HtmlDocSerializerSession.create(this);
963   }
964
965   @Override /* Overridden from XmlSerializer */
966   public HtmlSerializer getSchemaSerializer() {
967      HtmlSchemaDocSerializer result = schemaSerializer.get();
968      if (result == null) {
969         result = HtmlSchemaDocSerializer.create().marshallingContext(getMarshallingContext()).build();
970         if (! schemaSerializer.compareAndSet(null, result)) {
971            result = schemaSerializer.get();
972         }
973      }
974      return result;
975   }
976
977   @Override /* Overridden from Context */
978   public HtmlDocSerializerSession getSession() { return createSession().build(); }
979
980   <T> T newInstance(Class<T> c) {
981      try {
982         return c.getDeclaredConstructor().newInstance();
983      } catch (Exception e) {
984         throw toRex(e);
985      }
986   }
987
988   private static String[] toArray(List<String> x) {
989      return x.toArray(new String[x.size()]);
990   }
991
992   /**
993    * Performs an action on all widgets defined on this serializer.
994    *
995    * @param action The action to perform.
996    * @return This object.
997    */
998   protected final HtmlDocSerializer forEachWidget(Consumer<HtmlWidget> action) {
999      for (var w : widgetArray)
1000         action.accept(w);
1001      return this;
1002   }
1003
1004   /**
1005    * Aside section contents.
1006    *
1007    * @see Builder#aside(String...)
1008    * @return
1009    *    The overridden contents of the aside section on the HTML page.
1010    */
1011   protected final String[] getAside() { return aside; }
1012
1013   /**
1014    * Float side section contents.
1015    *
1016    * @see Builder#asideFloat(AsideFloat)
1017    * @return
1018    *    How to float the aside contents on the page.
1019    */
1020   protected final AsideFloat getAsideFloat() { return asideFloat; }
1021
1022   /**
1023    * Footer section contents.
1024    *
1025    * @see Builder#footer(String...)
1026    * @return
1027    *    The overridden contents of the footer section on the HTML page.
1028    */
1029   protected final String[] getFooter() { return footer; }
1030
1031   /**
1032    * Additional head section content.
1033    *
1034    * @see Builder#head(String...)
1035    * @return
1036    *    HTML content to add to the head section of the HTML page.
1037    */
1038   protected final String[] getHead() { return head; }
1039
1040   /**
1041    * Header section contents.
1042    *
1043    * @see Builder#header(String...)
1044    * @return
1045    *    The overridden contents of the header section on the HTML page.
1046    */
1047   protected final String[] getHeader() { return header; }
1048
1049   /**
1050    * Nav section contents.
1051    *
1052    * @see Builder#nav(String...)
1053    * @return
1054    *    The overridden contents of the nav section on the HTML page.
1055    */
1056   protected final String[] getNav() { return nav; }
1057
1058   /**
1059    * Page navigation links.
1060    *
1061    * @see Builder#navlinks(String...)
1062    * @return
1063    *    Navigation links to add to the HTML page.
1064    */
1065   protected final String[] getNavlinks() { return navlinks; }
1066
1067   /**
1068    * No-results message.
1069    *
1070    * @see Builder#noResultsMessage(String)
1071    * @return
1072    *    The message used when serializing an empty array or empty list.
1073    */
1074   protected final String getNoResultsMessage() { return noResultsMessage; }
1075
1076   /**
1077    * Javascript code.
1078    *
1079    * @see Builder#script(String...)
1080    * @return
1081    *    Arbitrary Javascript to add to the HTML page.
1082    */
1083   protected final String[] getScript() { return script; }
1084
1085   /**
1086    * CSS style code.
1087    *
1088    * @see Builder#style(String...)
1089    * @return
1090    *    The CSS instructions to add to the HTML page.
1091    */
1092   protected final String[] getStyle() { return style; }
1093
1094   /**
1095    * Stylesheet import URLs.
1096    *
1097    * @see Builder#stylesheet(String...)
1098    * @return
1099    *    The link to the stylesheet of the HTML page.
1100    */
1101   protected final String[] getStylesheet() { return stylesheet; }
1102
1103   /**
1104    * HTML document template.
1105    *
1106    * @see Builder#template(Class)
1107    * @return
1108    *    The template to use for serializing the page.
1109    */
1110   protected final HtmlDocTemplate getTemplate() { return templateBean; }
1111
1112   /**
1113    * HTML widgets.
1114    *
1115    * @see Builder#widgets(Class...)
1116    * @return
1117    *    Widgets defined on this serializers.
1118    */
1119   protected final HtmlWidgetMap getWidgets() { return widgetMap; }
1120
1121   /**
1122    * Prevent word wrap on page.
1123    *
1124    * @see Builder#nowrap()
1125    * @return
1126    *    <jk>true</jk> if <js>"* {white-space:nowrap}"</js> shoudl be added to the CSS instructions on the page to prevent word wrapping.
1127    */
1128   protected final boolean isNowrap() { return nowrap; }
1129
1130   @Override /* Overridden from HtmlSerializer */
1131   protected FluentMap<String,Object> properties() {
1132      return super.properties()
1133         .a("aside", aside)
1134         .a("asideFloat", asideFloat)
1135         .a("footer", footer)
1136         .a("head", head)
1137         .a("header", header)
1138         .a("nav", nav)
1139         .a("navlinks", navlinks)
1140         .a("noResultsMessage", noResultsMessage)
1141         .a("nowrap", nowrap)
1142         .a("style", style)
1143         .a("stylesheet", stylesheet)
1144         .a("template", template)
1145         .a("widgets", widgets);
1146   }
1147}