001/*
002 * ============================================================================
003 * Copyright © 2002-2023 by Thomas Thrien.
004 * All Rights Reserved.
005 * ============================================================================
006 *
007 * Licensed to the public under the agreements of the GNU Lesser General Public
008 * License, version 3.0 (the "License"). You may obtain a copy of the License at
009 *
010 *      http://www.gnu.org/licenses/lgpl.html
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
014 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
015 * License for the specific language governing permissions and limitations
016 * under the License.
017 */
018
019package org.tquadrat.foundation.xml.builder;
020
021import static org.apiguardian.api.API.Status.STABLE;
022import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument;
023import static org.tquadrat.foundation.xml.builder.XMLBuilderUtils.getNMTokenValidator;
024
025import java.net.URI;
026import java.net.URISyntaxException;
027import java.time.Instant;
028import java.time.LocalDate;
029import java.time.LocalDateTime;
030import java.time.ZonedDateTime;
031import java.util.Optional;
032
033import org.apiguardian.api.API;
034import org.tquadrat.foundation.annotation.ClassVersion;
035import org.tquadrat.foundation.xml.builder.internal.XMLDocumentImpl;
036import org.tquadrat.foundation.xml.builder.spi.Document;
037
038/**
039 *  The definition for an XML document.
040 *
041 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
042 *  @version $Id: XMLDocument.java 1071 2023-09-30 01:49:32Z tquadrat $
043 *  @since 0.0.5
044 *
045 *  @UMLGraph.link
046 */
047@SuppressWarnings( {"ClassWithTooManyMethods"} )
048@ClassVersion( sourceVersion = "$Id: XMLDocument.java 1071 2023-09-30 01:49:32Z tquadrat $" )
049@API( status = STABLE, since = "0.0.5" )
050public sealed interface XMLDocument extends Document<XMLElement>
051    permits XMLDocumentImpl
052{
053        /*---------*\
054    ====** Methods **==========================================================
055        \*---------*/
056    /**
057     *  Adds a child to the root element of this document.
058     *
059     *  @param  child    The child to add.
060     *  @return This instance.
061     *  @throws IllegalArgumentException    The child is not allowed for the
062     *      root element of this document, or the root element does not allow
063     *      adding children at all.
064     *  @throws IllegalStateException   The child has already a parent that is
065     *      not the root XML element.
066     */
067    @SuppressWarnings( "UnusedReturnValue" )
068    public default XMLDocument addChild( final XMLElement child ) throws IllegalArgumentException, IllegalStateException
069    {
070        getRootElement().addChild( child );
071
072        //---* Done *----------------------------------------------------------
073        return this;
074    }   //  addChild()
075
076    /**
077     *  Adds a comment to the root element of this document.
078     *
079     *  @param  comment The comment to add.
080     *  @return This instance.
081     *  @throws IllegalArgumentException    The root element does not allow
082     *      adding comments.
083     */
084    @SuppressWarnings( "UnusedReturnValue" )
085    public default XMLDocument addComment( final CharSequence comment ) throws IllegalArgumentException
086    {
087        getRootElement().addComment( comment );
088
089        //---* Done *----------------------------------------------------------
090        return this;
091    }   //  //  addComment()
092
093    /**
094     *  Adds a comment to the document itself.
095     *
096     *  @param  comment The comment to add.
097     *  @return This instance.
098     *  @throws IllegalArgumentException    The document does not allow adding
099     *      comments.
100     */
101    @SuppressWarnings( "UnusedReturnValue" )
102    public default XMLDocument addDocumentComment( final CharSequence comment ) throws IllegalArgumentException
103    {
104        throw new IllegalArgumentException( "No comment allowed for this document" );
105    }   //  addDocumentComment()
106
107    /**
108     *  Adds predefined XML markup to the root element of this document. The
109     *  given markup will not be validated, it just may not be {@code null}. So
110     *  the caller is responsible that it will be proper XML.<br>
111     *  <br>As the markup may be formatted differently (or not formatted at
112     *  all), the pretty printed output may be distorted when this is used.
113     *
114     *  @param  markup  The XML markup.
115     *  @return This instance.
116     *  @throws IllegalArgumentException    The root element does not allow
117     *      adding children at all.
118     */
119    @SuppressWarnings( "UnusedReturnValue" )
120    public default XMLDocument addPredefinedMarkup( final CharSequence markup ) throws IllegalArgumentException
121    {
122        getRootElement().addPredefinedMarkup( markup );
123
124        //---* Done *----------------------------------------------------------
125        return this;
126    }   //  addPredefinedMarkup()
127
128    /**
129     *  Adds a processing instruction to this document.
130     *
131     *  @param  processingInstruction   The procession instruction to add.
132     *  @return This instance.
133     *  @throws IllegalArgumentException    This document does not allow adding
134     *      processing instructions.
135     *  @throws IllegalStateException   The processing instruction has already
136     *      a parent.
137     */
138    @SuppressWarnings( "UnusedReturnValue" )
139    public default XMLDocument addProcessingInstruction( final ProcessingInstruction processingInstruction ) throws IllegalArgumentException, IllegalStateException
140    {
141        throw new IllegalArgumentException( "No processing instructions allowed for this document" );
142    }   //  addProcessingInstruction()
143
144    /**
145     *  Sets the attribute with the given name to the root element of this
146     *  document.<br>
147     *  <br>The method uses
148     *  {@link Boolean#toString(boolean)}
149     *  to convert the provided flag to a {@code String}.
150     *
151     *  @param  name    The name of the attribute; the name is case-sensitive.
152     *  @param  flag    The attribute's value.
153     *  @return This instance.
154     *  @throws IllegalArgumentException    The attribute is not allowed for
155     *      the root element, or the root element does not allow attributes at
156     *      all.
157     */
158    public default XMLDocument setAttribute( final String name, final boolean flag ) throws IllegalArgumentException
159    {
160        getRootElement().setAttribute( name, flag );
161
162        //---* Done *----------------------------------------------------------
163        return this;
164    }   //  setAttribute()
165
166    /**
167     *  Sets the attribute with the given name to the root element of this
168     *  document.<br>
169     *  <br>The method uses
170     *  {@link Boolean#toString()}
171     *  to convert the provided flag to a {@code String}.
172     *
173     *  @param  name    The name of the attribute; the name is case-sensitive.
174     *  @param  flag    The attribute's value; if {@code null} the
175     *      attribute will be removed.
176     *  @return This instance.
177     *  @throws IllegalArgumentException    The attribute is not allowed for
178     *      the root element, or the root element does not allow attributes at
179     *      all.
180     */
181    public default XMLDocument setAttribute( final String name, final Boolean flag ) throws IllegalArgumentException
182    {
183        getRootElement().setAttribute( name, flag );
184
185        //---* Done *----------------------------------------------------------
186        return this;
187    }   //  setAttribute()
188
189    /**
190     *  Sets the attribute with the given name to the root element of this
191     *  document.
192     *
193     *  @param  name    The name of the attribute; the name is case-sensitive.
194     *  @param  value   The attribute's value; if {@code null} the
195     *      attribute will be removed.
196     *  @return This instance.
197     *  @throws IllegalArgumentException    The attribute is not allowed for
198     *      the root element, or the root element does not allow attributes at
199     *      all.
200     */
201    public default XMLDocument setAttribute( final String name, final CharSequence value ) throws IllegalArgumentException
202    {
203        getRootElement().setAttribute( name, value );
204
205        //---* Done *----------------------------------------------------------
206        return this;
207    }   //  setAttribute()
208
209    /**
210     *  Sets the attribute with the given name to the root element of this
211     *  document.
212     *
213     *  @param  name    The name of the attribute; the name is case-sensitive.
214     *  @param  value   The attribute's value; if {@code null} the
215     *      attribute will be removed.
216     *  @param  append  If not
217     *      {@linkplain Optional#empty() empty}, the new value will be appended
218     *      on an already existing one, and this sequence is used as the
219     *      separator.
220     *  @return This instance.
221     *  @throws IllegalArgumentException    The attribute is not allowed for
222     *      the root element, or the root element does not allow attributes at
223     *      all.
224     */
225    @SuppressWarnings( "OptionalUsedAsFieldOrParameterType" )
226    public default XMLDocument setAttribute( final String name, final CharSequence value, final Optional<? extends CharSequence> append ) throws IllegalArgumentException
227    {
228        getRootElement().setAttribute( name, value, append );
229
230        //---* Done *----------------------------------------------------------
231        return this;
232    }   //  setAttribute()
233
234    /**
235     *  Sets the attribute with the given name to the root element of this
236     *  document.<br>
237     *  <br>The method uses
238     *  {@link Double#toString(double)}
239     *  to convert the provided number to a {@code String}.
240     *
241     *  @param  name    The name of the attribute; the name is case-sensitive.
242     *  @param  number   The attribute's value.
243     *  @return This instance.
244     *  @throws IllegalArgumentException    The attribute is not allowed for
245     *      the root element, or the root element does not allow attributes at
246     *      all.
247     */
248    public default XMLDocument setAttribute( final String name, final double number ) throws IllegalArgumentException
249    {
250        getRootElement().setAttribute( name, number );
251
252        //---* Done *----------------------------------------------------------
253        return this;
254    }   //  setAttribute()
255
256    /**
257     *  Sets the attribute with the given name to the root element of this
258     *  document.<br>
259     *  <br>The method uses
260     *  {@link Enum#name()}
261     *  to convert the provided value to a {@code String}.
262     *
263     *  @param  <E> The concrete enum type of {@code value}.
264     *  @param  name    The name of the attribute; the name is case-sensitive.
265     *  @param  enumValue   The attribute's value; if {@code null} the
266     *      attribute will be removed.
267     *  @return This instance.
268     *  @throws IllegalArgumentException    The attribute is not allowed for
269     *      the root element, or the root element does not allow attributes at
270     *      all.
271     */
272    public default <E extends Enum<E>> XMLDocument setAttribute( final String name, final E enumValue ) throws IllegalArgumentException
273    {
274        getRootElement().setAttribute( name, enumValue );
275
276        //---* Done *----------------------------------------------------------
277        return this;
278    }   //  setAttribute()
279
280    /**
281     *  Sets the attribute with the given name to the root element of this
282     *  document.<br>
283     *  <br>The method uses
284     *  {@link Instant#toString()}
285     *  to convert the provided number to a {@code String}.
286     *
287     *  @param  name    The name of the attribute; the name is case-sensitive.
288     *  @param  date    The attribute's value; if {@code null} the
289     *      attribute will be removed.
290     *  @return This instance.
291     *  @throws IllegalArgumentException    The attribute is not allowed for
292     *      the root element, or the root element does not allow attributes at
293     *      all.
294     */
295    public default XMLDocument setAttribute( final String name, final Instant date ) throws IllegalArgumentException
296    {
297        getRootElement().setAttribute( name, date );
298
299        //---* Done *----------------------------------------------------------
300        return this;
301    }   //  setAttribute()
302
303    /**
304     *  Sets the attribute with the given name to the root element of this
305     *  document.<br>
306     *  <br>The method uses
307     *  {@link Integer#toString(int)}
308     *  to convert the provided number to a {@code String}.
309     *
310     *  @param  name    The name of the attribute; the name is case-sensitive.
311     *  @param  number  The attribute's value.
312     *  @return This instance.
313     *  @throws IllegalArgumentException    The attribute is not allowed for
314     *      the root element, or the root element does not allow attributes at
315     *      all.
316     */
317    public default XMLDocument setAttribute( final String name, final int number ) throws IllegalArgumentException
318    {
319        getRootElement().setAttribute( name, number );
320
321        //---* Done *----------------------------------------------------------
322        return this;
323    }   //  setAttribute()
324
325    /**
326     *  Sets the attribute with the given name to the root element of this
327     *  document.<br>
328     *  <br>The method uses
329     *  {@link LocalDate#toString()}
330     *  to convert the provided number to a {@code String}.
331     *
332     *  @param  name    The name of the attribute; the name is case-sensitive.
333     *  @param  date    The attribute's value; if {@code null} the
334     *      attribute will be removed.
335     *  @return This instance.
336     *  @throws IllegalArgumentException    The attribute is not allowed for
337     *      the root element, or the root element does not allow attributes at
338     *      all.
339     */
340    public default XMLDocument setAttribute( final String name, final LocalDate date ) throws IllegalArgumentException
341    {
342        getRootElement().setAttribute( name, date );
343
344        //---* Done *----------------------------------------------------------
345        return this;
346    }   //  setAttribute()
347
348    /**
349     *  Sets the attribute with the given name to the root element of this
350     *  document.<br>
351     *  <br>The method uses
352     *  {@link LocalDateTime#toString()}
353     *  to convert the provided number to a {@code String}.
354     *
355     *  @param  name    The name of the attribute; the name is case-sensitive.
356     *  @param  date    The attribute's value; if {@code null} the
357     *      attribute will be removed.
358     *  @return This instance.
359     *  @throws IllegalArgumentException    The attribute is not allowed for
360     *      the root element, or the root element does not allow attributes at
361     *      all.
362     */
363    public default XMLDocument setAttribute( final String name, final LocalDateTime date ) throws IllegalArgumentException
364    {
365        getRootElement().setAttribute( name, date );
366
367        //---* Done *----------------------------------------------------------
368        return this;
369    }   //  setAttribute()
370
371    /**
372     *  Sets the attribute with the given name to the root element of this
373     *  document.<br>
374     *  <br>The method uses
375     *  {@link Long#toString(long)}
376     *  to convert the provided number to a {@code String}.
377     *
378     *  @param  name    The name of the attribute; the name is case-sensitive.
379     *  @param  number   The attribute's value.
380     *  @return This instance.
381     *  @throws IllegalArgumentException    The attribute is not allowed for
382     *      the root element, or the root element does not allow attributes at
383     *      all.
384     */
385    public default XMLDocument setAttribute( final String name, final long number ) throws IllegalArgumentException
386    {
387        getRootElement().setAttribute( name, number );
388
389        //---* Done *----------------------------------------------------------
390        return this;
391    }   //  setAttribute()
392
393    /**
394     *  Sets the attribute with the given name to the root element of this
395     *  document.<br>
396     *  <br>The method uses
397     *  {@link Number#toString()}
398     *  to convert the provided number to a {@code String}.
399     *
400     *  @param  name    The name of the attribute; the name is case-sensitive.
401     *  @param  number   The attribute's value; if {@code null} the
402     *      attribute will be removed.
403     *  @return This instance.
404     *  @throws IllegalArgumentException    The attribute is not allowed for
405     *      the root element, or the root element does not allow attributes at
406     *      all.
407     */
408    public default XMLDocument setAttribute( final String name, final Number number ) throws IllegalArgumentException
409    {
410        getRootElement().setAttribute( name, number );
411
412        //---* Done *----------------------------------------------------------
413        return this;
414    }   //  setAttribute()
415
416    /**
417     *  Sets the attribute with the given name to the root element of this
418     *  document.<br>
419     *  <br>The method uses
420     *  {@link ZonedDateTime#toString()}
421     *  to convert the provided number to a {@code String}.
422     *
423     *  @param  name    The name of the attribute; the name is case-sensitive.
424     *  @param  date    The attribute's value; if {@code null} the
425     *      attribute will be removed.
426     *  @return This instance.
427     *  @throws IllegalArgumentException    The attribute is not allowed for
428     *      the root element, or the root element does not allow attributes at
429     *      all.
430     */
431    public default XMLDocument setAttribute( final String name, final ZonedDateTime date ) throws IllegalArgumentException
432    {
433        getRootElement().setAttribute( name, date );
434
435        //---* Done *----------------------------------------------------------
436        return this;
437    }   //  setAttribute()
438
439    /**
440     *  Sets the attribute with the given name to the root element of this
441     *  document if the provided value is not empty.<br>
442     *  <br>The method uses
443     *  {@link org.tquadrat.foundation.util.StringUtils#isNotEmpty(CharSequence)}
444     *  to test if the given value is empty.
445     *
446     *  @param  name    The name of the attribute.
447     *  @param  value   The value for the attribute; can be {@code null}.
448     *  @return This instance.
449     *  @throws IllegalArgumentException    The attribute is not allowed for
450     *      the root element, or the root element does not allow attributes at
451     *      all.
452     */
453    @SuppressWarnings( "UnusedReturnValue" )
454    public default XMLDocument setAttributeIfNotEmpty( final String name, final CharSequence value ) throws IllegalArgumentException
455    {
456        getRootElement().setAttributeIfNotEmpty( name, value );
457
458        //---* Done *----------------------------------------------------------
459        return this;
460    }   //  setAttributeIfNotEmpty()
461
462    /**
463     *  Sets the attribute with the given name to the root element of this
464     *  document if the provided value is not empty.
465     *
466     *  @param  name    The name of the attribute.
467     *  @param  optional   The value for the attribute.
468     *  @return This instance.
469     *  @throws IllegalArgumentException    The attribute is not allowed for
470     *      the root element, or the root element does not allow attributes at
471     *      all.
472     */
473    @SuppressWarnings( {"UnusedReturnValue", "OptionalUsedAsFieldOrParameterType"} )
474    public default XMLDocument setAttributeIfNotEmpty( final String name, final Optional<? extends CharSequence> optional ) throws IllegalArgumentException
475    {
476        getRootElement().setAttributeIfNotEmpty( name, optional );
477
478        //---* Done *----------------------------------------------------------
479        return this;
480    }   //  setAttributeIfNotEmpty()
481
482    /**
483     *  Sets the id for the root element of this document.<br>
484     *  <br>The value will be validated using the method that is provided by a
485     *  call to
486     *  {@link XMLBuilderUtils#getNMTokenValidator()}.
487     *
488     *  @param  id  The id.
489     *  @return This instance.
490     *  @throws IllegalArgumentException    The attribute is not allowed for
491     *      the root element, or the root element does not allow attributes at
492     *      all.
493     *
494     *  @see org.tquadrat.foundation.lang.CommonConstants#XMLATTRIBUTE_Id
495     */
496    public default XMLDocument setId( final String id ) throws IllegalArgumentException
497    {
498        if( !getNMTokenValidator().test( requireNotEmptyArgument( id, "id" ) ) ) throw new IllegalArgumentException( "Invalid id: %s".formatted( id ) );
499        getRootElement().setId( id );
500
501        //---* Done *----------------------------------------------------------
502        return this;
503    }   //  setId()
504
505    /**
506     *  Sets the given namespace to the root element of this document.
507     *
508     *  @param  identifier  The namespace identifier.
509     *  @return This instance.
510     *  @throws IllegalArgumentException    Namespaces are not allowed for this
511     *      element.
512     *  @throws URISyntaxException  The provided URI String is invalid.
513     */
514    public default XMLDocument setNamespace( final String identifier ) throws IllegalArgumentException, URISyntaxException
515    {
516        getRootElement().setNamespace( new Namespace( identifier ) );
517
518        //---* Done *----------------------------------------------------------
519        return this;
520    }   //  setNamespace()
521
522    /**
523     *  Sets the given namespace to the root element of this document.
524     *
525     *  @param  identifier  The namespace identifier.
526     *  @return This instance.
527     *  @throws IllegalArgumentException    Namespaces are not allowed for this
528     *      element.
529     */
530    public default XMLDocument setNamespace( final URI identifier ) throws IllegalArgumentException
531    {
532        getRootElement().setNamespace( new Namespace( identifier ) );
533
534        //---* Done *----------------------------------------------------------
535        return this;
536    }   //  setNamespace()
537
538    /**
539     *  Sets the given namespace to the root element of this document.<br>
540     *  <br>The given prefix is validated using the method that is
541     *  provided by
542     *  {@link XMLBuilderUtils#getPrefixValidator()}.
543     *
544     *  @param  prefix  The namespace prefix.
545     *  @param  identifier  The namespace identifier.
546     *  @return This instance.
547     *  @throws IllegalArgumentException    Namespaces are not allowed for this
548     *      element.
549     *  @throws URISyntaxException  The provided URI String is invalid.
550     */
551    public default XMLDocument setNamespace( final String prefix, final String identifier ) throws IllegalArgumentException, URISyntaxException
552    {
553        getRootElement().setNamespace( new Namespace( prefix, identifier ) );
554
555        //---* Done *----------------------------------------------------------
556        return this;
557    }   //  setNamespace()
558
559    /**
560     *  Sets the given namespace to the root element of this document.<br>
561     *  <br>The given prefix is validated using the method that is
562     *  provided by
563     *  {@link XMLBuilderUtils#getPrefixValidator()}.
564     *
565     *  @param  prefix  The namespace prefix.
566     *  @param  identifier  The namespace identifier.
567     *  @return This instance.
568     *  @throws IllegalArgumentException    Namespaces are not allowed for this
569     *      element.
570     */
571    public default XMLDocument setNamespace( final String prefix, final URI identifier ) throws IllegalArgumentException
572    {
573        getRootElement().setNamespace( new Namespace( prefix, identifier ) );
574
575        //---* Done *----------------------------------------------------------
576        return this;
577    }   //  setNamespace()
578
579    /**
580     *  Sets the given namespace to the root element of this document.
581     *
582     *  @param  namespace   The namespace.
583     *  @return This instance.
584     *  @throws IllegalArgumentException    Namespaces are not allowed for this
585     *      element.
586     */
587    @SuppressWarnings( "UseOfConcreteClass" )
588    public default XMLDocument setNamespace( final Namespace namespace ) throws IllegalArgumentException
589    {
590        getRootElement().setNamespace( namespace );
591
592        //---* Done *----------------------------------------------------------
593        return this;
594    }   //  setNamespace()
595}
596//  interface XMLDocument
597
598/*
599 *  End of File
600 */