/*
 * 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.myfaces.extensions.scripting.core.common.util;


import org.apache.myfaces.extensions.scripting.core.api.WeavingContext;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * <p>Utility class for class loading purposes, e.g. to determine the classpath of a
 * class loader hierarchy.</p>
 */
public class ClassLoaderUtils {


    // ------------------------------------------ Public methods

    /**
     * CompilationResult
     * <p>Returns the default class loader to use.</p>
     *
     * @return the default class loader to use
     */
    public static ClassLoader getDefaultClassLoader() {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        if (classLoader != null) {
            return classLoader;
        } else {
            return ClassLoaderUtils.class.getClassLoader();
        }
    }

    /**
     * <p>Determines whether the given class is loadable by the given class loader.</p>
     *
     * @param className   the class you want to check
     * @param classLoader the class loader to use for that check
     * @return <code>true</code>, if the given class is loadable by the given class loader
     */
    public static boolean isClassAvailable(String className, ClassLoader classLoader) {
        try {
            classLoader.loadClass(className);
            return true;
        }
        catch (Throwable ex) {
            return false;
        }
    }

    /**
     * <p>Resolves the classpath by walking up the hierachy of class loaders. Assuming
     * that we're only dealing with URLClassLoaders it's possible to determine the
     * classpath. This method, however, returns the classpath as a String, where each
     * classpath entry is separated by a ';', i.e. it returns the classpath in a format
     * that Java tools usually expect it to be.</p>
     * <p/>
     * it also adds the additional classpaths issued by our configuration to the list
     *
     * @param classLoader the class loader which you want to resolve the class path for
     * @return the final classpath
     */
    public static String buildClasspath(ClassLoader classLoader) {
        StringBuilder classpath = new StringBuilder();

        URL[] urls = resolveClasspath(classLoader);
        for (URL url : urls) {
            try
            {
                classpath.append(URLDecoder.decode(url.getPath(), Charset.defaultCharset().toString()));
                // Note that the classpath separator character is platform
                // dependent. On Windows systems it's ";" whereas on other
                // UNIX systems it's ":".
                classpath.append(File.pathSeparatorChar);
            }
            catch (UnsupportedEncodingException e)
            {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
        }

        String retVal = classpath.toString();
        if (retVal.endsWith(File.pathSeparator)) {
            retVal = retVal.substring(0, retVal.length() - 1);
        }
        return retVal;
    }

    /**
     * <p>Resolves the classpath by walking up the hierarchy of class loaders. Assuming
     * that we're only dealing with URLClassLoaders it's possible to determine the
     * classpath.</p>
     *
     * @param parent the class loader which you want to resolve the class path for
     * @return the final classpath
     */
    public static URL[] resolveClasspath(ClassLoader parent) {
        List<URL> classpath = new ArrayList<URL>();

        ClassLoader classLoader = parent;
        // Walk up the hierachy of class loaders in order to determine the current classpath.
        File target = WeavingContext.getInstance().getConfiguration().getCompileTarget();
        if (target != null) {
            addFile(classpath, target);
        }

        while (classLoader != null) {
            if (classLoader instanceof URLClassLoader) {
                URLClassLoader urlClassLoader = (URLClassLoader) classLoader;

                URL[] urls = urlClassLoader.getURLs();
                if (urls != null) {
                    classpath.addAll(Arrays.asList(urls));
                }
            } /*else {
                if (logger.isWarnEnabled()) {
                    logger.warn("Resolving the classpath of the classloader '" + parent + "' - One of its parent class"
                            + " loaders is no URLClassLoader '" + classLoader + "', which means it's possible that"
                            + " some classpath entries aren't in the final outcome of this method call.");
                }
            } */

            //we disable this warning entirely for now because our own url classloader
            //can deal with this properly, due to extra startup context classpath determination

            // Inspect the parent class loader next.
            classLoader = classLoader.getParent();
        }

        List<String> additionalClassPaths = WeavingContext.getInstance().getConfiguration().getAdditionalClassPath();
        if (!(additionalClassPaths == null || additionalClassPaths.isEmpty())) {
            for (String additionalClassPath : additionalClassPaths) {
                File additionalPath = new File(additionalClassPath);
                addFile(classpath, additionalPath);
            }
        }

        return classpath.toArray(new URL[classpath.size()]);
    }

    private static void addFile(List<URL> classpath, File additionalPath) {
        if (additionalPath.exists()) {
            try {
                classpath.add(additionalPath.toURI().toURL());
            } catch (MalformedURLException e) {
                Logger log = Logger.getLogger(ClassLoaderUtils.class.getName());
                log.log(Level.SEVERE, "Additionalclasspath wrong url", e);
            }
        }
    }

}
