/**
 *  Copyright (C) 2004 Orbeon, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version
 *  2.1 of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU Lesser General Public License for more details.
 *
 *  The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.util;

import org.apache.commons.lang3.StringUtils;
import org.orbeon.oxf.common.OXFException;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;

public class SystemUtils {

    public static File getTemporaryDirectory() {
        return new File(System.getProperty("java.io.tmpdir")).getAbsoluteFile();
    }

    /**
     * @param pth java.io.File.pathSeparator delimited path.
     * @return	What you think.
     */
    public static File[] pathToFiles(final String pth) {
        final ArrayList pthArr = new ArrayList();
        final StringTokenizer st = new StringTokenizer
                (pth, File.pathSeparator);
        while (st.hasMoreTokens()) {
            final String fnam = st.nextToken();
            final File fil = new File(fnam);
            pthArr.add(fil);
        }
        final File[] ret = new File[pthArr.size()];
        pthArr.toArray(ret);
        return ret;
    }

    /**
     * @param	trgt	Receiver of path elements ( as java.io.File ) from pth.
     * @param	pth		java.io.File.pathSeparator delimited path.
     */
    public static void gatherPaths(final Collection trgt, final String pth) {
        final File[] files = pathToFiles(pth);
        for (int i = 0; i < files.length; i++) {
            trgt.add(files[i]);
        }
    }

    /**
     * @param c Reciever of java.io.File's gathered from sun.boot.class.path elements
     *          as well as the contents of the dirs named in java.ext.dirs.
     */
    public static void gatherSystemPaths(final Collection c) {
        final String bootcp = System.getProperty("sun.boot.class.path");
        gatherPaths(c, bootcp);
        final String extDirProp = System.getProperty("java.ext.dirs");
        final File[] extDirs = pathToFiles(extDirProp);
        for (int i = 0; i < extDirs.length; i++) {
            final File[] jars = extDirs[i].listFiles();
            if (jars != null) { // jars can be null when java.ext.dirs points to a non-existant directory
                for (int j = 0; j < jars.length; j++) {
                    final File fil = jars[j];
                    final String fnam = fil.toString();
                    final int fnamLen = fnam.length();
                    if (fnamLen < 5) continue;
                    if (fnam.regionMatches(true, fnamLen - 4, ".jar", 0, 4)) {
                        c.add(fil);
                    } else if (fnam.regionMatches(true, fnamLen - 4, ".zip", 0, 4)) {
                        c.add(fil);
                    }
                }
            }
        }
    }

    /**
     * Walks class loader hierarchy of clazz looking for instances of URLClassLoader.
     * For each found gets file urls and adds, converted, elements to the path.
     * Note that the more junior a class loader is the later in the path it's
     * contribution is. <br/>
     * Also, tries to deal with fact that urls may or may not be encoded.
     * Converts the urls to java.io.File and checks for existence.  If it
     * exists file name is used as is.  If not name is URLDecoded.  If the
     * decoded form exists that that is used.  If neither exists then the
     * undecoded for is used.
     *
     * @param clazz Class to try and build a classpath from.
     * @return	java.io.File.pathSeparator delimited path.
     */
    public static String pathFromLoaders(final Class clazz)
            throws java.io.UnsupportedEncodingException {
        final TreeSet sysPths = new TreeSet();
        gatherSystemPaths(sysPths);
        final StringBuilder sbuf = new StringBuilder();
        final LinkedList urlLst = new LinkedList();
        for (ClassLoader cl = clazz.getClassLoader(); cl != null; cl = cl.getParent()) {
            if (!(cl instanceof java.net.URLClassLoader)) {
                continue;
            }
            final java.net.URLClassLoader ucl = (java.net.URLClassLoader) cl;
            final java.net.URL[] urls = ucl.getURLs();
            if (urls == null) continue;
            for (int i = urls.length - 1; i > -1; i--) {
                final java.net.URL url = urls[i];
                final String prot = url.getProtocol();
                if (!"file".equalsIgnoreCase(prot)) continue;
                urlLst.addFirst(url);
            }
        }
        for (final Iterator itr = urlLst.iterator();
             itr.hasNext();) {
            final java.net.URL url = (java.net.URL) itr.next();
            final String fnam = url.getFile();
            final File fil = new File(fnam);
            if (sysPths.contains(fil)) continue;
            // 11/14/2004 d : Odd test attempts to deal with fact that
            // a.) The URLs passed to a URLClassLoader don't have to be encoded
            //     in general.
            // b.) The app class loader and the extensions class loader, the
            //     jdk class loaders responsible for loading classes from classpath
            //     and the extensions dir respectively, do encode their urls.
            // c.) java.io.File.toURL doesn't produce encoded urls. ( Perhaps
            //     a sun bug... )
            // As you can see given (a) and (c) odds are pretty good that
            // should anyone use something other than jdk class loaders we
            // will end up unencoded urls.
            if (!fil.exists()) {
                final String unEncNam = URLDecoder.decode(fnam, "utf-8");
                final File unEncFil = new File(unEncNam);
                if (unEncFil.exists()) sbuf.append(unEncNam);
            } else {
                sbuf.append(fnam);
            }
            sbuf.append(File.pathSeparatorChar);
        }
        final String ret = sbuf.toString();
        return ret;
    }

    public static String getJarPath(Class clazz) {
        String resourceName = StringUtils.replace(clazz.getName(), ".", "/") + ".class";
        try {
            URL url = clazz.getClassLoader().getResource(resourceName);
            if (url == null)
                throw new IllegalArgumentException("Invalid resource name: " + resourceName);
            if (url.getProtocol().equals("jar")) {
                if (url.getProtocol().equals("jar")) {
                    // The current class is in a JAR file
                    String fileName = url.getFile();

                    int end = fileName.length() - ("!/".length() + resourceName.length());
                    final String fileSlash = "file:/";
                    final int fileSlashLen = fileSlash.length();
                    if (end > fileSlashLen && fileName.regionMatches(true, 0, fileSlash, 0, fileSlashLen)) {
                        fileName = fileName.substring(fileSlashLen, end);

                        File file = new File(fileName);
                        if (!file.exists()) {
                            // Try to decode only if we cannot find the file (see explanation in other method above)
                            fileName = URLDecoder.decode(fileName, "utf-8");
                        }

                        File jarDirectory = new File(fileName).getParentFile();
                        if (jarDirectory.isDirectory())
                            return jarDirectory.getCanonicalPath();
                    }
                }
            }
            return null;
        } catch (IOException e) {
            throw new OXFException(e);
        }
    }

    private SystemUtils() {
        // disallow instantiation
    }
}
