001/* 002 * ============================================================================ 003 * Copyright © 2002-2023 by Thomas Thrien. 004 * All Rights Reserved. 005 * ============================================================================ 006 * Licensed to the public under the agreements of the GNU Lesser General Public 007 * License, version 3.0 (the "License"). You may obtain a copy of the License at 008 * 009 * http://www.gnu.org/licenses/lgpl.html 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, WITHOUT 013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 014 * License for the specific language governing permissions and limitations 015 * under the License. 016 */ 017 018package org.tquadrat.foundation.xml.builder.internal; 019 020import static org.apiguardian.api.API.Status.INTERNAL; 021import static org.tquadrat.foundation.lang.CommonConstants.UTF8; 022import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 023import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument; 024import static org.tquadrat.foundation.util.StringUtils.isNotEmptyOrBlank; 025import static org.tquadrat.foundation.xml.builder.XMLBuilderUtils.composeXMLHeader; 026 027import java.net.URI; 028import java.nio.charset.Charset; 029import java.util.ArrayList; 030import java.util.Collection; 031import java.util.Comparator; 032import java.util.List; 033 034import org.apiguardian.api.API; 035import org.tquadrat.foundation.annotation.ClassVersion; 036import org.tquadrat.foundation.xml.builder.ProcessingInstruction; 037import org.tquadrat.foundation.xml.builder.XMLDocument; 038import org.tquadrat.foundation.xml.builder.XMLElement; 039import org.tquadrat.foundation.xml.builder.spi.Element; 040import org.tquadrat.foundation.xml.builder.spi.InvalidXMLNameException; 041 042/** 043 * The implementation for the interface 044 * {@link XMLDocument}.<br> 045 * <br>It allows document comments and processing instructions to be added. 046 * 047 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 048 * @version $Id: XMLDocumentImpl.java 1071 2023-09-30 01:49:32Z tquadrat $ 049 * @since 0.0.5 050 * 051 * @UMLGraph.link 052 */ 053@SuppressWarnings( "ClassWithTooManyConstructors" ) 054@ClassVersion( sourceVersion = "$Id: XMLDocumentImpl.java 1071 2023-09-30 01:49:32Z tquadrat $" ) 055@API( status = INTERNAL, since = "0.0.5" ) 056public final class XMLDocumentImpl implements XMLDocument 057{ 058 /*------------*\ 059 ====** Attributes **======================================================= 060 \*------------*/ 061 /** 062 * The child elements for this document. 063 */ 064 @SuppressWarnings( "TypeMayBeWeakened" ) 065 private final List<Element> m_Children = new ArrayList<>(); 066 067 /** 068 * The root element for this document. 069 */ 070 @SuppressWarnings( "UseOfConcreteClass" ) 071 private final XMLElementImpl m_RootElement; 072 073 /*--------------*\ 074 ====** Constructors **===================================================== 075 \*--------------*/ 076 /** 077 * Creates a new {@code XMLDocumentImpl} instance.<br> 078 * <br>The resulting document will do not have an explicit doc type, the 079 * root element will be {@code <root>}. The encoding is defined as 080 * UTF-8.<br> 081 * <br>Basically, this document would have the DTD 082 * <pre>{@code <!ELEMENT root ANY>}</pre>. 083 */ 084 public XMLDocumentImpl() { this( "root" ); } 085 086 /** 087 * Creates a new {@code XMLDocumentImpl} instance.<br> 088 * <br>The resulting document will do not have an explicit doc type, the 089 * encoding is defined as UTF-8.<br> 090 * <br>The given root element name is validated using the method that is 091 * provided by 092 * {@link org.tquadrat.foundation.xml.builder.XMLBuilderUtils#getElementNameValidator()}. 093 * 094 * @param rootElementName The name of the root element for this document. 095 */ 096 public XMLDocumentImpl( final String rootElementName ) 097 { 098 this( new XMLElementImpl( requireNotEmptyArgument( rootElementName, "rootElementName" ) ), true ); 099 } // XMLDocumentImpl() 100 101 /** 102 * Creates a new {@code XMLDocumentImpl} instance.<br> 103 * <br>The resulting document will do not have an explicit doc type, the 104 * encoding is defined as UTF-8. 105 * 106 * @param rootElement The root element for this document. 107 * @param standalone {@code true} if the XML document is standalone, 108 * {@code false} if not. 109 */ 110 public XMLDocumentImpl( final XMLElement rootElement, final boolean standalone ) 111 { 112 m_RootElement = (XMLElementImpl) requireNonNullArgument( rootElement, "rootElement" ); 113 addProcessingInstruction( composeXMLHeader( UTF8, standalone ) ); 114 } // XMLDocumentBase() 115 116 /** 117 * Creates a new {@code XMLDocumentImpl} instance.<br> 118 * <br>The given root element name is validated using the method that is 119 * provided by 120 * {@link org.tquadrat.foundation.xml.builder.XMLBuilderUtils#getElementNameValidator()}. 121 * 122 * @param rootElementName The name for the root element for this 123 * document. 124 * @param encoding The encoding for the new XML document. 125 * @param name The name for the DTD. 126 * @param uri The URI for the DTD. 127 */ 128 public XMLDocumentImpl( final String rootElementName, final Charset encoding, final String name, final URI uri ) 129 { 130 this( new XMLElementImpl( requireNotEmptyArgument( rootElementName, "rootElementName" ) ), encoding, name, uri ); 131 } // XMLDocumentImpl() 132 133 /** 134 * Creates a new {@code XMLDocumentImpl} instance. 135 * 136 * @param rootElement The root element for this document. 137 * @param encoding The encoding for the new XML document. 138 * @param name The name for the DTD. 139 * @param uri The URI for the DTD. 140 */ 141 public XMLDocumentImpl( final XMLElement rootElement, final Charset encoding, final String name, final URI uri ) 142 { 143 m_RootElement = (XMLElementImpl) requireNonNullArgument( rootElement, "rootElement" ); 144 addProcessingInstruction( composeXMLHeader( encoding, false ) ); 145 final var docType = new DocType( m_RootElement.getElementName(), requireNotEmptyArgument( name, "name" ), requireNonNullArgument( uri, "uri" ) ); 146 addDocumentChild( docType ); 147 } // XMLDocumentBase() 148 149 /** 150 * Creates a new {@code XMLDocumentImpl} instance.<br> 151 * <br>The given element name is validated using the method that is 152 * provided by 153 * {@link org.tquadrat.foundation.xml.builder.XMLBuilderUtils#getElementNameValidator()}. 154 * 155 * @param rootElementName The name of the root element for this document. 156 * @param encoding The encoding for the new XML document. 157 * @param uri The URI for the DTD. 158 */ 159 public XMLDocumentImpl( final String rootElementName, final Charset encoding, final URI uri ) 160 { 161 this( new XMLElementImpl( requireNotEmptyArgument( rootElementName, "rootElementName" ) ), encoding, uri ); 162 } // XMLDocumentImpl() 163 164 /** 165 * Creates a new {@code XMLDocumentImpl} instance. 166 * 167 * @param rootElement The root element for this document. 168 * @param encoding The encoding for the new XML document. 169 * @param uri The URI for the DTD. 170 */ 171 public XMLDocumentImpl( final XMLElement rootElement, final Charset encoding, final URI uri ) 172 { 173 m_RootElement = (XMLElementImpl) requireNonNullArgument( rootElement, "rootElement" ); 174 addProcessingInstruction( composeXMLHeader( encoding, false ) ); 175 final var docType = new DocType( rootElement.getElementName(), requireNonNullArgument( uri, "uri" ) ); 176 addDocumentChild( docType ); 177 } // XMLDocumentImpl() 178 179 /*---------*\ 180 ====** Methods **========================================================== 181 \*---------*/ 182 /** 183 * Adds a child to the document itself, <i>not</i> to the root element. 184 * 185 * @param <E> The type of the child to add. 186 * @param child The element to add. 187 * @return This instance. 188 * @throws IllegalStateException The child has already a parent that is 189 * not this document. 190 */ 191 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 192 public final <E extends Element> XMLDocument addDocumentChild( final E child ) throws IllegalStateException 193 { 194 if( child.getParent().isPresent() && (child.getParent().get() != m_RootElement) ) 195 { 196 throw new IllegalStateException( "The child has already a parent" ); 197 } 198 m_Children.add( child ); 199 child.setParent( m_RootElement ); 200 201 //---* Done *---------------------------------------------------------- 202 return this; 203 } // addDocumentChild() 204 205 /** 206 * {@inheritDoc} 207 */ 208 @Override 209 public final XMLDocument addDocumentComment( final CharSequence comment ) throws IllegalArgumentException 210 { 211 if( isNotEmptyOrBlank( comment ) ) addDocumentChild( new Comment( comment ) ); 212 213 //---* Done *---------------------------------------------------------- 214 return this; 215 } // addDocumentComment() 216 217 /** 218 * {@inheritDoc} 219 */ 220 @Override 221 public final XMLDocument addProcessingInstruction( final ProcessingInstruction processingInstruction ) throws IllegalArgumentException, IllegalStateException 222 { 223 return addDocumentChild( processingInstruction ); 224 } // addProcessingInstruction() 225 226 /** 227 * {@inheritDoc} 228 */ 229 @Override 230 public final Collection<? extends Element> getChildren() 231 { 232 final Collection<Element> list = new ArrayList<>( m_Children.size() + 1 ); 233 list.addAll( m_Children ); 234 list.add( m_RootElement ); 235 236 final Collection<Element> retValue = List.copyOf( list ); 237 238 //---* Done *---------------------------------------------------------- 239 return retValue; 240 } // getChildren() 241 242 /** 243 * {@inheritDoc} 244 */ 245 @Override 246 public final XMLElement getRootElement() { return m_RootElement; } 247 248 /** 249 * Registers an attribute sequence for the root element of this document; 250 * this modifies any sort order that was previously set.<br> 251 * <br>The names for the attributes are not validated; in particular, it 252 * is not checked whether an attribute is listed as valid. 253 * 254 * @param attributes The names of the attributes in the desired 255 * sequence. 256 */ 257 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 258 public final void registerAttributeSequence( final String... attributes ) 259 { 260 m_RootElement.registerAttributeSequence( attributes ); 261 } // registerAttributeSequence() 262 263 /** 264 * Registers an attribute sequence for the root element of this document; 265 * this modifies any sort order that was previously set. 266 * 267 * @param sortOrder The sort order for the attributes. 268 */ 269 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 270 public final void registerAttributeSequence( final Comparator<String> sortOrder ) 271 { 272 m_RootElement.registerAttributeSequence( sortOrder ); 273 } // registerAttributeSequence() 274 275 /** 276 * Registers the valid attributes for the root element of this 277 * document. 278 * 279 * @note The given attributes will be <i>added</i> to the already 280 * existing ones! 281 * 282 * @param attributes The names of the valid attributes. 283 * @throws InvalidXMLNameException One of the attribute names is invalid. 284 */ 285 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 286 public final void registerValidAttributes( final String... attributes ) 287 { 288 m_RootElement.registerValidAttributes( attributes ); 289 } // registerValidAttributes() 290 291 /** 292 * Registers the element names of valid child elements for this document. 293 * 294 * @note The given children will be <i>added</i> to the already existing 295 * ones! 296 * 297 * @param children The element names of the valid children. 298 */ 299 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 300 public final void registerValidChildren( final String... children ) 301 { 302 m_RootElement.registerValidChildren( children ); 303 } // registerValidChildren() 304 305 /** 306 * {@inheritDoc} 307 */ 308 @Override 309 public final String toString() { return toString( true ); } 310} 311// class XMLDocumentImpl 312 313/* 314 * End of File 315 */