/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.commons.jci;

import java.io.File;

import org.apache.commons.io.FileUtils;
import org.apache.commons.jci.classes.ExtendedDump;
import org.apache.commons.jci.classes.SimpleDump;
import org.apache.commons.jci.compilers.CompilationResult;
import org.apache.commons.jci.compilers.JavaCompiler;
import org.apache.commons.jci.compilers.JavaCompilerSettings;
import org.apache.commons.jci.listeners.CompilingListener;
import org.apache.commons.jci.monitor.FilesystemAlterationMonitor;
import org.apache.commons.jci.problems.CompilationProblem;
import org.apache.commons.jci.problems.CompilationProblemHandler;
import org.apache.commons.jci.readers.ResourceReader;
import org.apache.commons.jci.stores.ResourceStore;
import org.apache.commons.jci.utils.ConversionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 
 * @author tcurdt
 */
public final class CompilingClassLoaderTestCase extends AbstractTestCase {

    private final Log log = LogFactory.getLog(CompilingClassLoaderTestCase.class);

    private ReloadingClassLoader classloader;
    private CompilingListener listener;
    private FilesystemAlterationMonitor fam;
        
    private final static class MockJavaCompiler implements JavaCompiler {

        private final Log log = LogFactory.getLog(MockJavaCompiler.class);

        public CompilationResult compile(String[] pResourcePaths, ResourceReader pReader, ResourceStore pStore, ClassLoader pClassLoader, JavaCompilerSettings pSettings ) {

            for (int i = 0; i < pResourcePaths.length; i++) {
                final String resourcePath = pResourcePaths[i];
                final byte[] resourceContent = pReader.getBytes(resourcePath);

                log.debug("resource " + resourcePath + " = " + ((resourceContent!=null)?new String(resourceContent):null) );

                final byte[] data;

                if ("jci/Simple.java".equals(resourcePath)) {

                    try {
                        data = SimpleDump.dump(new String(resourceContent));
                    } catch (Exception e) {
                        throw new RuntimeException("cannot handle resource " + resourcePath, e);
                    }

                } else if ("jci/Extended.java".equals(resourcePath)) {

                    try {
                        data = ExtendedDump.dump();
                    } catch (Exception e) {
                        throw new RuntimeException("cannot handle resource " + resourcePath, e);
                    }

                } else {
                    throw new RuntimeException("cannot handle resource " + resourcePath);
                }

                log.debug("compiling " + resourcePath + " (" + data.length + ")");

                pStore.write(ConversionUtils.stripExtension(resourcePath) + ".class", data);

            }

            return new CompilationResult(new CompilationProblem[0]);
        }

        public CompilationResult compile(String[] pResourcePaths, ResourceReader pReader, ResourceStore pStore, ClassLoader pClassLoader) {
            return compile(pResourcePaths, pReader, pStore, pClassLoader, null);
        }

        public CompilationResult compile(String[] pResourcePaths, ResourceReader pReader, ResourceStore pStore) {
            return compile(pResourcePaths, pReader, pStore, null);
        }

        public void setCompilationProblemHandler(CompilationProblemHandler pHandler) {
        }

        public JavaCompilerSettings createDefaultSettings() {
            return null;
        }

    }
    
    @Override
    protected void setUp() throws Exception {
        super.setUp();
        
        classloader = new ReloadingClassLoader(this.getClass().getClassLoader());
        listener = new CompilingListener(new MockJavaCompiler());   

        listener.addReloadNotificationListener(classloader);
        
        fam = new FilesystemAlterationMonitor();
        fam.addListener(directory, listener);
        fam.start();
    }

    private void initialCompile() throws Exception {
        log.debug("initial compile");        

        listener.waitForFirstCheck();
                
        writeFile("jci/Simple.java", "Simple1");        
        writeFile("jci/Extended.java", "Extended");        
        
        log.debug("waiting for compile changes to get applied");        
        listener.waitForCheck();
        
        log.debug("*** ready to test");        
    }
    
    public void testCreate() throws Exception {
        initialCompile();
        
        log.debug("loading Simple");        
        final Object simple = classloader.loadClass("jci.Simple").newInstance();        
        assertEquals("Simple1", simple.toString());
        
        log.debug("loading Extended");        
        final Object extended = classloader.loadClass("jci.Extended").newInstance();        
        assertEquals("Extended:Simple1", extended.toString());
    }

    public void testChange() throws Exception {        
        initialCompile();

        final Object simple = classloader.loadClass("jci.Simple").newInstance();        
        assertEquals("Simple1", simple.toString());
        
        final Object extended = classloader.loadClass("jci.Extended").newInstance();        
        assertEquals("Extended:Simple1", extended.toString());

        delay();
        writeFile("jci/Simple.java", "Simple2");
        listener.waitForCheck();
    
        final Object simple2 = classloader.loadClass("jci.Simple").newInstance();        
        assertEquals("Simple2", simple2.toString());
        
        final Object newExtended = classloader.loadClass("jci.Extended").newInstance();        
        assertEquals("Extended:Simple2", newExtended.toString());
    }

    public void testDelete() throws Exception {
        initialCompile();

        final Object simple = classloader.loadClass("jci.Simple").newInstance();        
        assertEquals("Simple1", simple.toString());
        
        final Object extended = classloader.loadClass("jci.Extended").newInstance();        
        assertEquals("Extended:Simple1", extended.toString());
                
        listener.waitForCheck();
        
        log.debug("deleting source file");
        assertTrue(new File(directory, "jci/Extended.java").delete());
        
        listener.waitForCheck();
       
        log.debug("loading Simple");
        final Object oldSimple = classloader.loadClass("jci.Simple").newInstance();        
        assertEquals("Simple1", oldSimple.toString());

        log.debug("trying to loading Extended");
        try {
            classloader.loadClass("jci.Extended").newInstance();
            fail();
        } catch(final ClassNotFoundException e) {
            assertEquals("jci.Extended", e.getMessage());
        }        
        
        log.debug("deleting whole directory");
        FileUtils.deleteDirectory(new File(directory, "jci"));

        listener.waitForCheck();

        log.debug("trying to loading Simple");
        try {
            classloader.loadClass("jci.Simple").newInstance();
            fail();
        } catch(final ClassNotFoundException e) {
            assertEquals("jci.Simple", e.getMessage());
        }

    }

    public void testDeleteDependency() throws Exception {        
        initialCompile();

        final Object simple = classloader.loadClass("jci.Simple").newInstance();        
        assertEquals("Simple1", simple.toString());
        
        final Object extended = classloader.loadClass("jci.Extended").newInstance();        
        assertEquals("Extended:Simple1", extended.toString());
        
        log.debug("deleting source file");
        assertTrue(new File(directory, "jci/Simple.java").delete());
        listener.waitForCheck();

        log.debug("trying to load dependend class");
        try {
            classloader.loadClass("jci.Extended").newInstance();
            fail();
        } catch(final NoClassDefFoundError e) {
            assertEquals("jci/Simple", e.getMessage());
        }
        
    }

    @Override
    protected void tearDown() throws Exception {
        fam.removeListener(listener);
        fam.stop();
        super.tearDown();
    }    
}
