class ProcedureJarLoader {

    public Callables loadProceduresFromDir( File root ) throws IOException, KernelException
    {
        if ( root == null || !root.exists() )
        {
            return Callables.empty();
        }

        Callables out = new Callables();

        File[] dirListing = root.listFiles( ( dir, name ) -> name.endsWith( ".jar" ) );

        if ( dirListing == null )
        {
            return Callables.empty();
        }

        if ( !allJarFilesAreValidZipFiles( Stream.of( dirListing ) ) )
        {
            throw new ZipException( "Some jar procedure files are invalid, see log for details." );
        }

        URL[] jarFilesURLs = Stream.of( dirListing ).map( this::toURL ).toArray( URL[]::new );

        URLClassLoader loader = new URLClassLoader( jarFilesURLs, this.getClass().getClassLoader() );

        for ( URL jarFile : jarFilesURLs )
        {
            loadProcedures( jarFile, loader, out );
        }
        return out;
    }

      ProcedureJarLoader( ReflectiveProcedureCompiler compiler, Log log );

}