/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, Red Hat Middleware LLC, 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.shrinkwrap.impl.base.classloader;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.logging.Logger;

import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.GenericArchive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.classloader.ShrinkWrapClassLoader;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.impl.base.io.IOUtil;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

/**
 * Ensures the {@link ShrinkWrapClassLoader} is working as contracted
 *
 * @author <a href="mailto:aslak@redhat.com">Aslak Knutsen</a>
 * @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
 * @version $Revision: $
 */
public class ShrinkWrapClassLoaderTestCase {

    // -------------------------------------------------------------------------------------||
    // Class Members ----------------------------------------------------------------------||
    // -------------------------------------------------------------------------------------||

    /**
     * Logger
     */
    private static final Logger log = Logger.getLogger(ShrinkWrapClassLoaderTestCase.class.getName());

    /**
     * Class to be accessed via a ShrinkWrap ClassLoader
     */
    private static final Class<?> applicationClassLoaderClass = LoadedTestClass.class;

    /**
     * Archive to be read via a {@link ShrinkWrapClassLoaderTestCase#shrinkWrapClassLoader}
     */
    private static final JavaArchive archive = ShrinkWrap.create(JavaArchive.class).addClass(
        applicationClassLoaderClass);

    // -------------------------------------------------------------------------------------||
    // Instance Members -------------------------------------------------------------------||
    // -------------------------------------------------------------------------------------||

    /**
     * ClassLoader used to load {@link ShrinkWrapClassLoaderTestCase#applicationClassLoaderClass}
     */
    private ClassLoader shrinkWrapClassLoader;

    // -------------------------------------------------------------------------------------||
    // Lifecycle --------------------------------------------------------------------------||
    // -------------------------------------------------------------------------------------||

    /**
     * Creates the {@link ShrinkWrapClassLoaderTestCase#shrinkWrapClassLoader} used to load classes from an
     * {@link Archive}. The {@link ClassLoader} will be isolated from the application classpath by specifying a null
     * parent explicitly.
     */
    @Before
    public void createClassLoader() {
        shrinkWrapClassLoader = new ShrinkWrapClassLoader((ClassLoader) null, archive);
    }

    /**
     * Closes resources associated with the {@link ShrinkWrapClassLoaderTestCase#shrinkWrapClassLoader}
     */
    @After
    public void closeClassLoader() {
        if (shrinkWrapClassLoader instanceof Closeable) {
            try {
                ((Closeable) shrinkWrapClassLoader).close();
            } catch (final IOException e) {
                log.warning("Could not close the " + shrinkWrapClassLoader + ": " + e);
            }
        }
    }

    // -------------------------------------------------------------------------------------||
    // Tests ------------------------------------------------------------------------------||
    // -------------------------------------------------------------------------------------||

    /**
     * Ensures we can load a Class instance from the {@link ShrinkWrapClassLoader}
     */
    @Test
    public void shouldBeAbleToLoadClassFromArchive() throws ClassNotFoundException {
        // Load the test class from the CL
        final Class<?> loadedTestClass = Class.forName(applicationClassLoaderClass.getName(), false,
            shrinkWrapClassLoader);

        final ClassLoader loadedTestClassClassLoader = loadedTestClass.getClassLoader();
        log.info("Got " + loadedTestClass + " from " + loadedTestClassClassLoader);

        // Assertions
        Assert.assertNotNull("Test class could not be found via the ClassLoader", loadedTestClass);

        Assert.assertSame("Test class should have been loaded via the archive ClassLoader", shrinkWrapClassLoader,
            loadedTestClassClassLoader);

        Assert.assertNotSame("Class Loaded from the CL should not be the same as the one on the appCL",
            loadedTestClass, applicationClassLoaderClass);

    }

