/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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 com.android.internal.os;

import android.os.Trace;

import dalvik.system.DelegateLastClassLoader;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;

import java.util.List;

/**
 * Creates class loaders.
 *
 * @hide
 */
public class ClassLoaderFactory {
    // Unconstructable
    private ClassLoaderFactory() {}

    private static final String PATH_CLASS_LOADER_NAME = PathClassLoader.class.getName();
    private static final String DEX_CLASS_LOADER_NAME = DexClassLoader.class.getName();
    private static final String DELEGATE_LAST_CLASS_LOADER_NAME =
            DelegateLastClassLoader.class.getName();

    /**
     * Returns the name of the class for PathClassLoader.
     */
    public static String getPathClassLoaderName() {
        return PATH_CLASS_LOADER_NAME;
    }

    /**
     * Returns true if {@code name} is a supported classloader. {@code name} must be a
     * binary name of a class, as defined by {@code Class.getName}.
     */
    public static boolean isValidClassLoaderName(String name) {
        // This method is used to parse package data and does not accept null names.
        return name != null && (isPathClassLoaderName(name) || isDelegateLastClassLoaderName(name));
    }

    /**
     * Returns true if {@code name} is the encoding for either PathClassLoader or DexClassLoader.
     * The two class loaders are grouped together because they have the same behaviour.
     */
    public static boolean isPathClassLoaderName(String name) {
        // For null values we default to PathClassLoader. This cover the case when packages
        // don't specify any value for their class loaders.
        return name == null || PATH_CLASS_LOADER_NAME.equals(name) ||
                DEX_CLASS_LOADER_NAME.equals(name);
    }

    /**
     * Returns true if {@code name} is the encoding for the DelegateLastClassLoader.
     */
    public static boolean isDelegateLastClassLoaderName(String name) {
        return DELEGATE_LAST_CLASS_LOADER_NAME.equals(name);
    }

    /**
     * Same as {@code createClassLoader} below, except that no associated namespace
     * is created.
     */
    public static ClassLoader createClassLoader(String dexPath,
            String librarySearchPath, ClassLoader parent, String classloaderName,
            List<ClassLoader> sharedLibraries) {
        ClassLoader[] arrayOfSharedLibraries = (sharedLibraries == null)
                ? null
                : sharedLibraries.toArray(new ClassLoader[sharedLibraries.size()]);
        if (isPathClassLoaderName(classloaderName)) {
            return new PathClassLoader(dexPath, librarySearchPath, parent, arrayOfSharedLibraries);
        } else if (isDelegateLastClassLoaderName(classloaderName)) {
            return new DelegateLastClassLoader(dexPath, librarySearchPath, parent,
                    arrayOfSharedLibraries);
        }

        throw new AssertionError("Invalid classLoaderName: " + classloaderName);
    }

    /**
     * Same as {@code createClassLoader} below, but passes a null list of shared
     * libraries.
     */
    public static ClassLoader createClassLoader(String dexPath,
            String librarySearchPath, String libraryPermittedPath, ClassLoader parent,
            int targetSdkVersion, boolean isNamespaceShared, String classLoaderName) {
        return createClassLoader(dexPath, librarySearchPath, libraryPermittedPath,
            parent, targetSdkVersion, isNamespaceShared, classLoaderName, null);
    }


    /**
     * Create a ClassLoader and initialize a linker-namespace for it.
     */
    public static ClassLoader createClassLoader(String dexPath,
            String librarySearchPath, String libraryPermittedPath, ClassLoader parent,
            int targetSdkVersion, boolean isNamespaceShared, String classLoaderName,
            List<ClassLoader> sharedLibraries) {

        final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent,
                classLoaderName, sharedLibraries);

        boolean isForVendor = false;
        for (String path : dexPath.split(":")) {
            if (path.startsWith("/vendor/")) {
                isForVendor = true;
                break;
            }
        }
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "createClassloaderNamespace");
        String errorMessage = createClassloaderNamespace(classLoader,
                                                         targetSdkVersion,
                                                         librarySearchPath,
                                                         libraryPermittedPath,
                                                         isNamespaceShared,
                                                         isForVendor);
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

        if (errorMessage != null) {
            throw new UnsatisfiedLinkError("Unable to create namespace for the classloader " +
                                           classLoader + ": " + errorMessage);
        }

        return classLoader;
    }

    private static native String createClassloaderNamespace(ClassLoader classLoader,
                                                            int targetSdkVersion,
                                                            String librarySearchPath,
                                                            String libraryPermittedPath,
                                                            boolean isNamespaceShared,
                                                            boolean isForVendor);
}
