blob: d5678533395b1c9e86f246d5bb6b11724aae7188 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 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.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
/**
* 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<String, String> 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 | 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 = 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) {
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<? extends ZipEntry> stream = this.buildJARFile(jarFileName).entries();
while (stream.hasMoreElements()) {
String entryName = 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.
*/
@Override
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 {
final ClassLoader cl = PrivilegedAccessHelper.callDoPrivileged(
() -> 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));
}
}