ConfigurationException If a requested configuration file
     *                                       does not have an associated
     *                                       configuration handler.
     *
     * @author ketter (ketter@laiketui.com)
     * @since  3.0.0
     */
    private static function callHandler ($handler, $config, $cache)
    {
        if (count(self::$handlers) == 0)
        {
            // we need to load the handlers first
            self::loadConfigHandlers();
        }
        // grab the base name of the handler
        $basename = basename($handler);
        if (isset(self::$handlers[$handler]))
        {
            // we have a handler associated with the full configuration path
            // call the handler and retrieve the cache data
            $data =& self::$handlers[$handler]->execute($config);
            self::writeCacheFile($config, $cache, $data, false);
            return;
        } else if (isset(self::$handlers[$basename]))
        {
            // we have a handler associated with the configuration base name
            // call the handler and retrieve the cache data
            $data =& self::$handlers[$basename]->execute($config);
            self::writeCacheFile($config, $cache, $data, false);
            return;
        } else
        {
            // let's see if we have any wildcard handlers registered that match
            // this basename
            foreach (self::$handlers as $key => $handlerInstance)
            {
                // replace wildcard chars in the configuration
                $pattern = str_replace('.', '\.', $key);
                $pattern = str_replace('*', '.*?', $pattern);
                // create pattern from config
                $pattern = '#' . $pattern . '#';
                if (preg_match($pattern, $handler))
                {
                    // we found a match!
                    // call the handler and retrieve the cache data
                    $data =& self::$handlers[$key]->execute($config);
                    self::writeCacheFile($config, $cache, $data, false);
                    return;
                }
            }
        }
        // we do not have a registered handler for this file
        $error = 'Configuration file "%s" does not have a ' .
                 'registered handler';
        $error = sprintf($error, $config);
        throw new ConfigurationException($error);
    }
    // -------------------------------------------------------------------------
    /**
     * Check to see if a configuration file has been modified and if so
     * recompile the cache file associated with it.
     *
     * If the configuration file path is relative, the path itself is relative
     * to the Mojavi MO_WEBAPP_DIR application setting.
     *
     * @param string A filesystem path to a configuration file.
     *
     * @return string An absolute filesystem path to the cache filename
     *                associated with this specified configuration file.
     *
     * @throws ConfigurationException If a requested configuration file
     *                                       does not exist.
     *
     * @author ketter (ketter@laiketui.com)
     * @since  3.0.0
     */
    public static function checkConfig ($config)
    {
        // the full filename path to the config
        $filename = $config;
        if (!Toolkit::isPathAbsolute($filename))
        {
            $filename = MO_WEBAPP_DIR . '/' . $filename;
        }
        if (!is_readable($filename))
        {
            // configuration file does not exist
            $error = 'Configuration file "%s" does not exist or is unreadable';
            $error = sprintf($error, $filename);
            throw new ConfigurationException($error);
        }
        // the cache filename we'll be using
        $cache = self::getCacheName($config);
        if (!is_readable($cache) || filemtime($filename) > filemtime($cache))
        {
            // configuration file has changed so we need to reparse it
            self::callHandler($config, $filename, $cache);
        }
        return $cache;
    }
    // -------------------------------------------------------------------------
    /**
     * Clear all configuration cache files.
     *
     * @return void
     *
     * @author ketter (ketter@laiketui.com)
     * @since  3.0.0
     */
    public static function clear ()
    {
        self::clearCache(MO_CACHE_DIR);
    }
    // -------------------------------------------------------------------------
    /**
     * Clear all configuration cache files.
     *
     * This method exists to prevent accidental deletion of non-cache directory
     * files.
     *
     * @param string An absolute filesystem path to a cache directory.
     *
     * @return void
     *
     * @author ketter (ketter@laiketui.com)
     * @since  3.0.0
     */
    private static function clearCache ($directory)
    {
        // open a file point to the cache dir
        $fp = opendir($directory);
        // ignore names
        $ignore = array('.', '..', 'CVS', '.svn');
        while (($file = readdir($fp)) !== false)
        {
            if (!in_array($file, $ignore))
            {
                if (is_dir($file))
                {
                    // recurse through directory
                    self::clearCache($file);
                    // delete the directory
                    rmdir($file);
                } else
                {
                    // delete the file
                    unlink($directory . '/' . $file);
                }
            }
        }
        // close file pointer
        closedir($fp);
    }
    // -------------------------------------------------------------------------
    /**
     * Convert a normal filename into a cache filename.
     *
     * @param string A normal filename.
     *
     * @return string An absolute filesystem path to a cache filename.
     *
     * @author ketter (ketter@laiketui.com)
     * @since  3.0.0
     */
    public static function getCacheName ($config)
    {
        if (strlen($config) > 3 && ctype_alpha($config{0}) &&
            $config{1} == ':' && $config{2} == '\\')
        {
            // file is a windows absolute path, strip off the drive letter
            $config = substr($config, 3);
        }
        // replace unfriendly filename characters with an underscore
        $config  = str_replace(array('\\', '/'), '_', $config);
        $config .= '.php';
        return MO_CACHE_DIR . '/' . $config;
    }
    // -------------------------------------------------------------------------
    /**
     * Import a configuration file.
     *
     * If the configuration file path is relative, the path itself is relative
     * to the Mojavi MO_WEBAPP_DIR application setting.
     *
     * @param string A filesystem path to a configuration file.
     * @param bool   Only allow this configuration file to be included once per
     *               request?
     *
     * @return void
     *
     * @author ketter (ketter@laiketui.com)
     * @since  3.0.0
     */
    public static function import ($config, $once = true)
    {
        // check the config file
        $cache = self::checkConfig($config);
        // include cache file
        if ($once)
        {
            include_once($cache);
        } else
        {
            include($cache);
        }
    }
    // -------------------------------------------------------------------------
    /**
     * Load all configuration application and module level handlers.
     *
     * @return void
     *
     * @throws ConfigurationException If a configuration related error
     *                                       occurs.
     *
     * @author ketter (ketter@laiketui.com)
     * @since  3.0.0
     */
    private static function loadConfigHandlers ()
    {
        // manually create our config_handlers.ini handler
        self::$handlers['config_handlers.ini'] = new RootConfigHandler();
        self::$handlers['config_handlers.ini']->initialize();
        // application configuration handlers
        require_once(ConfigCache::checkConfig('config/config_handlers.ini'));
        // module level configuration handlers
        // make sure our modules directory exists
        if (is_readable(MO_MODULE_DIR))
        {
            // ignore names
            $ignore = array('.', '..', 'CVS', '.svn');
            // create a file pointer to the module dir
            $fp = opendir(MO_MODULE_DIR);
            // loop through the directory and grab the modules
            while (($directory = readdir($fp)) !== false)
            {
                if (!in_array($directory, $ignore))
                {
                    $config = MO_MODULE_DIR . '/' . $directory .
                              '/config/config_handlers.ini';
                    if (is_readable($config))
                    {
                        // initialize the root configuration handler with this
                        // module name
                        $params = array('module_level' => true,
                                        'module_name'  => $directory);
                        self::$handlers['config_handlers.ini']->initialize($params);
                        // replace module dir path with a special keyword that
                        // checkConfig knows how to use
                        $config = 'modules/' . $directory .
                                  '/config/config_handlers.ini';
                        require_once(ConfigCache::checkConfig($config));
                    }
                }
            }
            // close file pointer
            closedir($fp);
        } else
        {
            // module directory doesn't exist or isn't readable
            $error = 'Module directory "%s" does not exist or is not readable';
            $error = sprintf($error, MO_MODULE_DIR);
            throw new ConfigurationException($error);
        }
    }
    // -------------------------------------------------------------------------
    /**
     * Write a cache file.
     *
     * @param string An absolute filesystem path to a configuration file.
     * @param string An absolute filesystem path to the cache file that will
     *               be written.
     * @param string Data to be written to the cache file.
     * @param string Should we append the data?
     *
     * @throws CacheException If the cache file cannot be written.
     *
     * @author ketter (ketter@laiketui.com)
     * @since  3.0.0
     */
    private static function writeCacheFile ($config, $cache, &$data, $append)
    {
        $flags = ($append) ? FILE_APPEND : 0;
        if (@file_put_contents($cache, $data, $flags) === false)
        {
            // cannot write cache file
            $error = 'Failed to write cache file "%s" generated from ' .
                     'configuration file "%s"';
            $error = sprintf($error, $cache, $config);
            throw new CacheException($error);
        }
    }
}
?>