001/* 002 * ============================================================================ 003 * Copyright © 2002-2024 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.Objects.nonNull; 022import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 023import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument; 024import static org.tquadrat.foundation.xml.builder.XMLBuilderUtils.getElementNameValidator; 025import static org.tquadrat.foundation.xml.builder.XMLElement.Flags.ALLOWS_CHILDREN; 026import static org.tquadrat.foundation.xml.builder.XMLElement.Flags.ALLOWS_TEXT; 027import static org.tquadrat.foundation.xml.builder.XMLElement.Flags.VALIDATES_ATTRIBUTES; 028import static org.tquadrat.foundation.xml.builder.XMLElement.Flags.VALIDATES_CHILDREN; 029 030import java.net.URI; 031import java.net.URISyntaxException; 032import java.util.Collection; 033import java.util.Comparator; 034import java.util.EnumSet; 035import java.util.Map; 036import java.util.Optional; 037import java.util.Set; 038 039import org.apiguardian.api.API; 040import org.tquadrat.foundation.annotation.ClassVersion; 041import org.tquadrat.foundation.xml.builder.Namespace; 042import org.tquadrat.foundation.xml.builder.XMLBuilderUtils; 043import org.tquadrat.foundation.xml.builder.XMLElement; 044import org.tquadrat.foundation.xml.builder.spi.AttributeSupport; 045import org.tquadrat.foundation.xml.builder.spi.ChildSupport; 046import org.tquadrat.foundation.xml.builder.spi.Element; 047import org.tquadrat.foundation.xml.builder.spi.InvalidXMLNameException; 048 049/** 050 * An implementation of 051 * {@link XMLElement} 052 * that supports attributes, namespaces, children, text, {@code CDATA} and 053 * comments. 054 * 055 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 056 * @version $Id: XMLElementImpl.java 1086 2024-01-05 23:18:33Z tquadrat $ 057 * @since 0.0.5 058 * 059 * @UMLGraph.link 060 */ 061@SuppressWarnings( "removal" ) 062@ClassVersion( sourceVersion = "$Id: XMLElementImpl.java 1086 2024-01-05 23:18:33Z tquadrat $" ) 063@API( status = INTERNAL, since = "0.0.5" ) 064public sealed class XMLElementImpl implements XMLElement 065 permits org.tquadrat.foundation.xml.builder.spi.XMLElementAdapter 066{ 067 /*------------*\ 068 ====** Attributes **======================================================= 069 \*------------*/ 070 /** 071 * The attribute support. 072 */ 073 @SuppressWarnings( "UseOfConcreteClass" ) 074 private final AttributeSupport m_Attributes; 075 076 /** 077 * The child support. 078 */ 079 @SuppressWarnings( "UseOfConcreteClass" ) 080 private final ChildSupport m_Children; 081 082 /** 083 * The element name. 084 */ 085 private final String m_ElementName; 086 087 /** 088 * The parent element. 089 */ 090 private Element m_Parent; 091 092 /*--------------*\ 093 ====** Constructors **===================================================== 094 \*--------------*/ 095 /** 096 * <p>{@summary Creates a new {@code XMLElementImpl} instance.}</p> 097 * <p>The given element name is validated using the method that is 098 * provided by 099 * {@link XMLBuilderUtils#getElementNameValidator()}.</p> 100 * <p>The new element allows attributes and children, but will not 101 * validate them. It also allows text.</p> 102 * 103 * @note This constructor is mainly used by the factory methods in 104 * {@link XMLBuilderUtils} 105 * for on-the-fly XML generation. 106 * 107 * @param elementName The element name. 108 */ 109 @SuppressWarnings( "ThisEscapedInObjectConstruction" ) 110 public XMLElementImpl( final String elementName ) 111 { 112 m_ElementName = requireNotEmptyArgument( elementName, "elementName" ); 113 if( !getElementNameValidator().test( elementName ) ) throw new InvalidXMLNameException( elementName ); 114 115 m_Attributes = new AttributeSupport( this, false ); 116 m_Children = new ChildSupport( this, false, true, true, XMLBuilderUtils::escapeXML ); 117 } // XMLElementImpl() 118 119 /** 120 * <p>{@summary Creates a new {@code XMLElementImpl} instance.}</p> 121 * <p>The given element name is validated using the method that is 122 * provided by 123 * {@link XMLBuilderUtils#getElementNameValidator()}.</p> 124 * 125 * @note This constructor is used for the implementation of XML 126 * specialisations, like SVG or HTML (although this not really XML). 127 * It is made accessible through 128 * {@link org.tquadrat.foundation.xml.builder.spi.XMLElementAdapter} 129 * 130 * @param elementName The element name. 131 * @param flags The configuration flags for the new element. 132 * 133 * @see org.tquadrat.foundation.xml.builder.XMLElement.Flags 134 * @see AttributeSupport#registerAttributes(String...) 135 * @see AttributeSupport#registerSequence(String...) 136 * @see ChildSupport#registerChildren(String...) 137 */ 138 @SuppressWarnings( "ThisEscapedInObjectConstruction" ) 139 protected XMLElementImpl( final String elementName, final Set<Flags> flags ) 140 { 141 m_ElementName = requireNotEmptyArgument( elementName, "elementName" ); 142 if( !getElementNameValidator().test( elementName ) ) throw new InvalidXMLNameException( elementName ); 143 144 final var checkAttributes = requireNonNullArgument( flags, "flags" ).contains( VALIDATES_ATTRIBUTES ); 145 final var checkChildren = flags.contains( VALIDATES_CHILDREN ); 146 final var allowChildren = checkChildren || flags.contains( ALLOWS_CHILDREN ); 147 final var allowText = flags.contains( ALLOWS_TEXT ); 148 149 m_Attributes = new AttributeSupport( this, checkAttributes ); 150 m_Children = new ChildSupport( this, checkChildren, allowChildren, allowText, XMLBuilderUtils::escapeXML ); 151 } // XMLElementImpl() 152 153 /*---------*\ 154 ====** Methods **========================================================== 155 \*---------*/ 156 /** 157 * {@inheritDoc} 158 */ 159 @Override 160 public final XMLElement addCDATA( final CharSequence text ) throws IllegalArgumentException 161 { 162 m_Children.addCDATA( text ); 163 164 //---* Done *---------------------------------------------------------- 165 return this; 166 } // addCDATA() 167 168 /** 169 * {@inheritDoc} 170 */ 171 @Override 172 public final <E extends XMLElement> XMLElement addChild( final E child ) throws IllegalArgumentException, IllegalStateException 173 { 174 m_Children.addChild( child ); 175 176 //---* Done *---------------------------------------------------------- 177 return this; 178 } // addChild() 179 180 /** 181 * {@inheritDoc} 182 */ 183 @Override 184 public final XMLElement addComment( final CharSequence comment ) throws IllegalArgumentException 185 { 186 m_Children.addComment( comment ); 187 188 //---* Done *---------------------------------------------------------- 189 return this; 190 } // addComment() 191 192 /** 193 * {@inheritDoc} 194 */ 195 @Override 196 public final XMLElement addPredefinedMarkup( final CharSequence markup ) throws IllegalArgumentException 197 { 198 m_Children.addPredefinedMarkup( markup ); 199 200 //---* Done *---------------------------------------------------------- 201 return this; 202 } // addPredefinedMarkup() 203 204 /** 205 * {@inheritDoc} 206 */ 207 @Override 208 public final XMLElement addText( final CharSequence text ) throws IllegalArgumentException 209 { 210 m_Children.addText( text ); 211 212 //---* Done *---------------------------------------------------------- 213 return this; 214 } // addText() 215 216 /** 217 * {@inheritDoc} 218 */ 219 @Override 220 public final Optional<String> getAttribute( final String name ) { return m_Attributes.getAttribute( name ); } 221 222 /** 223 * {@inheritDoc} 224 */ 225 @Override 226 public Map<String,String> getAttributes() { return m_Attributes.getAttributes(); } 227 228 /** 229 * {@inheritDoc} 230 */ 231 @Override 232 public Collection<? extends Element> getChildren() 233 { 234 final var retValue = m_Children.getChildren(); 235 236 //---* Done *---------------------------------------------------------- 237 return retValue; 238 } // getChildren() 239 240 /** 241 * {@inheritDoc} 242 */ 243 @Override 244 public final String getElementName() { return m_ElementName; } 245 246 /** 247 * {@inheritDoc} 248 */ 249 @Override 250 public final Set<Flags> getFlags() 251 { 252 final var retValue = EnumSet.noneOf( Flags.class ); 253 if( nonNull( m_Children ) ) 254 { 255 if( m_Children.allowsChildren() ) retValue.add( ALLOWS_CHILDREN ); 256 if( m_Children.allowsText() ) retValue.add( ALLOWS_TEXT ); 257 if( m_Children.checksIfValid() ) retValue.add( VALIDATES_CHILDREN ); 258 } 259 260 if( m_Attributes.checksIfValid() ) retValue.add( VALIDATES_ATTRIBUTES ); 261 262 //---* Done *---------------------------------------------------------- 263 return retValue; 264 } // getFlags() 265 266 /** 267 * {@inheritDoc} 268 */ 269 @Override 270 public final Collection<Namespace> getNamespaces() { return m_Attributes.getNamespaces(); } 271 272 /** 273 * {@inheritDoc} 274 */ 275 @Override 276 public final Optional<Element> getParent() { return Optional.ofNullable( m_Parent ); } 277 278 /** 279 * Returns the attribute sort order. 280 * 281 * @return The comparator that determines the attribute's sequence. 282 */ 283 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 284 public final Comparator<String> getSortOrder() { return m_Attributes.getSortOrder(); } 285 286 /** 287 * {@inheritDoc} 288 */ 289 @Override 290 public boolean hasChildren() { return m_Children.hasChildren(); } 291 292 /** 293 * <p>{@summary Registers an attribute sequence for this element}; this 294 * modifies any sort order that was previously set.</p> 295 * <p>The names for the attributes are not validated; in particular, it 296 * is not checked whether an attribute is listed as valid.</p> 297 * 298 * @param attributes The names of the attributes in the desired 299 * sequence. 300 */ 301 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 302 public final void registerAttributeSequence( final String... attributes ) 303 { 304 m_Attributes.registerSequence( attributes ); 305 } // registerAttributeSequence() 306 307 /** 308 * Registers an attribute sequence for this element; this modifies any 309 * sort order that was previously set. 310 * 311 * @param sortOrder The sort order for the attributes. 312 */ 313 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 314 public final void registerAttributeSequence( final Comparator<String> sortOrder ) 315 { 316 m_Attributes.setSortOrder( sortOrder ); 317 } // registerAttributeSequence() 318 319 /** 320 * Registers the valid attributes for this element.<br> 321 * <br>Nothing happens if 322 * {@link AttributeSupport#checksIfValid()} 323 * returns {@code false}, although a call to this method is obsolete then. 324 * 325 * @note The given attributes will be <i>added</i> to the already 326 * existing ones! 327 * 328 * @param attributes The names of the valid attributes. 329 * @throws InvalidXMLNameException One of the attribute names is invalid. 330 */ 331 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 332 public final void registerValidAttributes( final String... attributes ) 333 { 334 m_Attributes.registerAttributes( attributes ); 335 } // registerValidAttributes() 336 337 /** 338 * Registers the element names of valid child elements for this element. 339 * 340 * @note The given children will be <i>added</i> to the already existing 341 * ones! 342 * 343 * @param children The element names of the valid children. 344 */ 345 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 346 public final void registerValidChildren( final String... children ) 347 { 348 if( nonNull( m_Children ) ) m_Children.registerChildren( children ); 349 } // registerValidChildren() 350 351 /** 352 * Returns the list of the registered attributes. 353 * 354 * @return The registered attributes. 355 */ 356 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 357 public final Collection<String> retrieveValidAttributes() { return m_Attributes.retrieveValidAttributes(); } 358 359 /** 360 * Returns the list of the registered children. 361 * 362 * @return The registered children. 363 */ 364 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 365 public final Collection<String> retrieveValidChildren() { return m_Children.retrieveValidChildren(); } 366 367 /** 368 * {@inheritDoc} 369 * <p>The given attribute name is validated using the method that is 370 * provided by 371 * {@link XMLBuilderUtils#getAttributeNameValidator()}.</p> 372 */ 373 @Override 374 public final XMLElement setAttribute( final String name, final CharSequence value, final Optional<? extends CharSequence> append ) throws IllegalArgumentException 375 { 376 m_Attributes.setAttribute( name, value, append ); 377 378 //---* Done *---------------------------------------------------------- 379 return this; 380 } // setAttribute() 381 382 /** 383 * {@inheritDoc} 384 */ 385 @Override 386 public final XMLElement setNamespace( final String identifier ) throws IllegalArgumentException, URISyntaxException 387 { 388 m_Attributes.setNamespace( identifier ); 389 390 //---* Done *---------------------------------------------------------- 391 return this; 392 } // setNamespace() 393 394 /** 395 * {@inheritDoc} 396 */ 397 @Override 398 public final XMLElement setNamespace( final URI identifier ) throws IllegalArgumentException 399 { 400 m_Attributes.setNamespace( identifier ); 401 402 //---* Done *---------------------------------------------------------- 403 return this; 404 } // setNamespace() 405 406 /** 407 * {@inheritDoc} 408 * <p>The given prefix is validated using the method that is 409 * provided by 410 * {@link XMLBuilderUtils#getPrefixValidator()}.</p> 411 */ 412 @Override 413 public final XMLElement setNamespace( final String prefix, final String identifier ) throws IllegalArgumentException, URISyntaxException 414 { 415 m_Attributes.setNamespace( prefix, identifier ); 416 417 //---* Done *---------------------------------------------------------- 418 return this; 419 } // setNamespace() 420 421 /** 422 * {@inheritDoc} 423 * <p>The given prefix is validated using the method that is 424 * provided by 425 * {@link XMLBuilderUtils#getPrefixValidator()}.</p> 426 */ 427 @Override 428 public final XMLElement setNamespace( final String prefix, final URI identifier ) throws IllegalArgumentException 429 { 430 m_Attributes.setNamespace( prefix, identifier ); 431 432 //---* Done *---------------------------------------------------------- 433 return this; 434 } // setNamespace() 435 436 /** 437 * {@inheritDoc} 438 */ 439 @SuppressWarnings( "UseOfConcreteClass" ) 440 @Override 441 public final XMLElement setNamespace( final Namespace namespace ) throws IllegalArgumentException 442 { 443 m_Attributes.setNamespace( namespace ); 444 445 //---* Done *---------------------------------------------------------- 446 return this; 447 } // setNamespace() 448 449 /** 450 * {@inheritDoc} 451 */ 452 @Override 453 public final <E extends Element> void setParent( final E parent ) 454 { 455 m_Parent = requireNonNullArgument( parent, "parent" ); 456 } // setParent() 457 458 /** 459 * {@inheritDoc} 460 */ 461 @Override 462 public String toString() { return toString( 0, true ); } 463} 464// class XMLElementImpl 465 466/* 467 * End of File 468 */