/*
 * ClasspathUtils.java
 * 
 * Copyright 2001-2004 The Apache Software Foundation.
 * 
 * 
 * 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.apache.axis.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileFilter;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;

import org.apache.axis.AxisProperties;
import org.apache.axis.MessageContext;
import org.apache.axis.transport.http.HTTPConstants;

/**
 * Utility class for constructing the classpath
 */ 
public class ClasspathUtils {

    /**
     * Expand a directory path or list of directory paths (File.pathSeparator
     * delimited) into a list of file paths of all the jar files in those
     * directories.
     *
     * @param dirPaths The string containing the directory path or list of
     *                 directory paths.
     * @return The file paths of the jar files in the directories. This is an
     *         empty string if no files were found, and is terminated by an
     *         additional pathSeparator in all other cases.
     */
    public static String expandDirs(String dirPaths) {
        StringTokenizer st = new StringTokenizer(dirPaths, File.pathSeparator);
        StringBuffer buffer = new StringBuffer();
        while (st.hasMoreTokens()) {
            String d = st.nextToken();
            File dir = new File(d);
            if (dir.isDirectory()) {
                File[] files = dir.listFiles(new JavaArchiveFilter());
                for (int i = 0; i < files.length; i++) {
                    buffer.append(files[i]).append(File.pathSeparator);
                }
            }
        }
        return buffer.toString();
    }

    /**
     * Check if this inputstream is a jar/zip
     * @param is
     * @return true if inputstream is a jar
     */ 
    public static boolean isJar(InputStream is) {
        try {
            JarInputStream jis = new JarInputStream(is);
            if (jis.getNextEntry() != null) {
                return true;
            }
        } catch (IOException ioe) {
        }
        return false;
    }

    /**
     * Get the default classpath from various thingies in the message context
     * @param msgContext
     * @return default classpath
     */ 
    public static String getDefaultClasspath(MessageContext msgContext) {
        StringBuffer classpath = new StringBuffer();
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        fillClassPath(cl, classpath);

        // Just to be safe (the above doesn't seem to return the webapp
        // classpath in all cases), manually do this:

        String webBase = (String) msgContext.getProperty(HTTPConstants.MC_HTTP_SERVLETLOCATION);
        if (webBase != null) {
            classpath.append(webBase + File.separatorChar + "classes" +
                    File.pathSeparatorChar);
            try {
                String libBase = webBase + File.separatorChar + "lib";
                File libDir = new File(libBase);
                String[] jarFiles = libDir.list();
                for (int i = 0; i < jarFiles.length; i++) {
                    String jarFile = jarFiles[i];
                    if (jarFile.endsWith(".jar")) {
                        classpath.append(libBase +
                                File.separatorChar +
                                jarFile +
                                File.pathSeparatorChar);
                    }
                }
            } catch (Exception e) {
                // Oh well.  No big deal.
            }
        }

        // axis.ext.dirs can be used in any appserver
        getClassPathFromDirectoryProperty(classpath, "axis.ext.dirs");

        // classpath used by Jasper 
        getClassPathFromProperty(classpath, "org.apache.catalina.jsp_classpath");
        
        // websphere stuff.
        getClassPathFromProperty(classpath, "ws.ext.dirs");
        getClassPathFromProperty(classpath, "com.ibm.websphere.servlet.application.classpath");
        
        // java class path
        getClassPathFromProperty(classpath, "java.class.path");
        
        // Load jars from java external directory
        getClassPathFromDirectoryProperty(classpath, "java.ext.dirs");
        
        // boot classpath isn't found in above search
        getClassPathFromProperty(classpath, "sun.boot.class.path");
        return classpath.toString();
    }

    /**
     * Add all files in the specified directory to the classpath
     * @param classpath
     * @param property
     */ 
    private static void getClassPathFromDirectoryProperty(StringBuffer classpath, String property) {
        String dirs = AxisProperties.getProperty(property);
        String path = null;
        try {
            path = ClasspathUtils.expandDirs(dirs);
        } catch (Exception e) {
            // Oh well.  No big deal.
        }
        if (path != null) {
            classpath.append(path);
            classpath.append(File.pathSeparatorChar);
        }
    }

    /**
     * Add a classpath stored in a property.
     * @param classpath
     * @param property
     */ 
    private static void getClassPathFromProperty(StringBuffer classpath, String property) {
        String path = AxisProperties.getProperty(property);
        if (path != null) {
            classpath.append(path);
            classpath.append(File.pathSeparatorChar);
        }
    }

    /**
     * Walk the classloader hierarchy and add to the classpath
     * @param cl
     * @param classpath
     */
    private static void fillClassPath(ClassLoader cl, StringBuffer classpath) {
        while (cl != null) {
            if (cl instanceof URLClassLoader) {
                URL[] urls = ((URLClassLoader) cl).getURLs();
                for (int i = 0; (urls != null) && i < urls.length; i++) {
                    String path = urls[i].getPath();
                    //If it is a drive letter, adjust accordingly.
                    if (path.length() >= 3 && path.charAt(0) == '/' && path.charAt(2) == ':')
                        path = path.substring(1);
                    classpath.append(URLDecoder.decode(path));
                    classpath.append(File.pathSeparatorChar);

                    // if its a jar extract Class-Path entries from manifest
                    File file = new File(urls[i].getFile());
                    if (file.isFile()) {
                        FileInputStream fis = null;
                        try {
                            fis = new FileInputStream(file);
                            if (isJar(fis)) {
                                JarFile jar = new JarFile(file);
                                Manifest manifest = jar.getManifest();
                                if (manifest != null) {
                                    Attributes attributes = manifest.getMainAttributes();
                                    if (attributes != null) {
                                        String s = attributes.getValue(Attributes.Name.CLASS_PATH);
                                        String base = file.getParent();
                                        if (s != null) {
                                            StringTokenizer st = new StringTokenizer(s, " ");
                                            while (st.hasMoreTokens()) {
                                                String t = st.nextToken();
                                                classpath.append(base + File.separatorChar + t);
                                                classpath.append(File.pathSeparatorChar);
                                            }
                                        }
                                    }
                                }
                            }
                        } catch (IOException ioe) {
                        } finally {
                            if (fis != null) {
                                try {
                                    fis.close();
                                } catch (IOException ioe2) {
                                }
                            }
                        }
                    }
                }
            }
            cl = cl.getParent();
        }
    }

    /**
     * Filter for zip/jar
     */ 
    private static class JavaArchiveFilter implements FileFilter {
        public boolean accept(File file) {
            String name = file.getName().toLowerCase();
            return (name.endsWith(".jar") || name.endsWith(".zip"));
        }
    }
}
