/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and/or its affiliates, and individual
 * contributors by the @authors tag. See the copyright.txt in the
 * distribution for a full listing of individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.solder.resourceLoader;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import org.jboss.solder.resourceLoader.ResourceLoader;
import org.jboss.solder.resourceLoader.ResourceProvider;
import org.jboss.solder.util.Sortable;
import org.jboss.solder.util.service.ServiceLoader;

/**
 * <p>
 * {@link ResourceLoaderManager} discovers and instantiates all
 * {@link ResourceLoader}s defined. It also provides accesss to these resources,
 * either as {@link URL}s or {@link InputStream}s.
 * </p>
 * <p/>
 * <p>
 * If you are working in a CDI managed environment, you should use
 * {@link ResourceProvider} instead, as it provides automatic, contextual
 * management of resources. If you are outside a CDI managed environment, then
 * instantiating {@link ResourceLoaderManager} provides access to the same
 * resources.
 * </p>
 *
 * @author Pete Muir
 * @author Stuart Douglas
 * @see ResourceLoader
 * @see ResourceProvider
 */
public class ResourceLoaderManager {

    private final List<ResourceLoader> resourceLoaders;

    /**
     * Instantiate a new instance, loading any resource loaders from the service
     * loader, and sorting them by precedence.
     */
    public ResourceLoaderManager() {
        resourceLoaders = new ArrayList<ResourceLoader>();
        for (ResourceLoader resourceLoader : ServiceLoader.load(ResourceLoader.class)) {
            resourceLoaders.add(resourceLoader);
        }
        Collections.sort(resourceLoaders, new Sortable.Comparator());
    }

    /**
     * The discovered {@link ResourceLoader} instances.
     *
     * @return the resource loaders
     */
    public Iterable<ResourceLoader> getResourceLoaders() {
        return Collections.unmodifiableList(resourceLoaders);
    }

    /**
     * <p>
     * Load a resource by name.
     * </p>
     * <p/>
     * <p>
     * The resource loaders will be searched in precedence order, the first
     * result found being returned.
     * </p>
     *
     * @param name the resource to load
     * @return a URL pointing to the resource, or <code>null</code> if no
     *         resource can be loaded
     * @throws RuntimeException if an error occurs loading the resource
     */
    public URL getResource(String name) {
        for (ResourceLoader loader : resourceLoaders) {
            URL url = loader.getResource(name);
            if (url != null) {
                return url;
            }
        }
        return null;
    }

    /**
     * <p>
     * Load a properties bundle by name.
     * </p>
     * <p/>
     * <p>
     * The resource loaders will be searched in precedence order, the first
     * result found being returned.
     * </p>
     *
     * @param name the name of the properties bundle to load
     * @return a set of properties, or an empty set if no properties bundle can
     *         be loaded
     * @throws RuntimeException if an error occurs loading the properties bundle
     */
    public Properties getPropertiesBundle(String name) {
        return loadProperties(getResourceAsStream(name), name);
    }

    private Properties loadProperties(InputStream is, String name) {
        Properties properties = new Properties();
        if (is != null) {
            try {
                properties.load(is);
            } catch (IOException e) {
                throw new RuntimeException("Error opening stream " + name, e);
            } finally {
                try {
                    is.close();
                } catch (IOException e) {
                    throw new RuntimeException("Error closing stream " + name, e);
                }
            }
        }
        return properties;
    }

    /**
     * <p>
     * Load all properties bundles known to the resource loader by name.
     * </p>
     *
     * @param name the name of the properties bundle to load
     * @return a collection of properties bundles pointing to the resources, or an empty
     *         collection if no resources are found
     * @throws RuntimeException if an error occurs loading the properties bundles
     */
    public Collection<Properties> getPropertiesBundles(String name) {
        Collection<Properties> properties = new HashSet<Properties>();
        for (InputStream is : getResourcesAsStream(name)) {
            properties.add(loadProperties(is, name));
        }
        return properties;
    }

    /**
     * <p>
     * Load a resource by name.
     * </p>
     * <p/>
     * <p>
     * The resource loaders will be searched in precedence order, the first
     * result found being returned.
     * </p>
     *
     * @param name the resource to load
     * @return an InputStream providing access to the resource, or
     *         <code>null</code> if no resource can be loaded
     * @throws RuntimeException if an error occurs loading the resource
     */
    public InputStream getResourceAsStream(String name) {
        for (ResourceLoader loader : resourceLoaders) {
            InputStream is = loader.getResourceAsStream(name);
            if (is != null) {
                return is;
            }
        }
        return null;
    }

    /**
     * <p>
     * Load all resources known to the resource loader by name.
     * </p>
     *
     * @param name the resource to load
     * @return a collection of URLs pointing to the resources, or an empty
     *         collection if no resources are found
     * @throws RuntimeException if an error occurs loading the resource
     */
    public Collection<URL> getResources(String name) {
        Set<URL> urls = new HashSet<URL>();
        for (ResourceLoader loader : resourceLoaders) {
            urls.addAll(loader.getResources(name));
        }
        return urls;
    }

    /**
     * <p>
     * Load all resources known to the resource loader by name.
     * </p>
     *
     * @param name the resource to load
     * @return a collection of input streams pointing to the resources, or an
     *         empty collection if no resources are found
     * @throws RuntimeException if an error occurs loading the resource
     */
    public Collection<InputStream> getResourcesAsStream(String name) {
        Set<InputStream> streams = new HashSet<InputStream>();
        for (ResourceLoader loader : resourceLoaders) {
            streams.addAll(loader.getResourcesAsStream(name));
        }
        return streams;
    }

}
