/*
 * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.testing.oxm.classloader;

import java.io.*;
import java.net.URISyntaxException;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.*;
import java.util.zip.*;

import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedGetClassLoaderForClass;


/**
 * This class is an implementation of the <code>ClassLoader</code> abstract class.
 * Its purpose is to allow the ability to load a class from
 * a particular JAR file without regard to the system class path.
 *
 * @author Big Country
 * @since TOPLink/Java 3.0
 */
public class JARClassLoader extends ClassLoader {
    /** Map each package name to the appropriate JAR file. */
    private Hashtable overridePackageNames;

/**
 * Default constructor - initialize the new instance.
 */
protected JARClassLoader() {
    super();
    this.initialize();
}
/**
 * Construct a file loader for the JAR files.
 */
public JARClassLoader (String[] jarFileNames) {
    this();
    this.initialize(jarFileNames);
}
/**
 * Construct a file loader for the JAR file.
 */
public JARClassLoader (String jarFileName) {
    this();
    this.initialize(jarFileName);
}
/**
 * Build and return a ZIP file for the specified JAR file name.
 */
protected ZipFile buildJARFile(String jarFileName) {
    try {
        return new ZipFile(new File(Thread.currentThread().getContextClassLoader().getResource(jarFileName).toURI()));
    } catch (IOException e) {
        throw new RuntimeException(e);
    } catch (URISyntaxException e) {
        throw new RuntimeException(e);
    }
}
/**
 * Return the package name for the specified class.
 */
protected String buildPackageName(String className) {
    return className.substring(0, className.lastIndexOf("."));
}
/**
 * Return the class for the specified name, leaving it "unresolved".
 */
protected Class customLoadUnresolvedClass(String className) throws ClassNotFoundException {
    String jarFileName = (String) overridePackageNames.get(this.buildPackageName(className));
    ZipFile jarFile = this.buildJARFile(jarFileName);

    String url = className.replace('.', '/').concat(".class");
    ZipEntry jarEntry = jarFile.getEntry(url);
  if(jarEntry == null){
    return this.getParent().loadClass(className);
  }
    byte[] data;
    try {
        data = this.loadData(jarFile, jarEntry);
        jarFile.close();
    } catch (IOException e) {
        Object[] args = {jarFileName, url};
        throw new ClassNotFoundException();
    }
    return this.defineClass(className, data, 0, data.length);
}
/**
 * Extract the package names from the specified JAR file
 * and store them in the hashtable.
 */
protected void extractPackageNames(String jarFileName) {
    Enumeration stream = this.buildJARFile(jarFileName).entries();
    while (stream.hasMoreElements()) {
        String entryName = ((ZipEntry) stream.nextElement()).getName();
        int endIndex = entryName.lastIndexOf("/");
        // skip over entries for files in the root directory and directories
        if ((endIndex != -1) && (endIndex != (entryName.length() - 1))) {
            String packageName = (entryName.substring(0, endIndex)).replace('/', '.');
            // skip over JAR META-INF directory
            if (! packageName.equals("META-INF")) {
                overridePackageNames.put(packageName, jarFileName);
            }
        }
    }
}
/**
 * Initialize the newly-created instance.
 */
protected void initialize() {
    this.overridePackageNames = new Hashtable();
}
/**
 * Initialize
 */
protected void initialize(String[] jarFileNames) {
    if (jarFileNames == null) {
        throw new IllegalArgumentException();
    }
    // go backwards to maintain search order (earlier packages will overlay later ones)
    for (int i = jarFileNames.length - 1; i >= 0; i--) {
        this.extractPackageNames(jarFileNames[i]);
    }
}
/**
 * Initialize
 */
protected void initialize(String jarFileName) {
    if (jarFileName == null) {
        throw new IllegalArgumentException();
    }
    this.initialize(new String[] {jarFileName});
}
/**
 * Return the class for the specified name, resolving it if necessary.
 */
protected Class loadClass(String className, boolean resolve) throws ClassNotFoundException {
    Class c = this.loadUnresolvedClass(className);
    if (resolve) {
        this.resolveClass(c);
    }
    return c;
}
/**
 * Return a byte array holding the contents of the specified file.
 */
protected byte[] loadData(ZipFile jarFile, ZipEntry jarEntry) throws IOException {

    int size = (int) jarEntry.getSize();
    byte[] buffer = new byte[size];

    DataInputStream stream = new DataInputStream(jarFile.getInputStream(jarEntry));
    stream.readFully(buffer);
    stream.close();

    return buffer;
}
/**
 * Return the class for the specified name.
 */
protected Class loadUnresolvedClass(String className) throws ClassNotFoundException {
    // check whether we already loaded the class
    Class c = this.findLoadedClass(className);
    if (c != null) {
        return c;
    }
    // check whether we should custom load the class
    if (this.shouldCustomLoad(className)) {
        return this.customLoadUnresolvedClass(className);
    } else {
        ClassLoader cl = null;
        if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
            try{
                cl = AccessController.doPrivileged(new PrivilegedGetClassLoaderForClass(this.getClass()));
            }catch (PrivilegedActionException ex){
                throw (RuntimeException)ex.getCause();
            }
        }else{
             cl = PrivilegedAccessHelper.getClassLoaderForClass(this.getClass());
        }
        if (cl == null) {
            // this should only occur under jdk1.1.x
            return this.findSystemClass(className);
        } else {
            return cl.loadClass(className);
        }
    }
}
/**
 * Return whether the specified class should be custom loaded.
 * If it is a member of one of the override packages, it should be
 * custom loaded.
 */
protected boolean shouldCustomLoad(String className) {
    return overridePackageNames.containsKey(this.buildPackageName(className));
}
}
