package org.wikiwebserver.core;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;


public class WikiClassLoader extends URLClassLoader { 
    
	private static final int SLOW_CLASS_LOAD_WARNING_THRESHOLD = 
		ConfigManager.getInt("slow-class-load-warning-threshold");
	
    // Some classes should shared between all class loaders and should not be reloaded   
    private static final String[] EXCLUDED_PACKAGES = { 
        "java.", 
        "javax.", 
        "com.sun.", 
        "sun.", 
        "org.w3c.", 
        "org.xml.",
        "org.apache.",
        "org.wikiwebserver.core.",
        "org.wikiwebserver.distribute.", // Ensure static data persists
    };

    public WikiClassLoader(URL[] urls, ClassLoader parent) { 
        super(urls, parent);
    }

    public synchronized Class<?> loadClass(String className) throws ClassNotFoundException { 
 
        try {
            for (int i=0; i<EXCLUDED_PACKAGES.length; i++) {
                if (className.startsWith(EXCLUDED_PACKAGES[i])) {
                    Class<?> sysClass = ClassLoader.getSystemClassLoader().loadClass(className);
                    if (sysClass != null) {
                        return sysClass;
                    }
                }
            }
        } catch (ClassNotFoundException ex) {
            // System class loader failed to find class
        } catch (NoClassDefFoundError ex) {
            // System class loader failed to find class
        }
                
        // If this class loader has already loaded this class, reuse it
        Class<?> cachedClass = findLoadedClass(className);
        if (cachedClass != null) {
            return cachedClass;
        }
        
        // Try and load the class using this class loader.
        try {
            long time = System.currentTimeMillis();
            Class<?> newClass = findClass(className);
            long delay = System.currentTimeMillis() - time;
            if (delay >= SLOW_CLASS_LOAD_WARNING_THRESHOLD) {
                WareHouse.logInfo("Warning: " + className + " took " + delay + "ms to load.");
                // Cache the slow loading class file locally
                if (newClass != null) {
                    String fileString = className.replace('.', '/');
                    getResourceFile(fileString + ".class");
                }
            }
            if (newClass != null) {
                return newClass;
            }
        } catch (ClassNotFoundException ex) {
            // Stop BeanInfo class requests from filling logs
            if (!ex.getMessage().endsWith("BeanInfo")) {
                ex.printStackTrace();
            }
        } catch (NoClassDefFoundError ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        // If we get here, try the parent class loader
        if (getParent() != null) {
            return getParent().loadClass(className);
        }
        
        // Otherwise use the system class loader
        return ClassLoader.getSystemClassLoader().loadClass(className);
    }
    
    public File getResourceFile(String path) throws IOException {
        return getResourceFile(new File(path));
    }    
    
    public File getResourceFile(File file) throws IOException {
        
        if (file == null || file.exists()) return file;
        
        String fileString = file.toString().replace('\\', '/');

        URL url = super.getResource(fileString);
        
        if (url == null) return null;
        
        File parent = file.getParentFile();
        if (parent != null) parent.mkdirs();
        
        File tmpFile = new File(file.getPath() + ".tmp");
        FileOutputStream fout = null;
        long modified = System.currentTimeMillis();
        try {
            URLConnection conn = url.openConnection();
            modified = conn.getLastModified();
            InputStream rin = conn.getInputStream();
            fout = new FileOutputStream(tmpFile);
            WareHouse.proxyStream(rin, fout);         
        } catch (IOException ex) {
            IOException ioe = new IOException("Failed to transfer file from remote server");
            ioe.initCause(ex);
            throw ioe;
        } finally {
            if (fout != null) fout.close();
        }
        
        if (tmpFile.length() > 0) {      
            file.delete();
            tmpFile.renameTo(file);
            WareHouse.logInfo(file + " was cached locally.");
            file.setLastModified(modified);                  
        } else {
            tmpFile.delete();
        }
        
        return file;
    }    
}

