5050 * @checkstyle ClassFanOutComplexityCheck (500 lines)
5151 */
5252@ EqualsAndHashCode (of = "xsl" )
53- @ SuppressWarnings ({ "PMD.GodClass" , "PMD. TooManyMethods"} )
53+ @ SuppressWarnings ("PMD.TooManyMethods" )
5454public final class XSLDocument implements XSL {
5555
5656 /**
@@ -82,6 +82,25 @@ public final class XSLDocument implements XSL {
8282 XSL .class .getResourceAsStream ("strip.xsl" )
8383 );
8484
85+ /**
86+ * Per-thread builder — {@link DocumentBuilder} is not thread-safe, so each
87+ * thread keeps its own instance. The factory and builder are created lazily
88+ * on the first {@link #transform} call in each thread, avoiding any risk of
89+ * circular class-initialization when the factory's ServiceLoader scan runs.
90+ */
91+ private static final ThreadLocal <DocumentBuilder > DBUILDER =
92+ ThreadLocal .withInitial (
93+ () -> {
94+ try {
95+ return DocumentBuilderFactory .newInstance ().newDocumentBuilder ();
96+ } catch (final ParserConfigurationException ex ) {
97+ throw new IllegalStateException (
98+ "Failed to create DocumentBuilder" , ex
99+ );
100+ }
101+ }
102+ );
103+
85104 /**
86105 * XSL document.
87106 */
@@ -108,6 +127,12 @@ public final class XSLDocument implements XSL {
108127 */
109128 private final transient Unchecked <Templates > templates ;
110129
130+ /**
131+ * Formatted (pretty-printed) string form, cached on first use.
132+ * Since {@code xsl} is immutable the result never changes.
133+ */
134+ private final transient Unchecked <String > formatted ;
135+
111136 /**
112137 * Public ctor, from XML as a source.
113138 * @param src XSL document body
@@ -291,7 +316,11 @@ public XSLDocument(final String src, final Sources srcs,
291316 */
292317 public XSLDocument (final String src , final Sources srcs ,
293318 final Map <String , Object > map , final String base ) {
294- this (src , srcs , new HashMap <>(map ), base , XSLDocument .load (srcs , src , base ));
319+ this (
320+ src , srcs , new HashMap <>(map ), base ,
321+ XSLDocument .load (srcs , src , base ),
322+ XSLDocument .format (src )
323+ );
295324 }
296325
297326 /**
@@ -301,16 +330,18 @@ public XSLDocument(final String src, final Sources srcs,
301330 * @param map Map of XSL params
302331 * @param base SystemId/Base
303332 * @param tmpl Already-compiled stylesheet to reuse
333+ * @param fmt Already-allocated formatted-string scalar to reuse
304334 * @checkstyle ParameterNumberCheck (5 lines)
305335 */
306336 private XSLDocument (final String src , final Sources srcs ,
307337 final Map <String , Object > map , final String base ,
308- final Unchecked <Templates > tmpl ) {
338+ final Unchecked <Templates > tmpl , final Unchecked < String > fmt ) {
309339 this .xsl = src ;
310340 this .sources = srcs ;
311341 this .params = new HashMap <>(map );
312342 this .sid = base ;
313343 this .templates = tmpl ;
344+ this .formatted = fmt ;
314345 }
315346
316347 @ Override
@@ -325,7 +356,8 @@ public XSL with(final String name, final Object value) {
325356 this .sources ,
326357 new MapOf <String , Object >(this .params , new MapEntry <>(name , value )),
327358 this .sid ,
328- this .templates
359+ this .templates ,
360+ this .formatted
329361 );
330362 }
331363
@@ -374,25 +406,12 @@ public static XSL make(final URL url) {
374406
375407 @ Override
376408 public String toString () {
377- return new XMLDocument ( this .xsl ). toString ();
409+ return this .formatted . value ();
378410 }
379411
380412 @ Override
381413 public XML transform (final XML xml ) {
382- final DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance ();
383- final DocumentBuilder builder ;
384- try {
385- builder = factory .newDocumentBuilder ();
386- } catch (final ParserConfigurationException ex ) {
387- throw new IllegalArgumentException (
388- String .format (
389- "Failed to create new XML document by %s" ,
390- factory .getClass ().getName ()
391- ),
392- ex
393- );
394- }
395- final Document target = builder .newDocument ();
414+ final Document target = XSLDocument .DBUILDER .get ().newDocument ();
396415 this .transformInto (xml , new DOMResult (target ));
397416 return new XMLDocument (target );
398417 }
@@ -477,6 +496,17 @@ private Transformer transformer() {
477496 return trans ;
478497 }
479498
499+ /**
500+ * Lazy pretty-printed string form of an XSL document.
501+ * @param xsl XSL document body
502+ * @return Cached formatted string
503+ */
504+ private static Unchecked <String > format (final String xsl ) {
505+ return new Unchecked <>(
506+ new Sticky <>(() -> new XMLDocument (xsl ).toString ())
507+ );
508+ }
509+
480510 /**
481511 * Lazy-load and cache the compiled {@link Templates} object.
482512 * @param sources URI resolver for xsl:import/xsl:include
0 commit comments