    /**
     * Ensures that we can open up directory content as obtained via a {@link URL} from the
     * {@link ShrinkWrapClassLoader} (ie. should return null, not throw an exception)
     *
     * SHRINKWRAP-306
     */
    @Test
    public void shouldBeAbleToOpenStreamOnDirectoryUrl() throws IOException {
        // Make a new Archive with some content in a directory
        final String nestedResourceName = "nested/test";
        final Asset testAsset = new StringAsset("testContent");
        final GenericArchive archive = ShrinkWrap.create(GenericArchive.class).add(testAsset, nestedResourceName);

        // Make a CL to load the content
        final ClassLoader swCl = new ShrinkWrapClassLoader(archive);

        // Get the URL to the parent directory
        final URL nestedResourceUrl = swCl.getResource(nestedResourceName);
        final URL nestedResourceUpALevelUrl = new URL(nestedResourceUrl, "../");

        // openStream on the URL to the parent directory; should return null, not throw an exception
        final InputStream in = nestedResourceUpALevelUrl.openStream();
        Assert.assertNull("URLs pointing to a directory should openStream as null", in);
    }

    /**
     * Ensures that we can open up an asset that doesn't exist via a {@link URL} from the {@link ShrinkWrapClassLoader}
     * (ie. should throw {@link FileNotFoundException}
     *
     * SHRINKWRAP-308
     */
    @Test(expected = FileNotFoundException.class)
    public void shouldNotBeAbleToOpenStreamOnNonexistantAsset() throws IOException {
        // Make a new Archive with some content in a directory
        final String nestedResourceName = "nested/test";
        final Asset testAsset = new StringAsset("testContent");
        final GenericArchive archive = ShrinkWrap.create(GenericArchive.class).add(testAsset, nestedResourceName);

        // Make a CL to load the content
        final ClassLoader swCl = new ShrinkWrapClassLoader(archive);

        // Get the URL to something that doesn't exist
        final URL nestedResourceUrl = swCl.getResource(nestedResourceName);
        final URL nestedResourceThatDoesntExistUrl = new URL(nestedResourceUrl, "../fake");

        // openStream on the URL that doesn't exist should throw FNFE
        nestedResourceThatDoesntExistUrl.openStream();
    }

    /**
     * Ensures we can load a resource by name from the {@link ShrinkWrapClassLoader}
     */
    @Test
    public void shouldBeAbleToLoadResourceFromArchive() {

        // Load the class as a resource
        final URL resource = shrinkWrapClassLoader.getResource(getResourceNameOfClass(applicationClassLoaderClass));

        // Assertions
        Assert.assertNotNull(resource);

    }

    /**
     * SHRINKWRAP-237: Reading the same resource multiple times cause IOException
     */
    @Test
    public void shouldBeAbleToLoadAResourceFromArchiveMultipleTimes() throws Exception {
        String resourceName = getResourceNameOfClass(applicationClassLoaderClass);

        // Load the class as a resource
        URL resource = shrinkWrapClassLoader.getResource(resourceName);

        // Assertions
        Assert.assertNotNull(resource);

        // Read the stream until EOF
        IOUtil.copyWithClose(resource.openStream(), new ByteArrayOutputStream());

        // Load the class as a resource for the second time
        resource = shrinkWrapClassLoader.getResource(resourceName);

        // Assertions
        Assert.assertNotNull(resource);

        // SHRINKWRAP-237: This throws IOException: Stream closed
        IOUtil.copyWithClose(resource.openStream(), new ByteArrayOutputStream());
    }

    // -------------------------------------------------------------------------------------||
    // Internal Helper Methods ------------------------------------------------------------||
    // -------------------------------------------------------------------------------------||

    /**
     * Obtains the resource name for a given class
     */
    private static String getResourceNameOfClass(final Class<?> clazz) {
        assert clazz != null : "clazz must be specified";
        final StringBuilder sb = new StringBuilder();
        final String className = clazz.getName().replace('.', '/');
        sb.append(className);
        sb.append(".class");
        return sb.toString();
    }
}