001/* 002 * ============================================================================ 003 * Copyright © 2002-2025 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.config.internal; 020 021import static java.lang.String.format; 022import static java.lang.String.join; 023import static java.lang.System.out; 024import static java.util.Locale.ROOT; 025import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI; 026import static javax.xml.stream.XMLStreamConstants.ATTRIBUTE; 027import static javax.xml.stream.XMLStreamConstants.CDATA; 028import static javax.xml.stream.XMLStreamConstants.CHARACTERS; 029import static javax.xml.stream.XMLStreamConstants.COMMENT; 030import static javax.xml.stream.XMLStreamConstants.DTD; 031import static javax.xml.stream.XMLStreamConstants.END_DOCUMENT; 032import static javax.xml.stream.XMLStreamConstants.END_ELEMENT; 033import static javax.xml.stream.XMLStreamConstants.ENTITY_DECLARATION; 034import static javax.xml.stream.XMLStreamConstants.NAMESPACE; 035import static javax.xml.stream.XMLStreamConstants.NOTATION_DECLARATION; 036import static javax.xml.stream.XMLStreamConstants.PROCESSING_INSTRUCTION; 037import static javax.xml.stream.XMLStreamConstants.SPACE; 038import static javax.xml.stream.XMLStreamConstants.START_DOCUMENT; 039import static javax.xml.stream.XMLStreamConstants.START_ELEMENT; 040import static org.apiguardian.api.API.Status.INTERNAL; 041import static org.tquadrat.foundation.config.internal.ClassRegistry.m_HandlerClasses; 042import static org.tquadrat.foundation.config.spi.CLIDefinition.validateOptionName; 043import static org.tquadrat.foundation.lang.CommonConstants.UTF8; 044import static org.tquadrat.foundation.lang.CommonConstants.XMLATTRIBUTE_Name; 045import static org.tquadrat.foundation.lang.Objects.isNull; 046import static org.tquadrat.foundation.lang.Objects.nonNull; 047import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 048import static org.tquadrat.foundation.util.JavaUtils.isValidName; 049import static org.tquadrat.foundation.util.JavaUtils.retrieveMethod; 050import static org.tquadrat.foundation.util.StringUtils.isEmpty; 051import static org.tquadrat.foundation.util.StringUtils.isEmptyOrBlank; 052import static org.tquadrat.foundation.util.StringUtils.isNotEmptyOrBlank; 053 054import javax.xml.stream.Location; 055import javax.xml.stream.XMLEventReader; 056import javax.xml.stream.XMLInputFactory; 057import javax.xml.stream.XMLResolver; 058import javax.xml.stream.XMLStreamException; 059import javax.xml.stream.events.Attribute; 060import javax.xml.stream.events.StartElement; 061import javax.xml.transform.stax.StAXSource; 062import javax.xml.validation.SchemaFactory; 063import java.io.IOException; 064import java.io.InputStream; 065import java.io.StringReader; 066import java.lang.reflect.Constructor; 067import java.lang.reflect.InvocationTargetException; 068import java.net.URL; 069import java.util.ArrayList; 070import java.util.HashMap; 071import java.util.HashSet; 072import java.util.List; 073import java.util.Map; 074import java.util.Optional; 075import java.util.function.BiConsumer; 076 077import org.apiguardian.api.API; 078import org.tquadrat.foundation.annotation.ClassVersion; 079import org.tquadrat.foundation.annotation.NotRecord; 080import org.tquadrat.foundation.config.cli.CmdLineValueHandler; 081import org.tquadrat.foundation.config.cli.EnumValueHandler; 082import org.tquadrat.foundation.config.cli.SimpleCmdLineValueHandler; 083import org.tquadrat.foundation.config.spi.CLIArgumentDefinition; 084import org.tquadrat.foundation.config.spi.CLIDefinition; 085import org.tquadrat.foundation.config.spi.CLIOptionDefinition; 086import org.tquadrat.foundation.lang.StringConverter; 087import org.xml.sax.SAXException; 088import org.xml.sax.SAXParseException; 089 090/** 091 * Parses an XML CLI definition file. 092 * 093 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 094 * @version $Id: CLIDefinitionParser.java 1151 2025-10-01 21:32:15Z tquadrat $ 095 * @since 0.0.1 096 * 097 * @UMLGraph.link 098 */ 099@SuppressWarnings( {"OverlyComplexClass", "ClassWithTooManyMethods"} ) 100@ClassVersion( sourceVersion = "$Id: CLIDefinitionParser.java 1151 2025-10-01 21:32:15Z tquadrat $" ) 101@API( status = INTERNAL, since = "0.0.1" ) 102public final class CLIDefinitionParser 103{ 104 /*---------------*\ 105 ====** Inner Classes **==================================================== 106 \*---------------*/ 107 /** 108 * An implementation of 109 * {@link Location}. 110 * that is based on an instance of 111 * {@link SAXParseException}. 112 * 113 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 114 * @version $Id: CLIDefinitionParser.java 1151 2025-10-01 21:32:15Z tquadrat $ 115 * @since 0.0.1 116 * 117 * @UMLGraph.link 118 */ 119 @ClassVersion( sourceVersion = "$Id: CLIDefinitionParser.java 1151 2025-10-01 21:32:15Z tquadrat $" ) 120 @API( status = INTERNAL, since = "0.0.1" ) 121 @NotRecord 122 public static final class ExceptionLocation implements Location 123 { 124 /*------------*\ 125 ====** Attributes **=================================================== 126 \*------------*/ 127 /** 128 * The exception that provides the data. 129 */ 130 private final SAXParseException m_Exception; 131 132 /*--------------*\ 133 ====** Constructors **================================================= 134 \*--------------*/ 135 /** 136 * Creates a new {@code ExceptionLocation} instance from the given 137 * instance of 138 * {@link SAXParseException}. 139 * 140 * @param exception The exception. 141 */ 142 public ExceptionLocation( final SAXParseException exception ) { m_Exception = requireNonNullArgument( exception, "exception" ); } 143 144 /*---------*\ 145 ====** Methods **========================================================== 146 \*---------*/ 147 /** 148 * {@inheritDoc} 149 */ 150 @Override 151 public final int getCharacterOffset() { return -1; } 152 153 /** 154 * {@inheritDoc} 155 */ 156 @Override 157 public final int getColumnNumber() {return m_Exception.getColumnNumber(); } 158 159 /** 160 * {@inheritDoc} 161 */ 162 @Override 163 public final int getLineNumber() {return m_Exception.getLineNumber(); } 164 165 /** 166 * {@inheritDoc} 167 */ 168 @Override 169 public final String getPublicId() {return m_Exception.getPublicId(); } 170 171 /** 172 * {@inheritDoc} 173 */ 174 @Override 175 public final String getSystemId() {return m_Exception.getSystemId(); } 176 } 177 // class ExceptionLocation 178 179 /** 180 * An implementation of 181 * {@link XMLResolver} 182 * for this parser. 183 * 184 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 185 * @version $Id: CLIDefinitionParser.java 1151 2025-10-01 21:32:15Z tquadrat $ 186 * @since 0.0.1 187 * 188 * @UMLGraph.link 189 */ 190 @ClassVersion( sourceVersion = "$Id: CLIDefinitionParser.java 1151 2025-10-01 21:32:15Z tquadrat $" ) 191 @API( status = INTERNAL, since = "0.0.1" ) 192 private static class CLIDefinitionResolver implements XMLResolver 193 { 194 /*--------------*\ 195 ====** Constructors **================================================= 196 \*--------------*/ 197 /** 198 * Creates a new instance of {@code CLIDefinitionResolver}. 199 */ 200 public CLIDefinitionResolver() { /* Just exists */ } 201 202 /*---------*\ 203 ====** Methods **====================================================== 204 \*---------*/ 205 /** 206 * {@inheritDoc} 207 */ 208 @SuppressWarnings({"UseOfSystemOutOrSystemErr", "RedundantThrows"}) 209 @Override 210 public final Object resolveEntity( final String publicID, final String systemID, final String baseURI, final String namespace ) throws XMLStreamException 211 { 212 Object retValue = null; 213 214 if( CLI_DEFINITION_DTD.equals( systemID ) ) 215 { 216 retValue = retrieveCLIDefinitionDTD(); 217 } 218 else 219 { 220 out.printf( "PublicID = %s%n", publicID ); 221 out.printf( "SystemID = %s%n", systemID ); 222 out.printf( "BaseURI = %s%n", baseURI ); 223 out.printf( "Namespace = %s%n", namespace ); 224 } 225 226 //---* Done *------------------------------------------------------ 227 return retValue; 228 } // resolveEntity() 229 } 230 // class CLIDefinitionResolver 231 232 /*-----------*\ 233 ====** Constants **======================================================== 234 \*-----------*/ 235 /** 236 * The name for the CLI definition DTD file: {@value}. 237 */ 238 @SuppressWarnings( "StaticMethodOnlyUsedInOneClass" ) 239 @API( status = INTERNAL, since = "0.0.1" ) 240 public static final String CLI_DEFINITION_DTD = "CLIDefinition.dtd"; 241 242 /** 243 * The name for the CLI definition XSD file: {@value}. 244 */ 245 @API( status = INTERNAL, since = "0.0.1" ) 246 public static final String CLI_DEFINITION_XSD = "CLIDefinition.xsd"; 247 248 /** 249 * The message indicating that a start element was expected, but another 250 * event was encountered: {@value}. 251 */ 252 public static final String MSG_ExpectedStartEvent = "Start event expected: %s"; 253 254 /** 255 * The message indicating that a provided 256 * {@link StringConverter} 257 * implementation is invalid: 258 * {@value}. 259 */ 260 public static final String MSG_InvalidStringConverter = "Invalid StringConverter implementation: %s"; 261 262 /** 263 * The message indicating that an attribute value is invalid: 264 * {@value}. 265 */ 266 public static final String MSG_InvalidValue = "Value '%2$s' for attribute '%1$s' is invalid"; 267 268 /** 269 * The message indicating that an attribute does not have a value: 270 * {@value}. 271 */ 272 public static final String MSG_MissingValue = "Value for attribute '%1$s' is missing"; 273 274 /** 275 * The message indicating that a specific end element was expected, but 276 * another end element was encountered: {@value}. 277 */ 278 public static final String MSG_UnexpectedEndEvent = 279 """ 280 Unexpected End event: %1$s 281 '%3$s' does not match the expected '%2$s' 282 """; 283 284 /** 285 * The message indicating that an unexpected event was encountered: 286 * {@value}. 287 */ 288 public static final String MSG_UnexpectedEvent = "Unexpected event: %1$s"; 289 290 /** 291 * The message indicating that an unexpected attribute was 292 * encountered: {@value}. 293 */ 294 public static final String MSG_WrongAttribute = "Unexpected attribute: %1$s"; 295 296 /** 297 * The message indicating that an unexpected start element was 298 * encountered: {@value}. 299 */ 300 public static final String MSG_WrongElement1 = "Expected '%1$s', but encountered '%2$s'"; 301 302 /** 303 * The message indicating that an unexpected start element was 304 * encountered: {@value}. 305 */ 306 public static final String MSG_WrongElement2 = "Encountered '%2$s' while expected one of: %1$s"; 307 308 /** 309 * The name for the XML attribute 'handler': {@value}. 310 */ 311 public static final String XMLATTRIBUTE_Handler = "handler"; 312 313 /** 314 * The name for the XML attribute 'index': {@value}. 315 */ 316 public static final String XMLATTRIBUTE_Index = "index"; 317 318 /** 319 * The name for the XML attribute 'isMultiValue': {@value}. 320 */ 321 public static final String XMLATTRIBUTE_IsMultiValue = "isMultiValue"; 322 323 /** 324 * The name for the XML attribute 'isRequired': {@value}. 325 */ 326 public static final String XMLATTRIBUTE_IsRequired = "isRequired"; 327 328 /** 329 * The name for the XML attribute 'key': {@value}. 330 */ 331 public static final String XMLATTRIBUTE_Key = "key"; 332 333 /** 334 * The name for the XML attribute 'metaVar': {@value}. 335 */ 336 public static final String XMLATTRIBUTE_MetaVar = "metaVar"; 337 338 /** 339 * The name for the XML attribute 'propertyName': {@value}. 340 */ 341 public static final String XMLATTRIBUTE_PropertyName = "propertyName"; 342 343 /** 344 * The name for the XML attribute 'stringConversion': {@value}. 345 */ 346 public static final String XMLATTRIBUTE_StringConversion = "stringConversion"; 347 348 /** 349 * The name for the XML attribute 'type': {@value}. 350 */ 351 public static final String XMLATTRIBUTE_Type = "type"; 352 353 /** 354 * The name for the XML element 'alias': {@value}. 355 */ 356 public static final String XMLELEMENT_Alias = "alias"; 357 358 /** 359 * The name for the XML element 'argument': {@value}. 360 */ 361 public static final String XMLELEMENT_CLIArgument = "argument"; 362 363 /** 364 * The name for the XML element 'cliDefinition': {@value}. 365 */ 366 public static final String XMLELEMENT_CLIDefinition = "cliDefinition"; 367 368 /** 369 * The name for the XML element 'option': {@value}. 370 */ 371 public static final String XMLELEMENT_CLIOption = "option"; 372 373 /** 374 * The name for the XML element 'format': {@value}. 375 */ 376 public static final String XMLELEMENT_Format = "format"; 377 378 /** 379 * The name for the XML element 'usage': {@value}. 380 */ 381 public static final String XMLELEMENT_Usage = "usage"; 382 383 /*------------*\ 384 ====** Attributes **======================================================= 385 \*------------*/ 386 /** 387 * The XML event reader that provides the CLI definition. 388 */ 389 private final XMLEventReader m_EventReader; 390 391 /** 392 * The property map. 393 */ 394 private final Map<String,Object> m_PropertyMap; 395 396 /** 397 * The XML stream. 398 */ 399 private final String m_XMLContents; 400 401 /*--------------*\ 402 ====** Constructors **===================================================== 403 \*--------------*/ 404 /** 405 * Creates a new {@code CLIDefinitionParser} instance. 406 * 407 * @param inputStream The input stream that should contain the XML CLI 408 * definition. 409 * @param propertyMap The target data structure for the values from the 410 * command line. 411 * @throws XMLStreamException Cannot create a 412 * {@link XMLEventReader} 413 * instance for the given input stream. 414 * @throws IOException Cannot read the given input stream. 415 */ 416 private CLIDefinitionParser( final InputStream inputStream, final Map<String,Object> propertyMap ) throws XMLStreamException, IOException 417 { 418 m_PropertyMap = requireNonNullArgument( propertyMap, "propertyMap" ); 419 420 //---* Get XML contents *---------------------------------------------- 421 m_XMLContents = new String( requireNonNullArgument( inputStream, "inputStream" ).readAllBytes(), UTF8 ); 422 423 //---* Create the event reader for parsing *--------------------------- 424 final var xmlInputFactory = XMLInputFactory.newInstance(); 425 xmlInputFactory.setXMLResolver( new CLIDefinitionResolver() ); 426 m_EventReader = xmlInputFactory.createXMLEventReader( new StringReader( m_XMLContents ) ); 427 } // CLIDefinitionParser() 428 429 /*---------*\ 430 ====** Methods **========================================================== 431 \*---------*/ 432 /** 433 * <p>{@summary Creates the instance for the command line value handler 434 * based on the given class for the type and class for the handler 435 * implementation.} If {@code processClass} is {@code null}, the method 436 * will search for a specialised handler class in the internal registry; 437 * if none can be found, and {@code stringConverter} is not {@code null}, 438 * it creates an instance of 439 * {@link org.tquadrat.foundation.config.cli.SimpleCmdLineValueHandler} 440 * with it. But if {@code stringConverter} is {@code null}, an exception 441 * will be thrown.</p> 442 * <p>If {@code processClass} is not {@code null}, it has to be a class 443 * that implements 444 * {@link CmdLineValueHandler}. 445 * In that case that class will be instantiated.</p> 446 * 447 * @param <T> The type of the property the set. 448 * @param type The class for the property to set. 449 * @param processClass This is either the handler class or 450 * {@code null}. 451 * @param stringConverter The 452 * {@link StringConverter} 453 * instance for the property; can be {@code null}. 454 * @return The command line value handler instance. 455 * @throws IllegalArgumentException A command line value handler 456 * instance cannot be created. 457 */ 458 @SuppressWarnings( {"unchecked", "rawtypes"} ) 459 private final <T> CmdLineValueHandler<T> createHandler( final Class<? extends T> type, final Class<?> processClass, final StringConverter<? extends T> stringConverter ) throws IllegalArgumentException 460 { 461 final var propertyTypeIsEnum = requireNonNullArgument( type, "type" ).isEnum(); 462 final BiConsumer<String, T> valueSetter = m_PropertyMap::put; 463 CmdLineValueHandler<T> retValue = null; 464 Class<? extends CmdLineValueHandler<?>> handlerClass = null; 465 if( isNull( processClass ) ) 466 { 467 if( propertyTypeIsEnum ) 468 { 469 retValue = new EnumValueHandler( type, valueSetter ); 470 } 471 else 472 { 473 //---* Infer the handler class, if necessary *----------------- 474 final var foundHandlerClass = retrieveValueHandlerClass( type ); 475 if( foundHandlerClass.isPresent() ) 476 { 477 handlerClass = foundHandlerClass.get(); 478 } 479 else if( nonNull( stringConverter ) ) 480 { 481 retValue = new SimpleCmdLineValueHandler<>( valueSetter, stringConverter ); 482 } 483 } 484 } 485 else 486 { 487 if( !CmdLineValueHandler.class.isAssignableFrom( processClass ) ) 488 { 489 throw new IllegalArgumentException( "'%s' is neither a StringConverter nor a CmdLineValueHandler".formatted( processClass.getName() ) ); 490 } 491 492 //---* We got a command line value handler *----------------------- 493 handlerClass = (Class<? extends CmdLineValueHandler<?>>) processClass; 494 } 495 496 if( isNull( retValue ) ) 497 { 498 if( isNull( handlerClass ) ) 499 { 500 throw new IllegalArgumentException( "Could not determine a class for the Command Line Value Handler" ); 501 } 502 try 503 { 504 final var constructor = handlerClass.getConstructor( BiConsumer.class ); 505 retValue = (CmdLineValueHandler<T>) constructor.newInstance( valueSetter ); 506 } 507 catch( final InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e ) 508 { 509 throw new IllegalArgumentException( "Unable to create value handler from '%s'".formatted( handlerClass.getName() ), e ); 510 } 511 } 512 513 //---* Done *---------------------------------------------------------- 514 return retValue; 515 } // createHandler() 516 517 /** 518 * <p>{@summary Creates the instance for the string converter based on the 519 * given class for the type and class for the string converter 520 * implementation.} If {@code stringConverterClass} is {@code null}, the 521 * method will search for an implementation class in the internal 522 * registry; if none can be found, {@code null} will be returned.</p> 523 * 524 * @param <T> The type of the property to convert. 525 * @param type The class for the property to convert. 526 * @param stringConverterClass The String converter class or 527 * {@code null}. 528 * @return The String converter instance. 529 */ 530 @SuppressWarnings( {"unchecked", "rawtypes"} ) 531 private final <T> StringConverter<T> createStringConverter( final Class<?> type, final Class<? extends StringConverter<?>> stringConverterClass ) 532 { 533 requireNonNullArgument( type, "type" ); 534 535 final StringConverter<T> retValue; 536 if( isNull( stringConverterClass ) ) 537 { 538 if( type.isEnum() ) 539 { 540 retValue = (StringConverter<T>) StringConverter.forEnum( (Class<? extends Enum>) type ); 541 } 542 else 543 { 544 retValue = (StringConverter<T>) StringConverter.forClass( type ).orElse( null ); 545 } 546 } 547 else 548 { 549 final var foundProvider = retrieveMethod( stringConverterClass, "provider" ); 550 if( foundProvider.isPresent() ) 551 { 552 try 553 { 554 retValue = (StringConverter<T>) foundProvider.get().invoke( null ); 555 } 556 catch( final IllegalAccessException | InvocationTargetException e ) 557 { 558 throw new IllegalArgumentException( format( MSG_InvalidStringConverter, stringConverterClass.getName() ), e ); 559 } 560 } 561 else 562 { 563 try 564 { 565 @SuppressWarnings( "unchecked" ) 566 final var constructor = (Constructor<StringConverter<T>>) stringConverterClass.getConstructor(); 567 retValue = constructor.newInstance(); 568 } 569 catch( final NoSuchMethodException e ) 570 { 571 throw new IllegalArgumentException( "No default constructor for StringConverter: %s".formatted( stringConverterClass.getName() ), e ); 572 } 573 catch( final InstantiationException | InvocationTargetException | IllegalAccessException e ) 574 { 575 throw new IllegalArgumentException( format( MSG_InvalidStringConverter, stringConverterClass.getName() ), e ); 576 } 577 } 578 } 579 580 //---* Done *---------------------------------------------------------- 581 return retValue; 582 } // createStringConverter() 583 584 /** 585 * Executes the parsing. 586 * 587 * @return The parsed CLI definition. 588 * @throws XMLStreamException Cannot parse the given input stream. 589 */ 590 @SuppressWarnings( "SwitchStatementWithTooManyBranches" ) 591 private final List<CLIDefinition> execute() throws XMLStreamException 592 { 593 List<CLIDefinition> retValue = List.of(); 594 try 595 { 596 while( m_EventReader.hasNext() ) 597 { 598 final var event = m_EventReader.nextEvent(); 599 switch( event.getEventType() ) 600 { 601 case ATTRIBUTE: 602 case CDATA: 603 case CHARACTERS: 604 case END_ELEMENT: 605 //noinspection UseOfSystemOutOrSystemErr 606 out.printf( "%d: %s%n", event.getEventType(), event ); 607 throw new XMLStreamException( format( MSG_ExpectedStartEvent, event.toString() ), event.getLocation() ); 608 609 case START_ELEMENT: 610 { 611 final var startElement = event.asStartElement(); 612 final var name = startElement.getName(); 613 if( name.getLocalPart().equals( XMLELEMENT_CLIDefinition ) ) 614 { 615 retValue = handleCLIDefinition( startElement ); 616 } 617 else 618 { 619 throw new XMLStreamException( format( MSG_WrongElement1, XMLELEMENT_CLIDefinition, name.toString() ), event.getLocation() ); 620 } 621 break; 622 } 623 624 case COMMENT: 625 case DTD: 626 case END_DOCUMENT: 627 case ENTITY_DECLARATION: 628 case NAMESPACE: 629 case NOTATION_DECLARATION: 630 case PROCESSING_INSTRUCTION: 631 case SPACE: 632 case START_DOCUMENT: 633 break; 634 635 default: 636 //noinspection UseOfSystemOutOrSystemErr 637 out.printf( "%d: %s%n", event.getEventType(), event ); 638 throw new XMLStreamException( format( MSG_UnexpectedEvent, event.toString() ), event.getLocation() ); 639 } 640 } 641 } 642 finally 643 { 644 //noinspection ThrowFromFinallyBlock 645 m_EventReader.close(); 646 } 647 648 //---* Done *---------------------------------------------------------- 649 return retValue; 650 } // execute() 651 652 /** 653 * Retrieves the URL for the CLI definition DTD file from the resources. 654 * 655 * @return The URL for the file. 656 */ 657 @API( status = INTERNAL, since = "0.0.1" ) 658 public static final URL getCLIDefinitionDTDURL() 659 { 660 final var retValue = CLIDefinitionParser.class.getResource( format( "/%s", CLI_DEFINITION_DTD ) ); 661 662 //---* Done *---------------------------------------------------------- 663 return retValue; 664 } // getCLIDefinitionDTDURL() 665 666 /** 667 * Retrieves the URL for the CLI definition XSD file from the resources. 668 * 669 * @return The URL for the file. 670 */ 671 @API( status = INTERNAL, since = "0.0.1" ) 672 public static final URL getCLIDefinitionXSDURL() 673 { 674 final var retValue = CLIDefinitionParser.class.getResource( format( "/%s", CLI_DEFINITION_XSD ) ); 675 676 //---* Done *---------------------------------------------------------- 677 return retValue; 678 } // getCLIDefinitionXSDURL() 679 680 /** 681 * Handles the {@value #XMLELEMENT_Alias} element. 682 * 683 * @param element The current element. 684 * @return The option alias. 685 * @throws XMLStreamException A problem occurred while parsing the 686 * element. 687 */ 688 @SuppressWarnings( {"SwitchStatementWithTooManyBranches", "SwitchStatementWithTooFewBranches"} ) 689 private final String handleAlias( final StartElement element ) throws XMLStreamException 690 { 691 String retValue = null; 692 693 //---* Get the attributes *-------------------------------------------- 694 final var attributes = element.getAttributes(); 695 while( attributes.hasNext() ) 696 { 697 final var attribute = attributes.next(); 698 final var name = attribute.getName(); 699 retValue = switch( name.getLocalPart() ) 700 { 701 case XMLATTRIBUTE_Name -> processAttrName( attribute ); 702 default -> throw new XMLStreamException( format( MSG_WrongAttribute, name.toString() ), element.getLocation() ); 703 }; 704 } 705 706 //---* Proceed parsing ... *------------------------------------------- 707 var proceed = true; 708 while( m_EventReader.hasNext() && proceed ) 709 { 710 final var event = m_EventReader.nextEvent(); 711 switch( event.getEventType() ) 712 { 713 case ATTRIBUTE: 714 case CDATA: 715 case DTD: 716 case END_DOCUMENT: 717 case ENTITY_DECLARATION: 718 case NAMESPACE: 719 case NOTATION_DECLARATION: 720 case PROCESSING_INSTRUCTION: 721 case START_DOCUMENT: 722 case START_ELEMENT: 723 throw new XMLStreamException( format( MSG_UnexpectedEvent, event.toString() ), event.getLocation() ); 724 725 case END_ELEMENT: 726 { 727 final var endElement = event.asEndElement(); 728 final var name = endElement.getName(); 729 if( !name.equals( element.getName() ) ) 730 { 731 throw new XMLStreamException( format( MSG_UnexpectedEndEvent, event.toString(), element.getName(), name ), event.getLocation() ); 732 } 733 proceed = false; 734 break; 735 } 736 737 case CHARACTERS: 738 case COMMENT: 739 case SPACE: 740 break; 741 742 default: 743 //noinspection UseOfSystemOutOrSystemErr 744 out.printf( "%d: %s\n", event.getEventType(), event ); 745 throw new XMLStreamException( format( MSG_UnexpectedEvent, event.toString() ), event.getLocation() ); 746 } 747 } 748 749 if( isEmpty( retValue ) ) 750 { 751 throw new XMLStreamException( format( MSG_MissingValue, XMLATTRIBUTE_Name ), element.getLocation() ); 752 } 753 754 //---* Done *---------------------------------------------------------- 755 return retValue; 756 } // handleAlias() 757 758 /** 759 * Handles the {@value #XMLELEMENT_CLIArgument} element. 760 * 761 * @param element The current element. 762 * @return The parsed CLI definition. 763 * @throws XMLStreamException A problem occurred while parsing the 764 * element. 765 */ 766 @SuppressWarnings( {"SwitchStatementWithTooManyBranches", "NestedSwitchStatement", "OverlyLongMethod", "OverlyComplexMethod"} ) 767 private CLIDefinition handleArgument( final StartElement element ) throws XMLStreamException 768 { 769 String format = null; 770 Class<? extends CmdLineValueHandler<?>> processorClass = null; 771 Class<? extends StringConverter<?>> stringConverterClass = null; 772 var index = -1; 773 var isMultiValue = false; 774 var isRequired = false; 775 String metaVar = null; 776 String propertyName = null; 777 Class<?> type = null; 778 String usage = null; 779 String usageKey = null; 780 CLIArgumentDefinition retValue = null; 781 782 //---* Get the attributes *-------------------------------------------- 783 final var attributes = element.getAttributes(); 784 while( attributes.hasNext() ) 785 { 786 final var attribute = attributes.next(); 787 final var name = attribute.getName(); 788 switch( name.getLocalPart() ) 789 { 790 case XMLATTRIBUTE_Handler -> processorClass = processAttrHandler( attribute ); 791 case XMLATTRIBUTE_Index -> { 792 try 793 { 794 index = Integer.parseInt( attribute.getValue() ); 795 if( index < 0 ) 796 { 797 throw new XMLStreamException( format( MSG_InvalidValue, XMLATTRIBUTE_Index, Integer.toString( index ) ), attribute.getLocation() ); 798 } 799 } 800 catch( final NumberFormatException e ) 801 { 802 final var xse = new XMLStreamException( format( MSG_InvalidValue, XMLATTRIBUTE_Index, attribute.getValue() ), attribute.getLocation(), e ); 803 xse.initCause( e ); 804 throw xse; 805 } 806 } 807 808 case XMLATTRIBUTE_IsMultiValue -> isMultiValue = Boolean.parseBoolean( attribute.getValue() ); 809 case XMLATTRIBUTE_IsRequired -> isRequired = Boolean.parseBoolean( attribute.getValue() ); 810 case XMLATTRIBUTE_MetaVar -> metaVar = attribute.getValue(); 811 case XMLATTRIBUTE_PropertyName -> propertyName = processAttrPropertyName( attribute ); 812 case XMLATTRIBUTE_StringConversion -> stringConverterClass = processAttrStringConversion( attribute ); 813 case XMLATTRIBUTE_Type -> type = processAttrType( attribute ); 814 default -> throw new XMLStreamException( format( MSG_WrongAttribute, name.toString() ), element.getLocation() ); 815 } 816 } 817 818 //---* Get the StringConverter instance *------------------------------ 819 final StringConverter<?> stringConverter = createStringConverter( type, stringConverterClass ); 820 821 //---* Get the handler instance *-------------------------------------- 822 final CmdLineValueHandler<?> handler; 823 try 824 { 825 handler = createHandler( type, processorClass, stringConverter ); 826 } 827 catch( final IllegalArgumentException e ) 828 { 829 throw new XMLStreamException( "Cannot create Command Line Value Handler", e ); 830 } 831 832 //---* Proceed parsing ... *------------------------------------------- 833 var proceed = true; 834 while( m_EventReader.hasNext() && proceed ) 835 { 836 final var event = m_EventReader.nextEvent(); 837 switch( event.getEventType() ) 838 { 839 case ATTRIBUTE: 840 case CDATA: 841 case DTD: 842 case END_DOCUMENT: 843 case ENTITY_DECLARATION: 844 case NAMESPACE: 845 case NOTATION_DECLARATION: 846 case PROCESSING_INSTRUCTION: 847 case START_DOCUMENT: 848 //noinspection UseOfSystemOutOrSystemErr 849 out.printf( "%d: %s\n", event.getEventType(), event ); 850 throw new XMLStreamException( format( MSG_ExpectedStartEvent, event.toString() ), event.getLocation() ); 851 852 case END_ELEMENT: 853 { 854 final var endElement = event.asEndElement(); 855 final var name = endElement.getName(); 856 if( !name.equals( element.getName() ) ) 857 { 858 throw new XMLStreamException( format( MSG_UnexpectedEndEvent, event.toString(), element.getName(), name ), event.getLocation() ); 859 } 860 proceed = false; 861 break; 862 } 863 864 case START_ELEMENT: 865 { 866 final var startElement = event.asStartElement(); 867 final var name = startElement.getName(); 868 switch( name.getLocalPart() ) 869 { 870 case XMLELEMENT_Format -> format = handleFormat( startElement ); 871 872 case XMLELEMENT_Usage -> 873 { 874 final var result = handleUsage( startElement ); 875 usageKey = result.get( XMLATTRIBUTE_Key ); 876 usage = result.get( XMLELEMENT_Usage ); 877 } 878 879 default -> throw new XMLStreamException( format( MSG_WrongElement2, join( ", ", XMLELEMENT_Alias, XMLELEMENT_Format, XMLELEMENT_Usage ), name.toString() ), event.getLocation() ); 880 } 881 break; 882 } 883 884 case CHARACTERS: 885 case COMMENT: 886 case SPACE: 887 break; 888 889 default: 890 //noinspection UseOfSystemOutOrSystemErr 891 out.printf( "%d: %s\n", event.getEventType(), event ); 892 throw new XMLStreamException( format( MSG_UnexpectedEvent, event.toString() ), event.getLocation() ); 893 } 894 } 895 896 //---* Create the return value *--------------------------------------- 897 if( isEmptyOrBlank( metaVar ) ) 898 { 899 //noinspection DataFlowIssue 900 metaVar = type.getSimpleName().toUpperCase( ROOT ); 901 } 902 retValue = new CLIArgumentDefinition( propertyName, index, usage, usageKey, metaVar, isRequired, handler, isMultiValue, format ); 903 904 //---* Done *---------------------------------------------------------- 905 return retValue; 906 } // handleArgument() 907 908 /** 909 * Handles the {@value #XMLELEMENT_CLIDefinition} element. 910 * 911 * @param element The current element. 912 * @return The parsed CLI definition. 913 * @throws XMLStreamException A problem occurred while parsing the 914 * element. 915 */ 916 @SuppressWarnings( {"SwitchStatementWithTooManyBranches", "NestedSwitchStatement"} ) 917 private final List<CLIDefinition> handleCLIDefinition( final StartElement element ) throws XMLStreamException 918 { 919 final List<CLIDefinition> retValue = new ArrayList<>(); 920 var proceed = true; 921 while( m_EventReader.hasNext() && proceed ) 922 { 923 final var event = m_EventReader.nextEvent(); 924 switch( event.getEventType() ) 925 { 926 case ATTRIBUTE: 927 case CDATA: 928 case DTD: 929 case END_DOCUMENT: 930 case ENTITY_DECLARATION: 931 case NAMESPACE: 932 case NOTATION_DECLARATION: 933 case PROCESSING_INSTRUCTION: 934 case START_DOCUMENT: 935 throw new XMLStreamException( format( MSG_ExpectedStartEvent, event.toString() ), event.getLocation() ); 936 937 case END_ELEMENT: 938 { 939 final var endElement = event.asEndElement(); 940 final var name = endElement.getName(); 941 if( !name.equals( element.getName() ) ) 942 { 943 throw new XMLStreamException( format( MSG_UnexpectedEndEvent, event.toString(), element.getName(), name ), event.getLocation() ); 944 } 945 proceed = false; 946 break; 947 } 948 949 case START_ELEMENT: 950 { 951 final var startElement = event.asStartElement(); 952 final var name = startElement.getName(); 953 switch( name.getLocalPart() ) 954 { 955 case XMLELEMENT_CLIArgument -> retValue.add( handleArgument( startElement ) ); 956 case XMLELEMENT_CLIOption -> retValue.add( handleOption( startElement ) ); 957 default -> throw new XMLStreamException( format( MSG_WrongElement2, join( ", ", XMLELEMENT_CLIOption, XMLELEMENT_CLIArgument ), name.toString() ), event.getLocation() ); 958 } 959 break; 960 } 961 962 case CHARACTERS: 963 case COMMENT: 964 case SPACE: 965 break; 966 967 default: 968 //noinspection UseOfSystemOutOrSystemErr 969 out.printf( "%d: %s\n", event.getEventType(), event ); 970 throw new XMLStreamException( format( MSG_UnexpectedEvent, event.toString() ), event.getLocation() ); 971 } 972 } 973 974 //---* Done *---------------------------------------------------------- 975 return retValue; 976 } // handleCLIDefinition() 977 978 /** 979 * Handles the {@value #XMLELEMENT_Format} element. 980 * 981 * @param element The current element. 982 * @return The format. 983 * @throws XMLStreamException A problem occurred while parsing the 984 * element. 985 */ 986 @SuppressWarnings( {"SwitchStatementWithTooManyBranches", "AssignmentToNull"} ) 987 private final String handleFormat( final StartElement element ) throws XMLStreamException 988 { 989 String retValue = null; 990 991 var proceed = true; 992 while( m_EventReader.hasNext() && proceed ) 993 { 994 final var event = m_EventReader.nextEvent(); 995 switch( event.getEventType() ) 996 { 997 case ATTRIBUTE: 998 case DTD: 999 case END_DOCUMENT: 1000 case ENTITY_DECLARATION: 1001 case NAMESPACE: 1002 case NOTATION_DECLARATION: 1003 case PROCESSING_INSTRUCTION: 1004 case START_DOCUMENT: 1005 case START_ELEMENT: 1006 throw new XMLStreamException( format( MSG_UnexpectedEvent, event.toString() ), event.getLocation() ); 1007 1008 case END_ELEMENT: 1009 { 1010 final var endElement = event.asEndElement(); 1011 final var name = endElement.getName(); 1012 if( !name.equals( element.getName() ) ) 1013 { 1014 throw new XMLStreamException( format( MSG_UnexpectedEndEvent, event.toString(), element.getName(), name ), event.getLocation() ); 1015 } 1016 proceed = false; 1017 break; 1018 } 1019 1020 case CDATA: 1021 case CHARACTERS: 1022 { 1023 final var characters = event.asCharacters(); 1024 retValue = characters.getData(); 1025 break; 1026 } 1027 1028 case COMMENT: 1029 case SPACE: 1030 break; 1031 1032 default: 1033 //noinspection UseOfSystemOutOrSystemErr 1034 out.printf( "%d: %s\n", event.getEventType(), event ); 1035 throw new XMLStreamException( format( MSG_UnexpectedEvent, event.toString() ), event.getLocation() ); 1036 } 1037 } 1038 1039 if( isEmpty( retValue ) ) retValue = null; 1040 1041 //---* Done *---------------------------------------------------------- 1042 return retValue; 1043 } // handleFormat() 1044 1045 /** 1046 * Handles the {@value #XMLELEMENT_CLIOption} element. 1047 * 1048 * @param element The current element. 1049 * @return The parsed CLI definition. 1050 * @throws XMLStreamException A problem occurred while parsing the 1051 * element. 1052 */ 1053 @SuppressWarnings( {"SwitchStatementWithTooManyBranches", "NestedSwitchStatement", "OverlyLongMethod", "OverlyComplexMethod"} ) 1054 private final CLIDefinition handleOption( final StartElement element ) throws XMLStreamException 1055 { 1056 String format = null; 1057 Class<? extends CmdLineValueHandler<?>> processorClass = null; 1058 Class<? extends StringConverter<?>> stringConverterClass = null; 1059 var isMultiValue = false; 1060 var isRequired = false; 1061 String metaVar = null; 1062 final List<String> names = new ArrayList<>(); 1063 String propertyName = null; 1064 Class<?> type = null; 1065 String usage = null; 1066 String usageKey = null; 1067 CLIOptionDefinition retValue = null; 1068 1069 //---* Get the attributes *-------------------------------------------- 1070 final var attributes = element.getAttributes(); 1071 while( attributes.hasNext() ) 1072 { 1073 final var attribute = attributes.next(); 1074 final var name = attribute.getName(); 1075 switch( name.getLocalPart() ) 1076 { 1077 case XMLATTRIBUTE_Handler -> processorClass = processAttrHandler( attribute ); 1078 case XMLATTRIBUTE_IsMultiValue -> isMultiValue = Boolean.parseBoolean( attribute.getValue() ); 1079 case XMLATTRIBUTE_IsRequired -> isRequired = Boolean.parseBoolean( attribute.getValue() ); 1080 case XMLATTRIBUTE_MetaVar -> metaVar = attribute.getValue(); 1081 case XMLATTRIBUTE_Name -> names.add( processAttrName( attribute ) ); 1082 case XMLATTRIBUTE_PropertyName -> propertyName = processAttrPropertyName( attribute ); 1083 case XMLATTRIBUTE_StringConversion -> stringConverterClass = processAttrStringConversion( attribute ); 1084 case XMLATTRIBUTE_Type -> type = processAttrType( attribute ); 1085 default -> throw new XMLStreamException( format( MSG_WrongAttribute, name.toString() ), element.getLocation() ); 1086 } 1087 } 1088 1089 //---* Get the StringConverter instance *------------------------------ 1090 final StringConverter<?> stringConverter = createStringConverter( type, stringConverterClass ); 1091 1092 //---* Get the handler instance *-------------------------------------- 1093 final CmdLineValueHandler<?> handler; 1094 try 1095 { 1096 handler = createHandler( type, processorClass, stringConverter ); 1097 } 1098 catch( final IllegalArgumentException e ) 1099 { 1100 throw new XMLStreamException( "Cannot create Command Line Value Handler", e ); 1101 } 1102 1103 //---* Proceed parsing ... *------------------------------------------- 1104 var proceed = true; 1105 while( m_EventReader.hasNext() && proceed ) 1106 { 1107 final var event = m_EventReader.nextEvent(); 1108 switch( event.getEventType() ) 1109 { 1110 case ATTRIBUTE: 1111 case CDATA: 1112 case DTD: 1113 case END_DOCUMENT: 1114 case ENTITY_DECLARATION: 1115 case NAMESPACE: 1116 case NOTATION_DECLARATION: 1117 case PROCESSING_INSTRUCTION: 1118 case START_DOCUMENT: 1119 //noinspection UseOfSystemOutOrSystemErr 1120 out.printf( "%d: %s\n", event.getEventType(), event ); 1121 throw new XMLStreamException( format( MSG_ExpectedStartEvent, event.toString() ), event.getLocation() ); 1122 1123 case END_ELEMENT: 1124 { 1125 final var endElement = event.asEndElement(); 1126 final var name = endElement.getName(); 1127 if( !name.equals( element.getName() ) ) 1128 { 1129 throw new XMLStreamException( format( MSG_UnexpectedEndEvent, event.toString(), element.getName(), name ), event.getLocation() ); 1130 } 1131 proceed = false; 1132 break; 1133 } 1134 1135 case START_ELEMENT: 1136 { 1137 final var startElement = event.asStartElement(); 1138 final var name = startElement.getName(); 1139 switch( name.getLocalPart() ) 1140 { 1141 case XMLELEMENT_Alias -> names.add( handleAlias( startElement ) ); 1142 case XMLELEMENT_Format -> format = handleFormat( startElement ); 1143 case XMLELEMENT_Usage -> 1144 { 1145 final var result = handleUsage( startElement ); 1146 usageKey = result.get( XMLATTRIBUTE_Key ); 1147 usage = result.get( XMLELEMENT_Usage ); 1148 } 1149 default -> throw new XMLStreamException( format( MSG_WrongElement2, join( ", ", XMLELEMENT_Alias, XMLELEMENT_Format, XMLELEMENT_Usage ), name.toString() ), event.getLocation() ); 1150 } 1151 break; 1152 } 1153 1154 case CHARACTERS: 1155 case COMMENT: 1156 case SPACE: 1157 break; 1158 1159 default: 1160 //noinspection UseOfSystemOutOrSystemErr 1161 out.printf( "%d: %s\n", event.getEventType(), event ); 1162 throw new XMLStreamException( format( MSG_UnexpectedEvent, event.toString() ), event.getLocation() ); 1163 } 1164 } 1165 1166 //---* Create the return value *--------------------------------------- 1167 if( names.size() != new HashSet<>( names ).size() ) 1168 { 1169 throw new XMLStreamException( "Duplicate option names", element.getLocation() ); 1170 } 1171 if( isEmptyOrBlank( metaVar ) ) 1172 { 1173 //noinspection DataFlowIssue 1174 metaVar = type.getSimpleName().toUpperCase( ROOT ); 1175 } 1176 retValue = new CLIOptionDefinition( propertyName, names, usage, usageKey, metaVar, isRequired, handler, isMultiValue, format ); 1177 1178 //---* Done *---------------------------------------------------------- 1179 return retValue; 1180 } // handleOption() 1181 1182 /** 1183 * Handles the {@value #XMLELEMENT_Usage} element. 1184 * 1185 * @param element The current element. 1186 * @return The usage and the usage key. 1187 * @throws XMLStreamException A problem occurred while parsing the 1188 * element. 1189 */ 1190 @SuppressWarnings( {"SwitchStatementWithTooFewBranches", "SwitchStatementWithTooManyBranches"} ) 1191 private final Map<String,String> handleUsage( final StartElement element ) throws XMLStreamException 1192 { 1193 final Map<String,String> retValue = new HashMap<>(); 1194 1195 //---* Get the attributes *-------------------------------------------- 1196 final var attributes = element.getAttributes(); 1197 while( attributes.hasNext() ) 1198 { 1199 final var attribute = attributes.next(); 1200 final var name = attribute.getName(); 1201 switch( name.getLocalPart() ) 1202 { 1203 case XMLATTRIBUTE_Key -> retValue.put( XMLATTRIBUTE_Key, attribute.getValue() ); 1204 default -> throw new XMLStreamException( format( MSG_WrongAttribute, name.toString() ), element.getLocation() ); 1205 } 1206 } 1207 1208 //---* Proceed parsing ... *------------------------------------------- 1209 var proceed = true; 1210 while( m_EventReader.hasNext() && proceed ) 1211 { 1212 final var event = m_EventReader.nextEvent(); 1213 switch( event.getEventType() ) 1214 { 1215 case ATTRIBUTE: 1216 case DTD: 1217 case END_DOCUMENT: 1218 case ENTITY_DECLARATION: 1219 case NAMESPACE: 1220 case NOTATION_DECLARATION: 1221 case PROCESSING_INSTRUCTION: 1222 case START_DOCUMENT: 1223 case START_ELEMENT: 1224 throw new XMLStreamException( format( MSG_UnexpectedEvent, event.toString() ), event.getLocation() ); 1225 1226 case END_ELEMENT: 1227 { 1228 final var endElement = event.asEndElement(); 1229 final var name = endElement.getName(); 1230 if( !name.equals( element.getName() ) ) 1231 { 1232 throw new XMLStreamException( format( MSG_UnexpectedEndEvent, event.toString(), element.getName(), name ), event.getLocation() ); 1233 } 1234 proceed = false; 1235 break; 1236 } 1237 1238 case CDATA: 1239 case CHARACTERS: 1240 { 1241 final var characters = event.asCharacters(); 1242 retValue.put( XMLELEMENT_Usage, characters.getData() ); 1243 break; 1244 } 1245 1246 case COMMENT: 1247 case SPACE: 1248 break; 1249 1250 default: 1251 //noinspection UseOfSystemOutOrSystemErr 1252 out.printf( "%d: %s\n", event.getEventType(), event ); 1253 throw new XMLStreamException( format( MSG_UnexpectedEvent, event.toString() ), event.getLocation() ); 1254 } 1255 } 1256 1257 //---* Done *---------------------------------------------------------- 1258 return retValue; 1259 } // handleUsage() 1260 1261 /** 1262 * Parses the given 1263 * {@link InputStream}. 1264 * 1265 * @param inputStream The input stream that should contain the XML CLI 1266 * definition. 1267 * @param propertyMap The target data structure for the values from the 1268 * command line. 1269 * @param validate {@code true} if the given XML should be validated 1270 * against the schema {@code CLIDefinition.xsd} previous to parsing 1271 * it, {@code false} if the validation can be omitted. 1272 * 1273 * @return The parsed CLI definition. 1274 * @throws XMLStreamException Cannot parse the given input stream. 1275 * @throws IOException Cannot read the given input stream. 1276 */ 1277 public static final List<CLIDefinition> parse( final InputStream inputStream, final Map<String,Object> propertyMap, final boolean validate ) throws XMLStreamException, IOException 1278 { 1279 final var parser = new CLIDefinitionParser( inputStream, propertyMap ); 1280 if( validate ) parser.validate(); 1281 final var retValue = parser.execute(); 1282 1283 //---* Done *---------------------------------------------------------- 1284 return retValue; 1285 } // parse() 1286 1287 /** 1288 * Processes the attribute {@value #XMLATTRIBUTE_Handler}. 1289 * 1290 * @param attribute The attribute. 1291 * @return The handler class. 1292 * @throws XMLStreamException The attribute is somehow invalid. 1293 */ 1294 @SuppressWarnings( "unchecked" ) 1295 private static final Class<? extends CmdLineValueHandler<?>> processAttrHandler( final Attribute attribute ) throws XMLStreamException 1296 { 1297 1298 final var name = attribute.getName(); 1299 1300 final var value = attribute.getValue(); 1301 final Class<?> result; 1302 if( isNotEmptyOrBlank( value ) ) 1303 { 1304 try 1305 { 1306 result = Class.forName( value ); 1307 } 1308 catch( final ClassNotFoundException e ) 1309 { 1310 final var xse = new XMLStreamException( format( MSG_InvalidValue, name.toString(), value ), attribute.getLocation(), e ); 1311 xse.initCause( e ); 1312 throw xse; 1313 } 1314 if( !CmdLineValueHandler.class.isAssignableFrom( result ) ) 1315 { 1316 throw new XMLStreamException( format( MSG_InvalidValue, name.toString(), result.getName() ), attribute.getLocation() ); 1317 } 1318 } 1319 else 1320 { 1321 throw new XMLStreamException( format( MSG_MissingValue, name.toString() ), attribute.getLocation() ); 1322 } 1323 1324 @SuppressWarnings( "RedundantExplicitVariableType" ) 1325 final Class<? extends CmdLineValueHandler<?>> retValue = (Class<? extends CmdLineValueHandler<?>>) result; 1326 1327 //---* Done *---------------------------------------------------------- 1328 return retValue; 1329 } // processAttrHandler() 1330 1331 /** 1332 * Processes the attribute {@value org.tquadrat.foundation.lang.CommonConstants#XMLATTRIBUTE_Name}. 1333 * 1334 * @param attribute The attribute. 1335 * @return The property name. 1336 * @throws XMLStreamException The attribute is somehow invalid. 1337 */ 1338 private static final String processAttrName( final Attribute attribute ) throws XMLStreamException 1339 { 1340 final var name = attribute.getName(); 1341 1342 final var retValue = attribute.getValue(); 1343 try 1344 { 1345 validateOptionName( retValue ); 1346 } 1347 catch( final IllegalArgumentException e ) 1348 { 1349 final var xse = new XMLStreamException( join( "\n", format( MSG_InvalidValue, name.toString(), retValue ), e.getMessage() ), attribute.getLocation(), e ); 1350 xse.initCause( e ); 1351 throw xse; 1352 } 1353 1354 //---* Done *---------------------------------------------------------- 1355 return retValue; 1356 } // processAttrPropertyName() 1357 1358 /** 1359 * Processes the attribute {@value #XMLATTRIBUTE_PropertyName}. 1360 * 1361 * @param attribute The attribute. 1362 * @return The property name. 1363 * @throws XMLStreamException The attribute is somehow invalid. 1364 */ 1365 private static final String processAttrPropertyName( final Attribute attribute ) throws XMLStreamException 1366 { 1367 final var name = attribute.getName(); 1368 1369 final var retValue = attribute.getValue(); 1370 if( isNotEmptyOrBlank( retValue ) ) 1371 { 1372 if( !isValidName( retValue ) ) 1373 { 1374 throw new XMLStreamException( format( MSG_InvalidValue, name.toString(), retValue ), attribute.getLocation() ); 1375 } 1376 } 1377 else 1378 { 1379 throw new XMLStreamException( format( MSG_MissingValue, name.toString() ), attribute.getLocation() ); 1380 } 1381 1382 //---* Done *---------------------------------------------------------- 1383 return retValue; 1384 } // processAttrPropertyName() 1385 1386 /** 1387 * Processes the attribute {@value #XMLATTRIBUTE_StringConversion}. 1388 * 1389 * @param attribute The attribute. 1390 * @return The 1391 * {@link StringConverter} 1392 * implementation class. 1393 * @throws XMLStreamException The attribute is somehow invalid. 1394 */ 1395 private static final Class<? extends StringConverter<?>> processAttrStringConversion( final Attribute attribute ) throws XMLStreamException 1396 { 1397 final var name = attribute.getName(); 1398 1399 final var value = attribute.getValue(); 1400 final Class<?> result; 1401 if( isNotEmptyOrBlank( value ) ) 1402 { 1403 try 1404 { 1405 result = Class.forName( value ); 1406 } 1407 catch( final ClassNotFoundException e ) 1408 { 1409 final var xse = new XMLStreamException( format( MSG_InvalidValue, name.toString(), value ), attribute.getLocation(), e ); 1410 xse.initCause( e ); 1411 throw xse; 1412 } 1413 if( !StringConverter.class.isAssignableFrom( result ) ) 1414 { 1415 throw new XMLStreamException( format( MSG_InvalidStringConverter, result.getName() ), attribute.getLocation() ); 1416 } 1417 } 1418 else 1419 { 1420 throw new XMLStreamException( format( MSG_MissingValue, name.toString() ), attribute.getLocation() ); 1421 } 1422 1423 @SuppressWarnings( "unchecked" ) 1424 final var retValue = (Class<? extends StringConverter<?>>) result; 1425 1426 //---* Done *---------------------------------------------------------- 1427 return retValue; 1428 } // processAttrStringConversion() 1429 1430 /** 1431 * Processes the attribute {@value #XMLATTRIBUTE_Type}. 1432 * 1433 * @param attribute The attribute. 1434 * @return The class for the type. 1435 * @throws XMLStreamException The attribute is somehow invalid. 1436 */ 1437 private static final Class<?> processAttrType( final Attribute attribute ) throws XMLStreamException 1438 { 1439 final Class<?> retValue; 1440 1441 final var name = attribute.getName(); 1442 1443 final var value = attribute.getValue(); 1444 if( isNotEmptyOrBlank( value ) ) 1445 { 1446 try 1447 { 1448 retValue = Class.forName( value ); 1449 } 1450 catch( final ClassNotFoundException e ) 1451 { 1452 final var xse = new XMLStreamException( format( MSG_InvalidValue, name.toString(), value ), attribute.getLocation(), e ); 1453 xse.initCause( e ); 1454 throw xse; 1455 } 1456 } 1457 else 1458 { 1459 throw new XMLStreamException( format( MSG_MissingValue, name.toString() ), attribute.getLocation() ); 1460 } 1461 1462 //---* Done *---------------------------------------------------------- 1463 return retValue; 1464 } // processAttrType() 1465 1466 /** 1467 * Retrieves the CLI definition DTD file from the resources. 1468 * 1469 * @return The input stream for the file. 1470 */ 1471 @SuppressWarnings( "StaticMethodOnlyUsedInOneClass" ) 1472 @API( status = INTERNAL, since = "0.0.1" ) 1473 public static final InputStream retrieveCLIDefinitionDTD() 1474 { 1475 final var retValue = CLIDefinitionParser.class.getResourceAsStream( format( "/%s", CLI_DEFINITION_DTD ) ); 1476 1477 //---* Done *---------------------------------------------------------- 1478 return retValue; 1479 } // retrieveCLIDefinitionDTD() 1480 1481 /** 1482 * Retrieves the CLI definition XSD file from the resources. 1483 * 1484 * @return The input stream for the file. 1485 */ 1486 @API( status = INTERNAL, since = "0.0.1" ) 1487 public static final InputStream retrieveCLIDefinitionXSD() 1488 { 1489 final var retValue = CLIDefinitionParser.class.getResourceAsStream( format( "/%s", CLI_DEFINITION_XSD ) ); 1490 1491 //---* Done *---------------------------------------------------------- 1492 return retValue; 1493 } // retrieveCLIDefinitionXSD() 1494 1495 /** 1496 * <p>{@summary Retrieves the class for the value handler if the property 1497 * class is not an {@code enum} type.}</p> 1498 * <p>{@code enum} types have to be handled separately.</p> 1499 * 1500 * @param propertyClass The class of the property that should be set. 1501 * @return An instance of 1502 * {@link Optional} 1503 * that holds the effective handler class if the property class is not 1504 * an {@code enum}. 1505 */ 1506 private final Optional<Class<? extends CmdLineValueHandler<?>>> retrieveValueHandlerClass( final Class<?> propertyClass ) 1507 { 1508 final Optional<Class<? extends CmdLineValueHandler<?>>> retValue = requireNonNullArgument( propertyClass, "propertyClass" ).isEnum() 1509 ? Optional.empty() 1510 : Optional.ofNullable( m_HandlerClasses.get( propertyClass ) ); 1511 1512 //---* Done *---------------------------------------------------------- 1513 return retValue; 1514 } // retrieveValueHandlerClass() 1515 1516 /** 1517 * Validates the given XML. 1518 * 1519 * @throws XMLStreamException The validation failed. 1520 * @throws IOException Cannot read the given input stream. 1521 */ 1522 private final void validate() throws IOException, XMLStreamException 1523 { 1524 //---* Create the stream reader for validation *----------------------- 1525 final var xmlInputFactory = XMLInputFactory.newInstance(); 1526 final var streamReader = xmlInputFactory.createXMLStreamReader( new StringReader( m_XMLContents ) ); 1527 1528 //---* Create the schema factory for the validation *------------------ 1529 final var schemaFactory = SchemaFactory.newInstance( W3C_XML_SCHEMA_NS_URI ); 1530 try 1531 { 1532 final var schema = schemaFactory.newSchema( getCLIDefinitionXSDURL() ); 1533 1534 //---* Create the validator *-------------------------------------- 1535 final var validator = schema.newValidator(); 1536 1537 //---* Validate *-------------------------------------------------- 1538 validator.validate( new StAXSource( streamReader ) ); 1539 } 1540 catch( @SuppressWarnings( "CaughtExceptionImmediatelyRethrown" ) final IOException e ) 1541 { 1542 //---* Should not happen ... *------------------------------------- 1543 throw e; 1544 } 1545 catch( final SAXParseException e ) 1546 { 1547 final var xse = new XMLStreamException( e.getMessage(), new ExceptionLocation( e ), e ); 1548 xse.initCause( e ); 1549 throw xse; 1550 } 1551 catch( final SAXException e ) 1552 { 1553 throw new XMLStreamException( e.getMessage(), e ); 1554 } 1555 } // validate() 1556} 1557// class CLIDefinitionParser 1558 1559/* 1560 * End of File 1561 */