public abstract class CmdlineParameter
{
    /**
     * Constructor.
     *
     * @param name name of the parameter (e.g. "verbose")
     * @param shortName the short name of the parameter, if it has one (e.g. "v")
     * @param mayRepeat if true, this parameter may be provided more than once in the arguments
     */
    public CmdlineParameter(
            String  name,
            String  shortName,
            boolean mayRepeat )
    {
        theName      = name;
        theShortName = shortName;
        theMayRepeat = mayRepeat;
    }

    /**
     * Obtain the name.
     *
     * @return the name
     */
    public String getName()
    {
        return theName;
    }

    /**
     * Obtain the short name.
     *
     * @return the short name
     */
    public String getShortName()
    {
        return theShortName;
    }

    /**
     * Parse this parameter's value from this next element in the arguments.
     *
     * @param args the arguments
     * @param index the current index into the arguments
     * @return the number of arguments taken
     */
    public abstract int parseValues(
            String [] args,
            int       index );

    /**
     * Has a value been given for this parameter?
     */
    public abstract boolean hasValueSet();

    protected final String theName;
    protected final String theShortName;
    protected boolean theMayRepeat;

    /**
     * A parameter that takes no values but may be repeated.
     */
    public static class Flag
        extends
            CmdlineParameter
    {
        /**
         * Constructor.
         *
         * @param name name of the parameter
         * @param shortName short name of the parameter, if any
         * @param mayRepeat if true, this parameter may be provided more than once in the arguments
         */
        public Flag(
                String  name,
                String  shortName,
                boolean mayRepeat )
        {
            super( name, shortName, mayRepeat );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int parseValues(
                String [] args,
                int       index )
        {
            if( theCount > 0 && !theMayRepeat ) {
                CmdlineBootLoader.fatal( "Parameter must not be repeated: " + theName );
            }

            ++theCount;
            return 0;
        }

        /**
         * Determine how many times the parameter was set.
         */
        public int getCount()
        {
            return theCount;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean hasValueSet()
        {
            return theCount > 0;
        }

        /**
         * Counts the number of times the value was set.
         */
        protected int theCount;
    }

    /**
     * A parameter that takes a value and may be repeated.
     */
    public static class Value
        extends
            CmdlineParameter
    {
        /**
         * Constructor.
         *
         * @param name name of the parameter
         * @param shortName short name of the parameter, if any
         * @param mayRepeat if true, this parameter may be provided more than once in the arguments
         */
        public Value(
                String  name,
                String  shortName,
                boolean mayRepeat )
        {
            super( name, shortName, mayRepeat );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int parseValues(
                String [] args,
                int       index )
        {
            if( index > args.length-1 ) {
                CmdlineBootLoader.fatal( "Parameter needs value: " + theName );
            }
            if( !theValues.isEmpty() && !theMayRepeat ) {
                CmdlineBootLoader.fatal( "Parameter must not be repeated: " + theName );
            }
            theValues.add( args[index++] );
            return 1;
        }

        /**
         * Determine the value(s) of this parameter.
         */
        public List<String> getValues()
        {
            return theValues;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean hasValueSet()
        {
            return !theValues.isEmpty();
        }

        /**
         * The values of this parameter.
         */
        protected ArrayList<String> theValues = new ArrayList<>();
    }
} public class CmdlineParameters
{
    /**
     * Constructor, list allowed Parameters.
     *
     * @param pars the allowed parameters
     */
    public CmdlineParameters(
            CmdlineParameter ... pars )
    {
        for( CmdlineParameter par : pars ) {
            if( thePars.put( par.getName(), par ) != null ) {
                CmdlineBootLoader.fatal( "Programming error: have parameter with name already: " + par.getName() );
            }
            if( par.getShortName() != null ) {
                if( thePars.put( par.getShortName(), par ) != null ) {
                    CmdlineBootLoader.fatal( "Programming error: have parameter with name already: " + par.getShortName() );
                }
            }
        }
    }

    /**
     * Parse the command-line arguments.
     *
     * @param args the command-line arguments
     * @return the remaining arguments that are not interpreted as parameters
     */
    public String [] parse(
            String [] args )
    {
        String [] ret = null;

        int i=0;
        while( i<args.length ) {
            if( args[i].startsWith( "-" )) {
                args[i] = args[i].substring( 1 );
                if( args[i].startsWith( "-" )) {
                    args[i] = args[i].substring( 1 );
                }

                CmdlineParameter foundPar = thePars.get( args[i] );
                if( foundPar == null ) {
                    CmdlineBootLoader.fatal( "Unknown parameter: " + args[i] );
                }

                int taken = foundPar.parseValues( args, ++i ); // may fatal out
                i += taken;

            } else {
                // we are done
                ret = new String[ args.length-i ];
                System.arraycopy( args, i, ret, 0, args.length-i );
                break;
            }
        }
        if( ret != null ) {
            return ret;
        } else {
            return new String[0];
        }
    }

    /**
     * Has this named CmdlineParameter been given?
     *
     * @param name name of the CmdlineParameter
     * @return true or false
     */
    public boolean hasValueSetForKey(
            String name )
    {
        CmdlineParameter par = thePars.get( name );
        if( par == null ) {
            return false;
        }
        return par.hasValueSet();
    }

    /**
     * Obtain a CmdlineParameter
     *
     * @param name name of the CmdlineParameter
     * @return the CmdlineParameter
     */
    public CmdlineParameter get(
            String name )
    {
        return thePars.get( name );
    }

    /**
     * Obtain the value of flag parameter.
     */
    public int getFlagCount(
            String name )
    {
        CmdlineParameter par = thePars.get( name );

        int ret = ((CmdlineParameter.Flag)par).getCount();
        return ret;
    }

    /**
     * Obtain a single-valued parameter value.
     */
    public String getSingleValued(
            String name )
    {
        CmdlineParameter par = thePars.get( name );

        List<String> values = ((CmdlineParameter.Value)par).getValues();
        if( values.isEmpty() ) {
            return null;
        }
        if( values.size() == 1 ) {
            return values.get( 0 );
        }

        CmdlineBootLoader.fatal( "Internal error" );
        return null;
    }

    /**
     * Obtain a multiple-valued parameter value.
     */
    public List<String> getManyValued(
            String name )
    {
        CmdlineParameter par = thePars.get( name );

        List<String> values = ((CmdlineParameter.Value)par).getValues();
        return values;
    }

    /**
     * The defined parameters.
     */
    protected Map<String,CmdlineParameter> thePars = new HashMap<>();
} public class Module
{
    /**
     * Constructor. This should not be directly invoked by the application programmer.
     *
     * @param meta this Module's ModuleMeta
     * @param settings the settings for the Module
     * @param registry the registry of Modules in which we try to find dependent Modules
     * @param parentClassLoader the class loader of our parent Module
     */
    public Module(
            ModuleMeta             meta,
            ModuleSettings         settings,
            AbstractModuleRegistry registry,
            ClassLoader            parentClassLoader )
    {
        if( meta == null ) {
            throw new NullPointerException( "Null ModuleMeta" );
        }
        if( settings == null ) {
            throw new NullPointerException( "Null ModuleSettings" );
        }
        if( registry == null ) {
            throw new NullPointerException( "Null ModuleRegistry" );
        }
        if( parentClassLoader == null ) {
            throw new NullPointerException( "Null parent ClassLoader" );
        }

        theModuleMeta        = meta;
        theSettings          = settings;
        theRegistry          = registry;
        theParentClassLoader = parentClassLoader;
    }

    /**
     * Obtain the groupId of this Module. It is the same as the groupId of its ModuleMeta.
     *
     * @return the groupId of this Module
     */
    public final String getModuleGroupId()
    {
        return theModuleMeta.getModuleGroupId();
    }

    /**
     * Obtain the artifactId of this Module. It is the same as the artifactId of its ModuleMeta.
     *
     * @return the artifactId of this Module
     */
    public final String getModuleArtifactId()
    {
        return theModuleMeta.getModuleArtifactId();
    }

    /**
     * Obtain the version of this Module. It is the same as the version of its ModuleMeta.
     *
     * @return the version of this Module
     */
    public final String getModuleVersion()
    {
        return theModuleMeta.getModuleVersion();
    }

    /**
      * Obtain the ModuleMeta for this Module.
      *
      * @return the ModuleMeta for this Module
      */
    public final ModuleMeta getModuleMeta()
    {
        return theModuleMeta;
    }

    /**
     * Obtain the ModuleRegistry in which this Module has been registered.
     *
     * @return the ModuleRegistry in which this Module has been registered
     */
    public final ModuleRegistry getModuleRegistry()
    {
        return theRegistry;
    }

    /**
     * Obtain the settings for this Module. This will always return something
     * non-null, regardless of whether there are any settings
     *
     * @return the ModuleSettings
     */
    public final ModuleSettings getModuleSettings()
    {
        return theSettings;
    }

    /**
     * Convenience method to determine the dependent Modules of this Module
     * at run-time.
     *
     * @return the dependent Modules at run-time
     */
    public final Module [] determineRuntimeDependencies()
    {
        return theRegistry.determineRuntimeDependencies( this );
    }

    /**
     * Convenience method to determine the Modules that use this Module at
     * run-time.
     *
     * This is the inverse relationship found by determineRuntimeDependencies. However,
     * unlike determineRuntimeDependencies, the returned values may change dramatically
     * during operation of the system as additional Modules become users.
     *
     * @return the set of Modules that this Module currently is used by at run-time
     */
    public final Module [] determineRuntimeUses()
    {
        return theRegistry.determineRuntimeUses( this );
    }

    /**
     * Obtain a ClassLoader that knows how to load the code belonging to this Module.
     * This ClassLoader will delegate to the ClassLoader of the parent Module.
     *
     * @return this Module's ClassLoader
     * @throws MalformedURLException thrown if one of the URLs identifying the Module's JAR files is malformed
     */
    public synchronized final ClassLoader getClassLoader()
        throws
            MalformedURLException
    {
        if( theClassLoader == null ) {
            theClassLoader = theRegistry.createClassLoader( this, theParentClassLoader );
        }

        return theClassLoader;
    }

    /**
     * Determine whether this Module is active. A Module is active if it has been
     * activated and not deactivated afterwards.
     *
     * @return if true, this Module is active
     */
    public final boolean isActive()
    {
        return theActivationCount > 0 ;
    }

    /**
     * This recursively activates this Module. First, this method recursively activates all Modules
     * that it this Module depends on, and then it activates itself.
     *
     * @throws ModuleResolutionException thrown if a dependent Module cannot be resolved
     * @throws ModuleNotFoundException thrown if a dependent Module cannot be found
     * @throws ModuleActivationException thrown if this Module, or a dependent Module could not be activated
     */
    public final void activateRecursively()
        throws
            ModuleResolutionException,
            ModuleNotFoundException,
            ModuleActivationException
    {
        activateRecursively( getDefaultModuleActivator(), true );
    }

    /**
     * This recursively activates this Module. First, this method recursively activates all Modules
     * that it this Module depends on, and then it activates itself. By specifying a ModuleActivator,
     * a non-standard way of activating the Module can be performed.
     *
     * @param activator a ModuleActivator instance that knows how to activate this Module
     * @param activateAsRoot this is true if the Module is being activated as a root Module
     * @throws ModuleResolutionException thrown if a dependent Module cannot be resolved
     * @throws ModuleNotFoundException thrown if a dependent Module cannot be found
     * @throws ModuleActivationException thrown if this Module, or a dependent Module could not be activated
     */
    public final void activateRecursively(
            ModuleActivator activator,
            boolean         activateAsRoot )
        throws
            ModuleResolutionException,
            ModuleNotFoundException,
            ModuleActivationException
    {
        if( theActivationCount == 0 ) {
            boolean success = false;
            try {
                log.log( Level.FINER, "moduleActivateRecursivelyStarted: {0}", this );

                Module [] dependencies = theRegistry.determineRuntimeDependencies( this );

                for( int i=0 ; i<dependencies.length ; ++i ) {
                    if( dependencies[i] != null ) {
                        ModuleActivator childActivator = activator.dependentModuleActivator( dependencies[i] );
                        try {
                            dependencies[i].activateRecursively( childActivator, false ); // FIXME? Arguments?
                        } catch( Exception ex ) {
                            throw new ModuleActivationException( theModuleMeta, ex );
                        }
                    }
                }
                theContextObject = activator.activate();
                // this may throw an exception

                theIsActivatedAsRoot = activateAsRoot;
                success              = true;

            } finally {
                if( success ) {
                    log.log( Level.FINER, "moduleActivateRecursivelySucceeded: {0}", this );
                } else {
                    log.log( Level.FINER, "moduleActivateRecursivelyFailed: {0}", this );
                }

            }
        }
        ++theActivationCount;
    }

    /**
     * This recursively deactivates this Module. First, this method deactivates itself, and then
     * it recursively activates all Modules that this Module depends on
     *
     * @throws ModuleDeactivationException throws if the Module could not be deactivated
     */
    public final void deactivateRecursively()
        throws
            ModuleDeactivationException
    {
        deactivateRecursively( getDefaultModuleActivator() );
    }

    /**
     * This recursively deactivates this Module. First, this method deactivates itself, and then
     * it recursively activates all Modules that this Module depends on. By specifying a ModuleActivator,
     * a non-standard way of deactivating the Module can be performed.
     *
     * @param activator a ModuleActivator instance that knows how to deactivate instead of the default one specified in the ModuleMeta
     * @throws ModuleDeactivationException throws if the Module could not be deactivated
     */
    public final void deactivateRecursively(
            ModuleActivator activator )
        throws
            ModuleDeactivationException
    {
        --theActivationCount;
        theIsActivatedAsRoot = false; // in any case
        if( theActivationCount == 0 ) {
            Module [] dependencies = theRegistry.determineRuntimeDependencies( this );

            activator.deactivate();

            for( int i=0 ; i<dependencies.length ; ++i ) {
                if( dependencies[i] != null ) {
                    // might be an optional dependency
                    ModuleActivator childDeactivator = activator.dependentModuleActivator( dependencies[i] );
                    dependencies[i].deactivateRecursively( childDeactivator );
                }
            }
            // this may throw an exception
        }
    }

    /**
     * Obtain the context object returned by the activation method of this Module, if any.
     *
     * @return the context object, or null
     */
    public final Object getContextObject()
    {
        return theContextObject;
    }

    /**
     * Run this Module as a root Module.
     *
     * @param arguments arguments to run, similar to the arguments of a standard main(...) method
     * @throws ClassNotFoundException thrown if the specified run class cannot be found
     * @throws ModuleRunException thrown if the specified run method threw an Exception
     * @throws NoRunMethodException thrown if a suitable run method cannot be found
     * @throws InvocationTargetException thrown if the run method throws an Exception
     */
    public final void run(
            String [] arguments )
        throws
            ClassNotFoundException,
            ModuleRunException,
            NoRunMethodException,
            InvocationTargetException
    {
        run( null, null, arguments );
    }

    /**
     * Run this Module as a root Module. Specify a class to run other than the default,
     * and/or a method other than the main() method.
     *
     * @param overriddenRunClassName optional name of the class to run instead of the default one specified in the ModuleMeta
     * @param overriddenRunMethodName optional name of the method in the class to run instead of the default one specified in the ModuleMeta
     * @param arguments arguments to run, similar to the arguments of a standard main(...) method
     * @return the desired System exit code
     * @throws ClassNotFoundException thrown if the specified run class cannot be found
     * @throws ModuleRunException thrown if the specified run method threw an Exception
     * @throws NoRunMethodException thrown if a suitable run method cannot be found
     * @throws InvocationTargetException thrown if the run method throws an Exception
     */
    public int run(
            String    overriddenRunClassName,
            String    overriddenRunMethodName,
            String [] arguments )
        throws
            ClassNotFoundException,
            ModuleRunException,
            NoRunMethodException,
            InvocationTargetException
    {
        log.log( Level.FINER, "runStarted: {0} ({1} {2})", new Object[] { this, overriddenRunClassName, overriddenRunMethodName } );

        // look for a run method. If none found, throw an exception

        String runClassName  = overriddenRunClassName;
        String runMethodName = overriddenRunMethodName;

        try {
            if( runClassName == null ) {
                runClassName = theModuleMeta.getRunClassName();
            }
            if( runMethodName == null ) {
                runMethodName = RUN_METHOD_NAME;
            }

            if( runClassName == null ) {
                throw new NoRunMethodException( theModuleMeta, runClassName, runMethodName, null );
            }

            // invoke
            Class<?> runClass = Class.forName( runClassName, true, getClassLoader() );

            Method runMethod = runClass.getMethod(
                    runMethodName,
                    new Class<?>[] {
                            String[].class } );

            log.log( Level.FINER, "run: {0} ({1} {2})", new Object[] { this, runClass, runMethod } );

            Object ret = runMethod.invoke(
                    null,
                    new Object[] {
                            arguments } );

            log.log( Level.FINER, "runSucceeded: {0}", this );

            if( ret instanceof Number ) {
                return ((Number)ret).intValue();
            }  else {
                return 0; // everything seems fine
            }

        } catch( MalformedURLException ex ) {
            log.log( Level.SEVERE, "run failed: " + this, ex );
            return 1;

        } catch( InvocationTargetException ex ) {
            if( ex.getTargetException() instanceof ModuleRunException ) {
                throw (ModuleRunException) ex.getTargetException();
            } else {
                throw new ModuleRunException( theModuleMeta, runClassName, runMethodName, ex.getTargetException() );
            }

        } catch( Throwable ex ) {
            throw new NoRunMethodException( theModuleMeta, runClassName, runMethodName, ex );
       }
    }

    /**
     * Obtain the default ModuleActivator for this Module.
     *
     * @return the ModuleActivator
     */
    public ModuleActivator getDefaultModuleActivator()
    {
        return new DefaultModuleActivator( this );
    }

    /**
     * Determine Module equality. Two Modules are the same if they have the same
     * Module Advertisement.
     *
     * @param other other object to compare this Module to
     * @return returns true if the objects are the same
     */
    @Override
    public boolean equals(
            Object other )
    {
        if( other instanceof Module ) {
            return theModuleMeta.equals( ((Module)other).theModuleMeta );
        }
        return false;
    }

    /**
     * Determine hash code. We use the hash code of our ModuleMeta.
     *
     * @return the hash code
     */
    @Override
    public int hashCode()
    {
        return theModuleMeta.hashCode();
    }

    /**
     * For debugging.
     *
     * @return string representation of this object
     */
    @Override
    public String toString()
    {
        return theModuleMeta.toString();
    }

    /**
     * The AbstractModuleRegistry in which this Module is registered.
     */
    protected ModuleRegistry theRegistry;

    /**
     * The ModuleMeta for this Module.
     */
    protected ModuleMeta theModuleMeta;

    /**
     * The ModuleSettings for this Module.
     */
    protected ModuleSettings theSettings;

    /**
     * This Module's ClassLoader. Allocated as needed.
     */
    protected ClassLoader theClassLoader = null;

    /**
     * The parent ClassLoader of this Module's ClassLoader.
     */
    protected ClassLoader theParentClassLoader;

    /**
     * Once this Module has been activated, this is set to a non-zero value. Zero means
     * the Module is deactivated. A non-zero value N indicates that N other Modules
     * depend on this Module. In other words, other than keeping track whether or not
     * a Module is initialized, we also track how many Modules depend on it and thus
     * implement a form of "garbage-collection" policy.
     */
    private int theActivationCount = 0;

    /**
     * Flag set when this Module has been activated as a root Module. It is not set
     * if the Module has been activated as a dependency of some other Module.
     */
    private boolean theIsActivatedAsRoot = false;

    /**
     * Activation of this Module may create a context object, which is buffered here.
     */
    private Object theContextObject;

    /**
     * Name of the run method on a class.
     */
    public static final String RUN_METHOD_NAME = "main";

    /**
     * Logger.
     */
    private static final Logger log = Logger.getLogger( Module.class.getName() );
} public class ModuleActivationException
        extends
            ModuleException
{
    private static final long serialVersionUID = 1L; // helps with serialization

    /**
      * Constructor.
      *
      * @param meta the ModuleMeta for the Module whose activation failed.
      * @param org the Throwable that was the cause of this ModuleActivationException
      */
    public ModuleActivationException(
            ModuleMeta meta,
            Throwable  org )
    {
        super( meta, org );
    }

    /**
      * Constructor.
      *
      * @param meta the ModuleMeta for the Module whose activation failed.
      * @param msg a message explaining the cause of this ModuleActivationException
      */
    public ModuleActivationException(
            ModuleMeta meta,
            String     msg )
    {
        super( meta, msg, null );
    }

    /**
      * Constructor.
      *
      * @param meta the ModuleMeta for the Module whose activation failed.
      * @param msg a message explaining the cause of this ModuleActivationException
      * @param org the Throwable that was the cause of this ModuleActivationException
      */
    public ModuleActivationException(
            ModuleMeta meta,
            String     msg,
            Throwable  org )
    {
        super( meta, msg, org );
    }

    /**
     * For debugging.
     *
     * @return a string representation of this object
     */
    @Override
    public String toString()
    {
        StringBuilder buf = new StringBuilder( 100 ); // fudge
        buf.append( "ModuleActivationException: could not activate Module " );
        if( theModuleMeta != null ) {
            buf.append( theModuleMeta.toString() );
        } else {
            buf.append( "null" );
        }

        if( getMessage() != null ) {
            buf.append( ": ");
            buf.append( getMessage() );
        }
        return buf.toString();
    }
} public class ModuleDeactivationException
        extends
            ModuleException
{
    private static final long serialVersionUID = 1L; // helps with serialization

    /**
      * Constructor.
      *
      * @param meta the ModuleMeta for the Module whose deactivation failed.
      * @param org the Throwable that was the cause of this ModuleDeactivationException
      */
    public ModuleDeactivationException(
            ModuleMeta meta,
            Throwable  org )
    {
        super( meta, org );
    }

    /**
      * Constructor.
      *
      * @param meta the ModuleMeta for the Module whose deactivation failed.
      * @param msg a message explaining the cause of this ModuleDeactivationException
      */
    public ModuleDeactivationException(
            ModuleMeta meta,
            String     msg )
    {
        super( meta, msg, null );
    }

    /**
     * For debugging.
     *
     * @return a string representation of this object
     */
    @Override
    public String toString()
    {
        StringBuilder buf = new StringBuilder( 100 ); // fudge
        buf.append( "ModuleDeactivationException: could not deactivate Module " );
        if( theModuleMeta != null ) {
            buf.append( theModuleMeta.toString() );
        } else {
            buf.append( "null" );
        }

        if( getMessage() != null ) {
            buf.append( ": ");
            buf.append( getMessage() );
        }
        return buf.toString();
    }
} public class ModuleMeta
{
    /**
      * Constructor. This should not be directly invoked by the application programmer.
      *
      * @param moduleGroupId the Maven groupId of the to-be-created Module
      * @param moduleArtifactId the Maven artifactId of the to-be-created Module
      * @param moduleVersion the version of the to-be-created Module, may be null (but that's discouraged)
      * @param moduleUserNames the name shown to the user of the to-be-created Module, keyed by the locale
      * @param moduleUserDescriptions the description shown to the user of the to-be-created Module, keyed by the locale
      * @param moduleBuildDate the time when this Module was built
      * @param license the license for the to-be-created Module
      * @param runtimeModuleRequirements the ModuleRequirements of this Module at run time
      * @param moduleJar JAR file provided by this Module
      * @param activationClassName name of the Module's activation/deactivation class, or null
      * @param runClassName name of the class contained in this Module that contains the Module's run method, or null
      * @param moduleCapabilities set of ModuleCapabilities, or null
      */
    protected ModuleMeta(
            String                       moduleGroupId,
            String                       moduleArtifactId,
            String                       moduleVersion,
            Map<String,String>           moduleUserNames,
            Map<String,String>           moduleUserDescriptions,
            long                         moduleBuildDate,
            ModuleLicense                license,
            ModuleRequirement []         runtimeModuleRequirements,
            JarFile                      moduleJar,
            String                       activationClassName,
            String                       runClassName,
            Map<String,ModuleCapability> moduleCapabilities )
    {
        theModuleGroupId             = moduleGroupId;
        theModuleArtifactId          = moduleArtifactId;
        theModuleVersion             = moduleVersion;
        theModuleUserNames           = moduleUserNames;
        theModuleUserDescriptions    = moduleUserDescriptions;
        theModuleBuildDate           = moduleBuildDate;
        theModuleLicense             = license;
        theRuntimeModuleRequirements = runtimeModuleRequirements;
        theModuleJar                 = moduleJar;
        theActivationClassName       = activationClassName;
        theRunClassName              = runClassName;
        theModuleCapabilities        = moduleCapabilities;

        if( moduleJar == null ) {
            theResourceJarEntryPrefix = UNPACKED_RESOURCE_JAR_ENTRY_PREFIX;
        } else if( moduleJar.getName().endsWith( ".war" )) {
            theResourceJarEntryPrefix = WAR_RESOURCE_JAR_ENTRY_PREFIX;
        } else {
            theResourceJarEntryPrefix = JAR_RESOURCE_JAR_ENTRY_PREFIX;
        }
    }

    /**
      * Obtain the groupId of the Module.
      *
      * @return the groupId for this Module
      */
    public final String getModuleGroupId()
    {
        return theModuleGroupId;
    }

    /**
      * Obtain the artifactId of the Module.
      *
      * @return the artifactId for this Module
      */
    public final String getModuleArtifactId()
    {
        return theModuleArtifactId;
    }

    /**
     * Obtain the internationalized name of the Module in the current locale.
     *
     * @return the internationalized name of the Module, in the current locale
     */
    public String getModuleUserName()
    {
        return getModuleUserName( Locale.getDefault() );
    }

    /**
     * Obtain the internationalized name of the Module in a specified locale.
     *
     * @param loc the locale
     * @return the internationalized name of the Module, in the specified locale
     */
    public String getModuleUserName(
            Locale loc )
    {
        String ret = null;
        if( theModuleUserNames != null ) {
            String key1 = loc.getCountry();
            if( loc.getLanguage() != null ) {
                String key2 = key1 + "." + loc.getLanguage();
                if( loc.getVariant() != null ) {
                    String key3 = key2 + "." + loc.getVariant();

                    ret = theModuleUserNames.get( key3 );
                }
                if( ret == null ) {
                    ret = theModuleUserNames.get( key2 );
                }
            }
            if( ret == null ) {
                ret = theModuleUserNames.get( key1 );
            }
        }
        if( ret == null ) {
            ret = toString(); // reasonable default
        }
        return ret;
    }

    /**
     * Obtain the internationalized description of the Module in a specified locale.
     *
     * @param loc the locale
     * @return the internationalized description of the Module, in the specified locale
     */
    public String getModuleUserDescription(
            Locale loc )
    {
        String ret = null;
        if( theModuleUserDescriptions != null ) {
            String key1 = loc.getCountry();
            if( loc.getLanguage() != null ) {
                String key2 = key1 + "." + loc.getLanguage();
                if( loc.getVariant() != null ) {
                    String key3 = key2 + "." + loc.getVariant();

                    ret = theModuleUserDescriptions.get( key3 );
                }
                if( ret == null ) {
                    ret = theModuleUserDescriptions.get( key2 );
                }
            }
            if( ret == null ) {
                ret = theModuleUserDescriptions.get( key1 );
            }
        }
        return ret;
    }

    /**
     * Obtain the Map of internationalized names of the Module.
     *
     * @return the Map of internationalized names of the Module, keyed by locale
     */
    public Map<String,String> getModuleUserNames()
    {
        return theModuleUserNames;
    }

    /**
     * Obtain the Map of internationalized descriptions of the Module.
     *
     * @return the Map of internationalized descriptions of the Module, keyed by locale
     */
    public Map<String,String> getModuleUserDescriptions()
    {
        return theModuleUserDescriptions;
    }

    /**
     * Obtain the version of this Module.
     *
     * @return the version for of this Module; may be null
     */
    public final String getModuleVersion()
    {
        return theModuleVersion;
    }

    /**
     * Obtain the time at which this Module was built, in System.currentTimeMillis() format.
     *
     * @return the time at which this Module was built.
     */
    public final long getModuleBuildDate()
    {
        return theModuleBuildDate;
    }

    /**
     * Obtain the license for this Module.
     *
     * @return the license for this Module
     */
    public final ModuleLicense getModuleLicense()
    {
        return theModuleLicense;
    }

    /**
     * Obtain the list of requirements for other Modules that this Module depends on
     * at run time.
     *
     * @return the list of ModuleRequirements of this Module at run time
     */
    public final ModuleRequirement [] getRuntimeModuleRequirements()
    {
        return theRuntimeModuleRequirements;
    }

    /**
     * Obtain the JAR file that this Module provides as JarFile.
     *
     * @return the JarFile
     */
    public final JarFile getProvidesJar()
    {
        return theModuleJar;
    }

    /**
     * Obtain the relative path below which resources, such as a class files,
     * are to be found. In a WAR file, for example, that would be "WEB-INF/classes/".
     *
     * @return the prefix
     */
    public final String getResourceJarEntryPrefix()
    {
        return theResourceJarEntryPrefix;
    }

    /**
     * Obtain the name of the Module activation/deactivation class.
     * If this returns null, it means the Module has no activation method.
     *
     * @return name of the activation/deactivation class contained in this Module, or null if none
     */
    public final String getActivationClassName()
    {
        return theActivationClassName;
    }

    /**
     * Obtain the name of the class that contains the Module's run method.
     * If this returns null, it means the Module has no run method.
     *
     * @return name of the run class contained in this Module, or null if none
     */
    public final String getRunClassName()
    {
        return theRunClassName;
    }

    /**
     * Obtain the provided ModuleCapabilities, if any.
     *
     * @return the ModuleCapabilities
     */
    public final Map<String,ModuleCapability> getModuleCapabilities()
    {
        return theModuleCapabilities;
    }

    /**
     * Determine how well this Module provides the named capability.
     *
     * @param candidateName name of the candidate module capability
     * @return a score between 0.0 (does not match) and 1.0 (perfect match)
     */
    public double meetsCapability(
            String candidateName )
    {
        if( theModuleCapabilities == null ) {
            return 0.0d;
        }
        ModuleCapability found = theModuleCapabilities.get( candidateName );
        if( found == null ) {
            return 0.0d;
        }
        String matchQuality = found.getProperty( ModuleCapability.MATCH_QUALITY_PROPERTY );
        if( matchQuality != null ) {
            return Double.parseDouble( matchQuality );
        }
        return 1.0d;
    }

    /**
     * Two ModuleMetas are the same if they have the same name and version.
     *
     * @param other the object to test against
     * @return if they are equal, return true
     */
    @Override
    public boolean equals(
            Object other )
    {
        if( other == null ) {
            return false;
        }
        if( getClass() != other.getClass() ) {
            return false;
        }
        ModuleMeta realOther = (ModuleMeta) other;

        if( ! theModuleGroupId.equals( realOther.theModuleGroupId )) {
            return false;
        }
        if( ! theModuleArtifactId.equals( realOther.theModuleArtifactId )) {
            return false;
        }
        return theModuleVersion.equals( realOther.theModuleVersion );
    }

    /**
     * We determine the hash code by looking at the Module's name and version.
     *
     * @return a hash code
     */
    @Override
    public int hashCode()
    {
        int ret = theModuleGroupId.hashCode() % theModuleArtifactId.hashCode() % theModuleVersion.hashCode();

        return ret;
    }

    /**
     * Create a Module from this ModuleMeta. This is not supposed to be invoked
     * by the application programmer.
     *
     * @param settings the settings for the to-be-created Module
     * @param registry the AbstractModuleRegistry in which the to-be-created Module will look for dependent Modules
     * @param parentClassLoader the ClassLoader of our parent Module
     * @return the created Module
     */
    protected Module createModule(
            ModuleSettings         settings,
            AbstractModuleRegistry registry,
            ClassLoader            parentClassLoader )
    {
        return new Module( this, settings, registry, parentClassLoader );
    }

    /**
     * Obtain a String representation.
     *
     * @return String representation
     */
    @Override
    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        buf.append( theModuleGroupId );
        buf.append( ":" );
        buf.append( theModuleArtifactId );
        buf.append( ":" );
        buf.append( theModuleVersion );
        return buf.toString();
    }

    /**
     * The groupId of the module
     */
    protected final String theModuleGroupId;

    /**
     * The artifactId of the module.
     */
    protected final String theModuleArtifactId;

    /**
     * The version of the module.
     */
    protected final String theModuleVersion;

    /**
     * The time when this Module was built.
     */
    protected final long theModuleBuildDate;

    /**
     * The user-visible names for this Module, keyed by the locale.
     */
    protected final Map<String,String> theModuleUserNames;

    /**
     * The user-visible descriptions for this Module, keyed by the locale.
     */
    protected final Map<String,String> theModuleUserDescriptions;

    /**
     * The requirements for other modules that this Module will have at run time.
     */
    protected final ModuleRequirement [] theRuntimeModuleRequirements;

    /**
     * The license of the Module.
     */
    protected final ModuleLicense theModuleLicense;

    /**
     * The JAR that this Module provides.
     */
    protected final JarFile theModuleJar;

    /**
     * The relative path below which resources, such as a class files,
     * are to be found. Must be suitable for prepending without further processing,
     * i.e. must end with slash unless at the root.
     */
    protected final String theResourceJarEntryPrefix;

    /**
     * The resourceJarEntryPrefix for JAR files.
     */
    public static final String JAR_RESOURCE_JAR_ENTRY_PREFIX = "";

    /**
     * The resourceJarEntryPrefix for WAR files.
     */
    public static final String WAR_RESOURCE_JAR_ENTRY_PREFIX = "WEB-INF/classes/";

    /**
     * The resourceJarEntryPrefix for unpacked directories.
     */
    public static final String UNPACKED_RESOURCE_JAR_ENTRY_PREFIX = "";

    /**
     * The name of the Module activation/deactivation class.
     */
    protected final String theActivationClassName;

    /**
     * The name of the class in this Module which provides a method to
     * run this Module.
     */
    protected final String theRunClassName;

    /**
     * The capabilities of this Module, or null.
     */
    protected final Map<String,ModuleCapability> theModuleCapabilities;
} public class ModuleNotFoundException
        extends
            ModuleException
{
    private static final long serialVersionUID = 1L; // helps with serialization

    /**
      * Constructor.
      *
      * @param meta the ModuleMeta whose Module we could not find
      * @param cause the cause of this ModuleNotFoundException (e.g. IOException)
      */
    public ModuleNotFoundException(
            ModuleMeta meta,
            Throwable  cause )
    {
        super( meta, cause );
    }

    /**
     * For debugging.
     *
     * @return string representation of this object
     */
    @Override
    public String toString()
    {
        StringBuilder buf = new StringBuilder( 100 ); // fudge
        buf.append( "ModuleNotFoundException: could not resolve Module " );

        buf.append( theModuleMeta.toString() );
        buf.append( ", was looking for JAR file: " );
        buf.append( theModuleMeta.getProvidesJar() );

        return buf.toString();
    }
} public abstract class ModuleRegistry
{
    /**
     * Determine the ModuleMetas that are candidates to resolve a Module dependency, based
     * on the knowledge of this ModuleRegistry.
     *
     * @param req the ModuleRequirement that we attempt to resolve
     * @return the ModuleMetas that are candidates for the resolution of the ModuleRequirement, in sequence from
     *         best matched to least matched if the ModuleRegistry makes such distinctions
     */
    public abstract ModuleMeta [] determineResolutionCandidates(
            ModuleRequirement req );

    /**
     * Convenience method to determine the one and only ModuleMeta that is the only candidate to resolve a Module
     * dependency, based on the knowledge of this ModuleRegistry. If there is more or less than one
     * match, thrown an Exception.
     *
     * @param req the ModuleRequirement that we attempt to resolve
     * @return the ModuleMeta that is the only candidate for the resolution of the ModuleRequirement
     * @throws NoModuleResolutionCandidateException thrown if no ModuleMeta was found
     * @throws ModuleResolutionCandidateNotUniqueException thrown if there were more than one ModuleMeta found
     */
    public abstract ModuleMeta determineSingleResolutionCandidate(
            ModuleRequirement req )
        throws
            NoModuleResolutionCandidateException,
            ModuleResolutionCandidateNotUniqueException;

    /**
     * Recursively resolve this ModuleMeta into a Module.
     *
     * @param meta the ModuleMeta to resolve
     * @return the resolved Module.
     * @throws ModuleNotFoundException thrown if the Module could not be found
     * @throws ModuleResolutionException thrown if the Module could not be resolved
     */
    public abstract Module resolve(
            ModuleMeta meta )
        throws
            ModuleNotFoundException,
            ModuleResolutionException;

    /**
     * Resolve this ModuleMeta into a Module.
     *
     * @param meta the ModuleMeta to resolve
     * @param recursive resolve recursively if set to true
     * @return the resolved Module.
     * @throws ModuleNotFoundException thrown if the Module could not be found
     * @throws ModuleResolutionException thrown if the Module could not be resolved
     */
    public abstract Module resolve(
            ModuleMeta meta,
            boolean    recursive )
        throws
            ModuleNotFoundException,
            ModuleResolutionException;

    /**
     * Determine whether a certain ModuleMeta is resolved already, and if so,
     * return it. Do not attempt to resolve.
     *
     * @param meta the ModuleMeta whose resolution we check
     * @return the Module to which the ModuleMeta has been resolved already, or null if not
     */
    public abstract Module getResolutionOf(
            ModuleMeta meta );

    /**
     * Given a Module, this allows us to determine which other Modules it depends at
     * run-time.
     * This is similar to the result of determineResolutionCandidates(), except that
     * this returns one resolved Module for each dependency, not a set of ModuleMetas
     * that may or may not be resolvable.
     *
     * @param theModule the Module whose run-time dependencies we want to determine
     * @return the set of Modules that this Module depends on at run-time
     * @see #determineRuntimeUses
     */
    public abstract Module [] determineRuntimeDependencies(
            Module theModule );

    /**
     * Given a Module, this allows us to determine which other Modules use it at
     * run-time.
     *
     * This is the inverse relationship found by determineRuntimeDependencies. However,
     * unlike determineRuntimeDependencies, the returned values may change dramatically
     * during operation of the system as additional Modules become users.
     *
     * @param theModule the Module whose uses we want to determine at run-time
     * @return the set of Modules that this Module currently is used by at run-time
     * @see #determineRuntimeDependencies
     */
    public abstract Module [] determineRuntimeUses(
            Module theModule );

    /**
     * ModuleRegistries can also acts as a factory for the Modules' ClassLoaders.
     *
     * @param module the Module for which to create a ClassLoader
     * @param parentClassLoader the ClassLoader to use as the parent ClassLoader
     * @return the ClassLoader to use with the Module
     */
    protected abstract ClassLoader createClassLoader(
            Module      module,
            ClassLoader parentClassLoader );

    /**
     * Obtain the set of Module names currently contained in the registry.
     *
     * @return the set of Module names
     */
    public abstract Set<String> nameSet();

    /**
     * Obtain the set of Module names currently contained in the registry that match a
     * naming pattern.
     *
     * @param regex the regular expression
     * @return the set of Module names
     */
    public abstract Set<String> nameSet(
            Pattern regex );

    /**
     * Construct printable String, for error reporting.
     *
     * @return String
     */
    @Override
    public abstract String toString();

    /**
     * Add a ModuleRegistry listener to be notified when new Modules become available etc.
     *
     * @param newListener the new listener to add
     * @see #removeModuleRegistryListener
     */
    public abstract void addModuleRegistryListener(
            ModuleRegistryListener newListener );

    /**
     * Remove a ModuleRegistry listener.
     *
     * @param oldListener the listener to remove
     * @see #addModuleRegistryListener
     */
    public abstract void removeModuleRegistryListener(
            ModuleRegistryListener oldListener );
} public class ModuleRequirement
        implements
            Serializable
{
    private static final long serialVersionUID = 1L; // helps with serialization

    /**
      * Factory method using a String representation corresponding to toString().
      *
      * @param s the String to be parsed
      * @return the created ModuleRequirement
      * @throws ParseException thrown if the provided s did have an invalid syntax
      */
    public static ModuleRequirement parse(
            String s )
        throws
            ParseException
    {
        String [] parts = s.split( ":" );
        switch( parts.length ) {
            case 1:
                return new ModuleRequirement(
                        null,
                        parts[0],
                        null,
                        false,
                        null );
            case 2:
                return new ModuleRequirement(
                        parts[0].isEmpty() ? null : parts[0],
                        parts[1],
                        null,
                        false,
                        null );
            case 3:
                return new ModuleRequirement(
                        parts[0].isEmpty() ? null : parts[0],
                        parts[1],
                        parts[2].isEmpty() ? null : parts[2],
                        false,
                        null );
            default:
                throw new ParseException( "Not a valid Module identifier: " + s, 0 );
        }
    }

    /**
      * Construct one.
      *
      * @param requiredModuleGroupId the groupId of the required Module
      * @param requiredModuleArtifactId the artifactId of the required Module
      * @param requiredModuleVersion the version of the required Module, null if any
      * @param isOptional if true, this ModuleRequirement is optional
      * @param requiredCapabilities names of the required ModuleCapabilities, may be empty
      */
    protected ModuleRequirement(
            String    requiredModuleGroupId,
            String    requiredModuleArtifactId,
            String    requiredModuleVersion,
            boolean   isOptional,
            String [] requiredCapabilities )
    {
        if( requiredModuleGroupId != null && !MAVEN_ID_REGEX.matcher( requiredModuleGroupId ).matches() ) {
            throw new IllegalArgumentException( "Required module groupId contains invalid characters: " + requiredModuleGroupId );
        }
        if( requiredModuleArtifactId == null ) {
            if( requiredCapabilities.length == 0 ) {
                throw new IllegalArgumentException( "Must provide at least either a required module artifactId or required capabilities" );
            }
        } else {
            if( requiredModuleArtifactId.isEmpty() ) {
                throw new IllegalArgumentException( "Required module artifactId must not be null or an empty string" );
            }
            if( !MAVEN_ID_REGEX.matcher( requiredModuleArtifactId ).matches() ) {
                throw new IllegalArgumentException( "Required module artifactId contains invalid characters: " + requiredModuleArtifactId );
            }
        }
        if( requiredModuleVersion != null && requiredModuleVersion.isEmpty() ) {
            throw new IllegalArgumentException( "Required module version must not be an empty string" );
        }
        theRequiredModuleGroupId    = requiredModuleGroupId;
        theRequiredModuleArtifactId = requiredModuleArtifactId;
        theIsOptional               = isOptional;

        parseAndSetMinMaxVersions( requiredModuleVersion );

        theRequiredCapabilities = requiredCapabilities;
    }

    /**
      * Obtain the groupId of the Module that we require.
      *
      * @return the groupId of the Module that we require
      */
    public final String getRequiredModuleGroupId()
    {
        return theRequiredModuleGroupId;
    }

    /**
      * Obtain the artifactId of the Module that we require.
      *
      * @return the artifactId of the Module that we require
      */
    public final String getRequiredModuleArtifactId()
    {
        return theRequiredModuleArtifactId;
    }

    /**
     * Obtain  the version of the Module that we require. This returns the
     * uninterpreted version string.
     *
     * @return the version of the Module that we require
     */
    public final String getUninterpretedRequiredModuleVersion()
    {
        return theUninterpretedRequiredModuleVersion;
    }

    /**
     * Determine whether this ModuleRequirement is optional.
     *
     * @return true, if the ModuleRequirement is optional
     */
    public final boolean isOptional()
    {
        return theIsOptional;
    }

    /**
     * Determine the ModuleCapabilities provided by this Module.
     *
     * @return the ModuleCapabilities
     */
    public String [] getRequiredModuleCapabilities()
    {
        return theRequiredCapabilities;
    }

    /**
     * Determine how well a candidate ModuleMeta meets this ModuleRequirement.
     *
     * @param candidate the candidate ModuleMeta
     * @return 0: does not match: 1: perfect match, 2: new version
     */
    public double matches(
            ModuleMeta candidate )
    {
        if( !matchesCoordinates( candidate )) {
            return 0.0d;
        }

        double score = 1.0d;
        if( theRequiredCapabilities != null ) {
            for( int i=0 ; i<theRequiredCapabilities.length ; ++i ) {
                double match = candidate.meetsCapability( theRequiredCapabilities[i] );
                if( match == 0.0d ) {
                    return 0;
                } else {
                    score *= match;
                }
            }
        }
        return score;
    }

    /**
     * Given an ordered set of ModuleMetas, find the matching versions.
     *
     * @param candidates the ModuleMeta candidates
     * @return the matched ModuleMetas
     */
    public ModuleMeta [] findVersionMatchesFrom(
            ModuleMeta [] candidates )
    {
        ModuleMeta [] perfect = new ModuleMeta[ candidates.length ];
        ModuleMeta [] later   = new ModuleMeta[ candidates.length ];

        int perfectCount = 0;
        int laterCount   = 0;
        for( int i=0 ; i<candidates.length ; ++i ) {
            switch( matchesVersion( candidates[i] )) {
                case 1:
                    perfect[perfectCount++] = candidates[i];
                    break;
                case 2:
                    later[laterCount++] = candidates[i];
                    break;
            }
        }
        ModuleMeta [] ret = new ModuleMeta[ perfectCount + laterCount ];
        System.arraycopy( perfect, 0, ret, 0, perfectCount );
        System.arraycopy( later, 0, ret, perfectCount, laterCount );

        return ret;
    }

    /**
     * Determine whether the provide ModuleMeta meets the version requirements (but no other requirements).
     *
     * @param candidate the candidate ModuleMeta
     * @return 0: does not match: 1: matches perfectly, 2: new version
     */
    public int matchesVersion(
            ModuleMeta candidate )
    {
        return matchesVersion( candidate.getModuleVersion() );
    }

    /**
     * Determine whether the provided version String meets the version requirement in this ModuleRequirement.
     *
     * @param version the version string
     * @return 0: does not match: 1: matches perfectly, 2: new version
     */
    public int matchesVersion(
            String version )
    {
        // for speed purposes, get exact min version requirement out of the way
        if( theMinRequiredModuleVersionIsInclusive && version != null && version.equals( theMaxRequiredModuleVersion )) {
            return 1;
        }

        ensureVersionsParsed();

        Object [][] parsedVersion = parseVersion( version );

        int ret = 1;
        if( theParsedMinRequiredModuleVersion != null ) {
            int comp = compareParsedVersions( theParsedMinRequiredModuleVersion, parsedVersion );
            if( comp > 0 ) {
                return 0;
            }
            if( comp == 0 && !theMinRequiredModuleVersionIsInclusive ) {
                return 0;
            }
            if( comp != 0 ) {
                ret = 2;
            }
        }

        if( theParsedMaxRequiredModuleVersion != null ) {
            int comp = compareParsedVersions( theParsedMaxRequiredModuleVersion, parsedVersion );
            if( comp < 0 ) {
                return 0;
            }
            if( comp == 0 && !theMaxRequiredModuleVersionIsInclusive ) {
                return 0;
            }
        }

        return ret;
    }

    /**
     * Determine whether the provided ModuleMeta matches the coordinate requirements,
     * i.e. excluding the CapabilityRequirements.
     *
     * @param candidate the candidate ModuleMeta
     * @return true or false
     */
    public boolean matchesCoordinates(
            ModuleMeta candidate )
    {
        if(    theRequiredModuleGroupId != null
            && !theRequiredModuleGroupId.equals( candidate.getModuleGroupId()) )
        {
            return false;
        }

        if(    theRequiredModuleArtifactId != null
            && !theRequiredModuleArtifactId.equals( candidate.getModuleArtifactId()) )
        {
            return false;
        }

        if( matchesVersion( candidate.getModuleVersion() ) == 0 ) {
            return false;
        }
        return true;
    }

    /**
     * Compare two parsed versions.
     *
     * @param a the first parsed version
     * @param b the second parsed version
     * @return -1, 0 or 1 like strcmp()
     */
    protected int compareParsedVersions(
            Object [][] a,
            Object [][] b )
    {
        int max = Math.min( a.length, b.length );
        for( int i=0 ; i<max ; ++i ) {
            if( a[i] != null ) {
                if( b[i] != null ) {
                    int found = compareParsedVersionParts( a[i], b[i] );
                    if( found != 0 ) {
                        return found;
                    }
                } else {
                    return 1;
                }
            } else {
                if( b[i] != null ) {
                    return -1;

                } else {
                    // do nothing; should not really occur
                }
            }
        }
        if( a.length > max ) {
            return 1;
        } else if( b.length > max ) {
            return -1;
        } else {
            return 0;
        }
    }

    /**
     * Helper method to compare version parts.
     *
     * @param a first version part
     * @param b second version part
     * @return -1, 0 or 1 like strcmp()
     */
    protected int compareParsedVersionParts(
            Object [] a,
            Object [] b )
    {
        int max = Math.min( a.length, b.length );
        while( max > 0 && a[max-1] == null && b[max-1] == null ) {
            --max;
        }
        if( max == 0 ) {
            return 0;
        }
        for( int i=0 ; i<max ; ++i ) {
            if( a[i] != null ) {
                if( b[i] != null ) {
                    if( a[i] instanceof String ) {
                        if( b[i] instanceof String ) {
                            int found = ((String)a[i]).compareTo( (String)b[i] );
                            if( found != 0 ) {
                                return found;
                            }
                        } else {
                            return 1; // int before string
                        }
                    } else {
                        if( b[i] instanceof String ) {
                            return -1; // int before string
                        } else {
                            int found = ((Long)a[i]).compareTo( (Long)b[i] );
                            if( found != 0 ) {
                                return found;
                            }
                        }
                    }
                } else {
                    return 1;
                }
            } else {
                if( b[i] != null ) {
                    return -1;

                } else {
                    return 0;
                }
            }
        }

        if( a.length > max ) {
            if( a[max] != null ) {
                return 1; // so b[max] must not exist or be null
            } else {
                return -1;
            }
        } else if( b.length > max ) {
            if( b[max] != null ) {
                return -1;
            } else {
                return 0; // should that happen?
            }
        } else {
            return 0;
        }
    }

    /**
     * Parse a version string, and set the properties on this instance accordingly.
     *
     * @param s the version string
     */
    protected void parseAndSetMinMaxVersions(
            String s )
    {
        theUninterpretedRequiredModuleVersion = s;

        theParsedMinRequiredModuleVersion = null;
        theParsedMaxRequiredModuleVersion = null;

        if( s == null ) {
            theMinRequiredModuleVersion = null;
            theMaxRequiredModuleVersion = null;
            theMinRequiredModuleVersionIsInclusive = true;
            theMaxRequiredModuleVersionIsInclusive = true;
        } else {
            Matcher m = MAVEN_VERSION_REGEX.matcher( s );
            if( m.matches() ) {
                theMinRequiredModuleVersion = m.group( 2 );
                theMaxRequiredModuleVersion = m.group( 3 );
                theMinRequiredModuleVersionIsInclusive = "[".equals( m.group( 1 ));
                theMaxRequiredModuleVersionIsInclusive = "]".equals( m.group( 4 ));
            } else {
                theMinRequiredModuleVersion = s;
                theMaxRequiredModuleVersion = null;
                theMinRequiredModuleVersionIsInclusive = true;
                theMaxRequiredModuleVersionIsInclusive = true;
            }
        }
    }

    /**
     * Ensure that the min and max versions have been parsed.
     */
    protected synchronized void ensureVersionsParsed()
    {
        if( theMinRequiredModuleVersion != null ) {
            if( theParsedMinRequiredModuleVersion == null ) {
                theParsedMinRequiredModuleVersion = parseVersion( theMinRequiredModuleVersion );
            }
        } else {
            theParsedMinRequiredModuleVersion = null;
        }
        if( theMaxRequiredModuleVersion != null ) {
            if( theParsedMaxRequiredModuleVersion == null ) {
                theParsedMaxRequiredModuleVersion = parseVersion( theMaxRequiredModuleVersion );
            }
        } else {
            theParsedMaxRequiredModuleVersion = null;
        }
    }

    /**
     * Take a version string and parse it into its components.
     *
     * @param v the version string
     * @return the components, left to right
     */
    protected Object [][] parseVersion(
            String v )
    {
        String []                    major  = v.split( "\\." );
        ArrayList<ArrayList<Object>> almost = new ArrayList<>();

        for( int i=0 ; i<major.length ; ++i ) {
            StringBuilder currentString = null; // once non-null, we know we are parsing a string
            long          currentLong   = -1;

            almost.add( new ArrayList<>());
            for( int j=0 ; j<major[i].length() ; ++j ) {
                char c = major[i].charAt( j );
                if( Character.isDigit( c )) {
                    if( currentString != null ) {
                        almost.get( i ).add( currentString.toString());
                        currentString = null;

                        currentLong = Character.digit( c, 10 );
                    } else {
                        if( currentLong == -1 ) {
                            currentLong = Character.digit( c, 10 );
                        } else {
                            currentLong = currentLong * 10 + Character.digit( c, 10 );
                        }
                    }
                } else {
                    if( currentString != null ) {
                        currentString.append( c );
                    } else {
                        currentString = new StringBuilder();
                        currentString.append( c );
                        almost.get( i ).add( currentLong );
                        currentLong = -1;
                    }
                }
            }
            if( currentString != null ) {
                almost.get( i ).add( currentString.toString());
            } else if( currentLong != -1 ) {
                almost.get( i ).add( currentLong );
            }
        }

        Object [][] ret = new Object[ almost.size() ][];
        for( int i=0 ; i<ret.length ; ++i ) {
            ret[i] = new Object[ almost.get( i ).size() ];
            for( int j=0 ; j<ret[i].length ; ++j ) {
                ret[i][j] = almost.get(i).get(j);
            }
        }
        return ret;
    }

    /**
     * Obtain a string representation of this object, for debugging purposes.
     *
     * @return string representation of this object
     */
    @Override
    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        if( theRequiredModuleGroupId != null ) {
            buf.append( theRequiredModuleGroupId );
        }
        buf.append( ":" );
        buf.append( theRequiredModuleArtifactId );
        buf.append( ":" );
        if( theMinRequiredModuleVersion != null && theMinRequiredModuleVersionIsInclusive && theMaxRequiredModuleVersion == null ) {
            // short representation
            buf.append( theMinRequiredModuleVersion );

        } else if( theMinRequiredModuleVersion != null || theMaxRequiredModuleVersion != null ) {
            // interval representation
            if( theMinRequiredModuleVersionIsInclusive ) {
                buf.append( "[" );
            } else {
                buf.append( "(" );
            }
            if( theMinRequiredModuleVersion != null ) {
                buf.append( theMinRequiredModuleVersion );
            }
            buf.append( "," );
            if( theMaxRequiredModuleVersion != null ) {
                buf.append( theMaxRequiredModuleVersion );
            }
            if( theMaxRequiredModuleVersionIsInclusive ) {
                buf.append( "]" );
            } else {
                buf.append( ")" );
            }
        }
        return buf.toString();
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(
            Object obj )
    {
        if( this == obj ) {
            return true;
        }
        if( obj == null ) {
            return false;
        }
        if( !( obj instanceof ModuleRequirement )) {
            return false;
        }
        final ModuleRequirement other = (ModuleRequirement) obj;
        if( !Objects.equals( this.theRequiredModuleGroupId, other.theRequiredModuleGroupId )) {
            return false;
        }
        if( !Objects.equals( this.theRequiredModuleArtifactId, other.theRequiredModuleArtifactId )) {
            return false;
        }
        if( !Objects.equals( this.theMinRequiredModuleVersion, other.theMinRequiredModuleVersion )) {
            return false;
        }
        if( !Objects.equals( this.theMaxRequiredModuleVersion, other.theMaxRequiredModuleVersion )) {
            return false;
        }
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode()
    {
        int hash = 5;
        hash = 31 * hash + Objects.hashCode(this.theRequiredModuleGroupId);
        hash = 31 * hash + Objects.hashCode(this.theRequiredModuleArtifactId);
        hash = 31 * hash + Objects.hashCode(this.theMinRequiredModuleVersion);
        hash = 31 * hash + Objects.hashCode(this.theMaxRequiredModuleVersion);
        return hash;
    }

    /**
     * The groupId of the required Module.
     */
    protected String theRequiredModuleGroupId;

    /**
     * The artifactId of the required Module.
     */
    protected String theRequiredModuleArtifactId;

    /**
     * The uninterpreted version string of the ModuleRequirement.
     */
    protected String theUninterpretedRequiredModuleVersion;

    /**
     * The minimum version of the required Module.
     */
    protected String theMinRequiredModuleVersion;

    /**
     * Is the minimum version inclusive.
     */
    protected boolean theMinRequiredModuleVersionIsInclusive;

    /**
     * Parsed form of the minimum version of the required Module.
     * Calculated on demand.
     */
    protected Object [][] theParsedMinRequiredModuleVersion;

    /**
     * The maximum version of the required Module.
     */
    protected String theMaxRequiredModuleVersion;

    /**
     * Is the maximum version inclusive.
     */
    protected boolean theMaxRequiredModuleVersionIsInclusive;

    /**
     * Parsed form of the maximum version of the required Module.
     * Calculated on demand.
     */
    protected Object [][] theParsedMaxRequiredModuleVersion;

    /**
     * Is this dependency optional.
     */
    protected boolean theIsOptional;

    /**
     * The set of names of the required ModuleCapabilities.
     */
    protected String [] theRequiredCapabilities;

    /**
     * The regex defining Maven version expressions.
     */
    public static Pattern MAVEN_VERSION_REGEX = Pattern.compile(
            "([\\[\\(])([^,\\[\\]\\(\\)]*),([^,\\[\\]\\(\\)]*)(\\]\\))" );

    /**
     * The regex that groupId and artifactId strings must match.
     */
    public static Pattern MAVEN_ID_REGEX = Pattern.compile(
            "[-a-zA-Z0-9._]+");


    /**
     * A builder class for ModuleRequirement.
     */
    public static class Builder
    {
        /**
         * Constructor.
         */
        public Builder()
        {}

        /**
         * Set the groupId of the required Module.
         *
         * @param requiredModuleGroupId the groupId of the required Module
         * @return self
         */
        public Builder requiredModuleGroupId(
                String requiredModuleGroupId )
        {
            theRequiredModuleArtifactId = requiredModuleGroupId;
            return this;
        }

        /**
         * Set the artifactId of the required Module.
         *
         * @param requiredModuleArtifactId the artifactId of the required Module
         * @return self
         */
        public Builder requiredModuleArtifactId(
                String requiredModuleArtifactId )
        {
            theRequiredModuleArtifactId = requiredModuleArtifactId;
            return this;
        }

        /**
         * Set the version of the required Module.
         *
         * @param requiredModuleVersion the version of the required Module
         * @return self
         */
        public Builder requiredModuleVersion(
                String requiredModuleVersion )
        {
            theRequiredModuleVersion = requiredModuleVersion;
            return this;
        }

        /**
         * Set whether the required Module is optional.
         *
         * @param isOptional if true, the Module is optional
         * @return self
         */
        public Builder isOptional(
                boolean isOptional )
        {
            theIsOptional = isOptional;
            return this;
        }

        /**
         * Add a named required ModuleCapability.
         *
         * @param name the name of the required ModuleCapability
         * @return self
         */
        public Builder requiredCapability(
                String name )
        {
            if( theCapabilities == null ) {
                theCapabilities = new HashSet<>();
            }
            theCapabilities.add( name );
            return this;
        }

        /**
         * Create the ModuleRequirement
         *
         * @return the ModuleRequirement
         */
        public ModuleRequirement build()
        {
            String [] capabilitiesArray;
            if( theCapabilities == null ) {
                capabilitiesArray = new String[0];
            } else {
                capabilitiesArray = new String[ theCapabilities.size() ];
                theCapabilities.toArray( capabilitiesArray );
            }
            return new ModuleRequirement(
                    theRequiredModuleGroupId,
                    theRequiredModuleArtifactId,
                    theRequiredModuleVersion,
                    theIsOptional,
                    capabilitiesArray );
        }

        /**
         * The required Module groupId, if any.
         */
        protected String theRequiredModuleGroupId;

        /**
         * The required Module artifactId, if any.
         */
        protected String theRequiredModuleArtifactId;

        /**
         * The required Module version, if any.
         */
        protected String theRequiredModuleVersion;

        /**
         * The optionality of the Module.
         */
        protected boolean theIsOptional = false;

        /**
         * Set of named capabilities.
         */
        protected Set<String> theCapabilities;
    }
} public class ModuleResolutionException
        extends
            ModuleException
{
    private static final long serialVersionUID = 1L; // helps with serialization

    /**
      * Constructor.
      *
      * @param meta the ModuleMeta whose ModuleRequirement could not be met
      * @param req the ModuleRequirement that could not be met
      * @param cause the Throwable that caused this Exception, if any
      */
    public ModuleResolutionException(
             ModuleMeta        meta,
             ModuleRequirement req,
             Throwable         cause )
    {
        super( meta, cause );

        theRequirement = req;
    }

    /**
     * Obtain the ModuleRequirement that could not be met.
     * 
     * @return the ModuleRequirement
     */
    public ModuleRequirement getModuleRequirement()
    {
        return theRequirement;
    }

    /**
     * Returns the detail message string of this Throwable.
     *
     * @return  the detail message string of this <tt>Throwable</tt> instance
     *          (which may be <tt>null</tt>).
     */
    @Override
    public String getMessage()
    {
        StringBuilder buf = new StringBuilder( 100 ); // fudge
        buf.append( "Could not resolve ModuleRequirement " );
        if( theRequirement != null ) {
            buf.append( theRequirement.toString() );
        } else {
            buf.append( "null" );
        }

        buf.append( " within Module " );
        if( theModuleMeta != null ) {
            buf.append( theModuleMeta.toString() );
        } else {
            buf.append( "null" );
        }

        return buf.toString();
    }

    /**
     * The ModuleRequirement that could not be met.
     */
    protected ModuleRequirement theRequirement;
} public class ModuleRunException
        extends
            ModuleException
{
    private static final long serialVersionUID = 1L; // helps with serialization

    /**
      * Constructor.
      *
      * @param meta the ModuleMeta whose Module we wanted to run
      * @param runClassName the name of the class whose run method we wanted to execute, or null if not known
      * @param runMethodName the name of the method within the runClassName that we wanted to execute, or null if not known
      * @param cause the Throwable that caused this Exception
      */
    public ModuleRunException(
             ModuleMeta meta,
             String     runClassName,
             String     runMethodName,
             Throwable  cause )
    {
        super( meta, cause );

        theRunClassName  = runClassName;
        theRunMethodName = runMethodName;
    }

    /**
     * For debugging.
     *
     * @return this object in printable format
     */
    @Override
    public String toString()
    {
        return getMessage();
    }

    /**
     * Obtain the message.
     *
     * @return the message
     */
    @Override
    public String getMessage()
    {
        StringBuilder buf = new StringBuilder( 100 );
        buf.append( "ModuleRunException: Module: " );
        if( theModuleMeta != null ) {
            buf.append( theModuleMeta.toString() );
        } else {
            buf.append( "null" );
        }

        buf.append( ", cannot execute run method " );
        buf.append( theRunMethodName );
        buf.append( " in class " );
        buf.append( theRunClassName );
        return buf.toString();
    }

    /**
     * Name of the class whose run method we were trying to run.
     */
    protected String theRunClassName;

    /**
     * Name of the method within theRunClassName that we were trying to run.
     */
    protected String theRunMethodName;
} public class ModuleSettings
{
    /**
     * Create a new ModuleSettings object.
     *
     * @param map the content
     * @return the ModuleSettings
     */
    public static ModuleSettings create(
            Map<String,String> map )
    {
        return new ModuleSettings( map );
    }

    /**
     * Constructor for subclasses only.
     *
     * @param map the content
     */
    protected ModuleSettings(
            Map<String,String> map )
    {
        theMap = map;
    }

    /**
     * Obtain iterator over all keys in the settings.
     *
     * @return the Iterator over the keys
     */
    public Iterable<String> keysIterator()
    {
        if( theMap != null ) {
            return theMap.keySet();
        } else {
            return EMPTY;
        }
    }

    /**
     * Obtain a a particular setting as string.
     *
     * @param key the key for the setting
     * @return the value for the setting, or null
     */
    public String getString(
            String key )
    {
        return getString( key, null );
    }

    /**
     * Obtain a a particular setting as string.
     *
     * @param key the key for the setting
     * @param defaultValue the default value, if the setting does not exist
     * @return the value for the setting, or the defaultValue
     */
    public String getString(
            String key,
            String defaultValue )
    {
        if( theMap == null ) {
            return defaultValue;
        }
        String found = theMap.get( key );
        if( found != null ) {
            return found;
        } else {
            return defaultValue;
        }
    }

    /**
     * Obtain a a particular setting as integer.
     *
     * @param key the key for the setting
     * @return the value for the setting, or null
     */
    public Integer getInteger(
            String key )
    {
        return getInteger( key, null );
    }

    /**
     * Obtain a a particular setting as integer.
     *
     * @param key the key for the setting
     * @param defaultValue the default value, if the setting does not exist
     * @return the value for the setting, or the defaultValue
     */
    public Integer getInteger(
            String  key,
            Integer defaultValue )
    {
        if( theMap == null ) {
            return defaultValue;
        }
        String found = theMap.get( key );
        if( found != null ) {
            return Integer.parseInt( found );
        } else {
            return defaultValue;
        }
    }

    /**
     * Obtain a a particular setting as boolean.
     *
     * @param key the key for the setting
     * @return the value for the setting, or null
     */
    public Boolean getBoolean(
            String key )
    {
        return getBoolean( key, null );
    }

    /**
     * Obtain a a particular setting as boolean.
     *
     * @param key the key for the setting
     * @param defaultValue the default value, if the setting does not exist
     * @return the value for the setting, or the defaultValue
     */
    public Boolean getBoolean(
            String  key,
            Boolean defaultValue )
    {
        if( theMap == null ) {
            return defaultValue;
        }
        String found = theMap.get( key );
        if( found != null ) {
            return Boolean.parseBoolean( found );
        } else {
            return defaultValue;
        }
    }

    /**
     * Determine whether a particular key exists.
     *
     * @param key the key for the setting
     * @return true or false
     */
    public boolean containsKey(
            String key )
    {
        if( theMap != null ) {
            return theMap.containsKey( key );
        } else {
            return false;
        }
    }

    /**
     * Internal map.
     */
    protected Map<String,String> theMap;

    /**
     * An iterable that always returns nothing.
     */
    protected static final Iterable<String> EMPTY = () -> new Iterator<String>() {
        @Override
        public String next() {
            return null;
        }
        @Override
        public boolean hasNext() {
            return false;
        }
    };
} public class NoRunMethodException
        extends
            ModuleException
{
    private static final long serialVersionUID = 1L; // helps with serialization

    /**
      * Constructor.
      *
      * @param meta the ModuleMeta whose Module we wanted to run
      * @param runClassName the name of the class whose run method we wanted to execute, or null if not known
      * @param runMethodName the name of the method within the runClassName that we wanted to execute, or null if not known
      * @param cause the Throwable that caused this Exception
      */
    public NoRunMethodException(
             ModuleMeta meta,
             String     runClassName,
             String     runMethodName,
             Throwable  cause )
    {
        super( meta, cause );

        theRunClassName  = runClassName;
        theRunMethodName = runMethodName;
    }

    /**
     * For debugging.
     *
     * @return this object in printable format
     */
    @Override
    public String toString()
    {
        StringBuilder buf = new StringBuilder( 100 );
        buf.append( "NoRunMethodException: Module: " );
        if( theModuleMeta != null ) {
            buf.append( theModuleMeta.toString() );
        } else {
            buf.append( "null" );
        }

        buf.append( ", cannot find or access run method " );
        buf.append( theRunMethodName );
        buf.append( " in class " );
        buf.append( theRunClassName );
        return buf.toString();
    }

    /**
     * Name of the class whose run method we were trying to run.
     */
    protected String theRunClassName;

    /**
     * Name of the method within theRunClassName that we were trying to run.
     */
    protected String theRunMethodName;
} public class ScanningDirectoriesModuleRegistry
    extends
        AbstractScanningModuleRegistry
{
    /**
     * Factory method. Delegate the default set of classes to the system class loader.
     *
     * @param dirs the directories to scan
     * @param moduleSettings the settings for the modules
     * @return the created ScanningDirectoriesModuleRegistry
     */
    public static ScanningDirectoriesModuleRegistry create(
            File []                               dirs,
            Map<ModuleRequirement,ModuleSettings> moduleSettings )
    {
        return create( dirs, moduleSettings, AbstractModuleRegistry.DEFAULT_DO_NOT_LOAD_CLASS_PREFIXES );
    }

    /**
     * Factory method.
     *
     * @param dirs the directories to scan
     * @param moduleSettings the settings for the modules
     * @param doNotLoadClassPrefixes prefixes of classes always to be loaded through the system class loader, not this one
     * @return the created ScanningDirectoriesModuleRegistry
     */
    public static ScanningDirectoriesModuleRegistry create(
            File []                               dirs,
            Map<ModuleRequirement,ModuleSettings> moduleSettings,
            String []                             doNotLoadClassPrefixes )
    {
        if( dirs == null || dirs.length == 0 ) {
            dirs = DEFAULT_MODULE_DIRECTORIES;
        }

        List<JarFile> jars     = new ArrayList<>();
        for( int i=0 ; i<dirs.length ; ++i ) {

            if( !dirs[i].exists() ) {
                // silently ignore
                continue;
            }
            if( !dirs[i].isDirectory() ) {
                throw new IllegalArgumentException( "Not a directory: " + dirs[i].getAbsolutePath() );
            }

            try {
                List<JarFile> newJars = Files.walk( dirs[i].toPath() )
                        .filter( ( Path f ) -> {
                                String name = f.getFileName().toString();
                                return name.endsWith( ".jar" ) || name.endsWith( ".war" ); } )
                        .filter( f -> Files.isRegularFile( f, LinkOption.NOFOLLOW_LINKS ))
                        .map( (Path f ) -> {
                                try {
                                    return new JarFile( f.toFile() );
                                } catch( IOException ex ) {
                                    log.log( Level.SEVERE, "Cannot access {0}: {1}", new Object[]{ f.toString(), ex.getLocalizedMessage() });
                                    return null;
                                }} )
                        .filter( f-> ( f != null )) // happens if previous step returned null
                        .collect( Collectors.toList() );
                jars.addAll( newJars );

            } catch( IOException ex ) {
                log.log( Level.SEVERE, "I/O Error", ex );
            }
        }

        HashMap<String,MiniModuleMetaMap> metas = new HashMap<>();
        addParsedModuleMetasFromJars( jars, metas );

        ScanningDirectoriesModuleRegistry ret = new ScanningDirectoriesModuleRegistry(
                dirs,
                metas,
                moduleSettings,
                doNotLoadClassPrefixes );
        return ret;
    }

    /**
     * Private constructor, use factory method.
     *
     * @param dirs the directories that were scanned
     * @param metas the ModuleMetas found during boot, keyed by their name, and then ordered by version
     * @param moduleSettings the settings for the modules
     * @param doNotLoadClassPrefixes prefixes of classes always to be loaded through the system class loader, not this one
     */
    protected ScanningDirectoriesModuleRegistry(
            File []                               dirs,
            HashMap<String,MiniModuleMetaMap>     metas,
            Map<ModuleRequirement,ModuleSettings> moduleSettings,
            String []                             doNotLoadClassPrefixes )
    {
        super( metas, moduleSettings, doNotLoadClassPrefixes );

        theDirectories = dirs;
    }

    /**
     * Determine the directories that were scanned.
     *
     * @return the directories
     */
    public File [] getScannedDirectories()
    {
        return theDirectories;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        buf.append( getClass().getName() );
        buf.append( " (" );
        buf.append( nameSet().size());
        buf.append( " known modules, dirs:" );
        for( File d : theDirectories ) {
            buf.append( ' ' );
            buf.append( d.getPath());
        }
        buf.append( ')' );
        return buf.toString();
    }

    /**
     * The directories that were scanned.
     */
    protected File [] theDirectories;

    /**
     * The directories scanned by default if none are given as parameters.
     */
    public static final File [] DEFAULT_MODULE_DIRECTORIES;
    static {
        DEFAULT_MODULE_DIRECTORIES = new File [] {
            new File( System.getProperty( "user.home" ) + File.separatorChar + ".m2" )
        };
    }

    /**
     * Logger.
     */
    private static final Logger log = Logger.getLogger( ScanningDirectoriesModuleRegistry.class.getName() );
}
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.LogManager;
import org.apache.commons.daemon.Daemon;
import org.apache.commons.daemon.DaemonContext;
import org.apache.commons.daemon.DaemonInitException;
import org.diet4j.cmdline.CmdlineParameter;
import org.diet4j.cmdline.CmdlineParameters;
import org.diet4j.core.Module;
import org.diet4j.core.ModuleActivationException;
import org.diet4j.core.ModuleDeactivationException;
import org.diet4j.core.ModuleMeta;
import org.diet4j.core.ModuleNotFoundException;
import org.diet4j.core.ModuleRegistry;
import org.diet4j.core.ModuleRequirement;
import org.diet4j.core.ModuleResolutionException;
import org.diet4j.core.ModuleRunException;
import org.diet4j.core.ModuleSettings;
import org.diet4j.core.NoRunMethodException;
import org.diet4j.core.ScanningDirectoriesModuleRegistry;
                for( String dir : configProps.getProperty( "diet4j!directories" ).split( "[:; ]+" )) {
                    directories.add( dir );
                }
            }
            if( configProps.containsKey( "diet4j!module" )) {
                if( moduleNames != null && moduleNames.length > 0 ) {
                    fatal( "Specified both as argument and in config file: module" );
                }
                moduleNames = configProps.getProperty( "diet4j!module" ).split( "[,\\s]+" );
                // FIXME: no spaces or commas in file names
            }
            if( configProps.containsKey( "diet4j!runclass" )) {
                if( theRunClassName != null ) {
                    fatal( "Specified both as argument and in config file: runclass" );
                }
                theRunClassName = configProps.getProperty( "diet4j!runclass" );
            }
            if( configProps.containsKey( "diet4j!runmethod" )) {
                if( theRunMethodName != null ) {
                    fatal( "Specified both as argument and in config file: runmethod" );
                }
                theRunMethodName = configProps.getProperty( "diet4j!runmethod" );
            }
            if( configProps.containsKey( "diet4j!runarg" )) {
                if( theRunArguments != null && theRunArguments.length > 0 ) {
                    fatal( "Specified both as argument and in config file: arg" );
                }
                theRunArguments = configProps.getProperty( "diet4j!runarg" ).split( "[,\\s]+" );
                // FIXME: this currently does not allow quotes to be used to keep white space or such
            }
            for( Object key : configProps.keySet() ) {
                String realKey = (String) key;
                int    excl    = realKey.indexOf( '!' );
                if( excl > 0 ) {
                    String n1 = realKey.substring( 0, excl );
                    String n2 = realKey.substring( excl + 1 );

                    if( !"diet4j".equals( n1 )) {
                        try {
                            ModuleRequirement req1 = ModuleRequirement.parse( n1 );
                            Map<String,String> forThisModule = rawModuleSettings.get( req1 );
                            if( forThisModule == null ) {
                                forThisModule = new HashMap<>();
                                rawModuleSettings.put( req1, forThisModule );
                            }
                            forThisModule.put( n2, configProps.getProperty( realKey ));

                        } catch( ParseException ex ) {
                            fatal( "Failed to parse String into ModuleRequirement: " + n1 );
                        }
                    }
                }
            }
        }

        if( moduleNames == null || moduleNames.length == 0 ) {
            fatal( "No root module given" );
            return; // won't happen, but make IDE happy
        }

        theModuleRequirements = new ModuleRequirement[ moduleNames.length ];
        for( int i=0 ; i<moduleNames.length ; ++i ) {
            try {
                theModuleRequirements[i] = ModuleRequirement.parse( moduleNames[i] );
            } catch( ParseException ex ) {
                fatal( ex.getLocalizedMessage() );
            }
        }

        HashSet<File> fileDirs = new HashSet<>();
        if( directories != null ) {
            for( String dir : directories ) {
                try {
                    if( !fileDirs.add( new File( dir ).getCanonicalFile() )) {
                        fatal( "Directory (indirectly?) specified more than once in moduledirectory parameter: " + dir );
                    }
                } catch( IOException ex ) {
                    fatal( "Failed to read directory " + dir, ex );
                }
            }
        }
        theDirectories = new File[ fileDirs.size() ];
        fileDirs.toArray( theDirectories );

        if( theRunArguments == null ) {
            theRunArguments = new String[0];
        }

        Map<ModuleRequirement,ModuleSettings> moduleSettings = new HashMap<>();
        for( Map.Entry<ModuleRequirement,Map<String,String>> e : rawModuleSettings.entrySet() ) {
            if( !e.getValue().isEmpty() ) {
                moduleSettings.put( e.getKey(), ModuleSettings.create( e.getValue() ));
            }
        }

        // create ModuleRegistry
        theModuleRegistry = ScanningDirectoriesModuleRegistry.create( theDirectories, moduleSettings );

        // find and resolve modules
        theModuleMetas = new ModuleMeta[ theModuleRequirements.length ];
        theModules     = new Module[     theModuleRequirements.length ]; // while we are at it

        for( int i=0 ; i<theModuleMetas.length ; ++i ) {
            try {
                theModuleMetas[i] = theModuleRegistry.determineSingleResolutionCandidate( theModuleRequirements[i] );

            } catch( Throwable ex ) {
                fatal( "Cannot find module " + theModuleRequirements[i] + " in registry " + theModuleRegistry, ex );
            }
        }
    }

    @Override
    public void start()
        throws
            ModuleResolutionException,
            ModuleNotFoundException,
            ModuleActivationException,
            ClassNotFoundException,
            ModuleRunException,
            NoRunMethodException,