package org.seasar.struts.util;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.util.jar.JarFile;

import org.apache.log4j.Logger;
import org.seasar.framework.util.ClassUtil;

/**
 * @author Satoshi Kimura
 */
public class ClassFinderImpl implements ClassFinder {
    private static final Logger logger = Logger.getLogger(ClassFinderImpl.class);

    private static final String CLASS_FILE_EXTENTION = ".class";

    private static final int CLASS_FILE_EXTENTION_LENGTH = CLASS_FILE_EXTENTION.length();

    private static final char FILE_SEPARATOR = File.separatorChar;

    private Collection classCollection = new ArrayList();

    public ClassFinderImpl() {
    }

    public void find() {
        find(true);
    }

    public synchronized void find(boolean enableJar) {
        String cp = System.getProperty("java.class.path");
        String ps = System.getProperty("path.separator");

        for (StringTokenizer tokenizer = new StringTokenizer(cp, ps); tokenizer.hasMoreTokens();) {
            String path = tokenizer.nextToken();
            loadAllClass(path, enableJar);
        }
    }

    public synchronized void find(String path, boolean enableJar) {
        loadAllClass(path, enableJar);
    }

    public synchronized void find(File file, boolean enableJar) {
        loadAllClass(file.getAbsolutePath(), enableJar);
    }

    private void loadAllClass(String classpath, boolean enableJar) {
        File path = new File(classpath);
        if (!path.exists()) {
            return;
        }
        
        if (path.isDirectory()) {
            loadFromDir(path, path);
        } else if (enableJar) {
            loadFromJar(path);
        }
    }

    private void loadFromJar(File path) {
        JarFile jarFile = createJarFileInstance(path);
        if(jarFile == null) {
            return;
        }
        
        for (Enumeration entries = jarFile.entries(); entries.hasMoreElements();) {
            String entryName = entries.nextElement().toString();
            if (entryName.endsWith(CLASS_FILE_EXTENTION)) {
                String classResourceName = entryName;
                Class clazz = forResourceName(classResourceName);
                addToCollection(clazz);
            }
        }
    }

    private static JarFile createJarFileInstance(File path) {
        try {
            return new JarFile(path);
        } catch (IOException e) {
            logger.warn(e.toString());
            return null;
        }
    }

    private void loadFromDir(File rootPath, File path) {
        File[] files = path.listFiles();
        int rootPathDirNameLength = rootPath.getAbsolutePath().length() + 1;
        for (int i = 0; i < files.length; i++) {
            if (files[i].isDirectory()) {
                loadFromDir(rootPath, files[i]);
            } else {
                if (files[i].getName().endsWith(CLASS_FILE_EXTENTION)) {
                    String classFilePath = files[i].getAbsolutePath();
                    String classResourceName = classFilePath.substring(rootPathDirNameLength);
                    Class clazz = forResourceName(classResourceName);
                    addToCollection(clazz);
                }
            }
        }
    }

    private void addToCollection(Class clazz) {
        if (clazz == null) {
            return;
        }
        classCollection.add(clazz);
    }

    private static final Class forResourceName(String classResourceName) {
        String className = classResourceName.substring(0, classResourceName.length() - CLASS_FILE_EXTENTION_LENGTH);
        className = className.replace(FILE_SEPARATOR, '.');
        className = className.replace('/', '.');
        try {
            Class clazz = ClassUtil.forName(className);
            return clazz;
        } catch (NoClassDefFoundError e) {
            logger.warn(e.toString());
            return null;
        } catch (UnsatisfiedLinkError e) {
            logger.warn(e.toString());
            return null;
        }
    }

    public synchronized Collection getClassCollection() {
        return classCollection;
    }

    public synchronized void destroy() {
        classCollection = null;
    }
}
