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 */