/*
* Copyright 2016 NAVER Corp.
*
* 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.navercorp.pinpoint.test.classloader;
import com.navercorp.pinpoint.profiler.util.JavaAssistUtils;
import com.navercorp.pinpoint.test.util.BytecodeUtils;
import java.io.InputStream;
import java.security.ProtectionDomain;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* copy & modify javassist.Loader
*
* @author Woonduk Kang(emeroad)
*/
public class TransformClassLoader extends ClassLoader {
private final Logger logger = Logger.getLogger(TransformClassLoader.class.getName());
private final ConcurrentMap<String, Object> lockMap = new ConcurrentHashMap<String, Object>();
private final Set<String> notDefinedClass = new CopyOnWriteArraySet<String>();
private final List<String> notDefinedPackages = new CopyOnWriteArrayList<String>();
private Translator translator;
private ProtectionDomain domain;
/**
* Specifies the algorithm of class loading.
* <p>
* <p>This class loader uses the parent class loader for
* <code>java.*</code> and <code>javax.*</code> classes.
* If this variable <code>doDelegation</code>
* is <code>false</code>, this class loader does not delegate those
* classes to the parent class loader.
* <p>
* <p>The default value is <code>true</code>.
*/
public boolean doDelegation = true;
/**
* Creates a new class loader.
*/
public TransformClassLoader() {
}
/**
* Creates a new class loader
* using the specified parent class loader for delegation.
*
* @param parent the parent class loader.
*/
public TransformClassLoader(ClassLoader parent) {
super(parent);
init();
}
private void init() {
translator = null;
domain = null;
delegateLoadingOf("com.navercorp.pinpoint.test.classloader.TransformClassLoader");
}
/**
* Adds a translator, which is called whenever a class is loaded.
*
* @param t a translator.
*/
public void addTranslator(Translator t) {
translator = t;
t.start();
}
/**
* Records a class so that the loading of that class is delegated
* to the parent class loader.
* <p>
* <p>If the given class name ends with <code>.</code> (dot), then
* that name is interpreted as a package name. All the classes
* in that package and the sub packages are delegated.
*/
public void delegateLoadingOf(String classname) {
if (classname.endsWith(".")) {
notDefinedPackages.add(classname);
}
else {
notDefinedClass.add(classname);
}
}
/**
* Sets the protection domain for the classes handled by this class
* loader. Without registering an appropriate protection domain,
* the program loaded by this loader will not work with a security
* manager or a signed jar file.
*/
public void setDomain(ProtectionDomain d) {
domain = d;
}
/**
* Requests the class loader to load a class.
*/
protected Class loadClass(String name, boolean resolve)
throws ClassFormatError, ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
if (c == null) {
c = loadClassByDelegation(name);
}
if (c == null) {
c = findClass(name);
}
if (c == null) {
c = delegateToParent(name);
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
protected Object getClassLoadingLock(String className) {
final Object newLock = new Object();
final Object existLock = lockMap.putIfAbsent(className, newLock);
if (existLock != null) {
return existLock;
}
return newLock;
}
/**
* Finds the specified class using <code>ClassPath</code>.
* If the source throws an exception, this returns null.
* <p>
* <p>This method can be overridden by a subclass of
* <code>Loader</code>. Note that the overridden method must not throw
* an exception when it just fails to find a class file.
*
* @return null if the specified class could not be found.
* @throws ClassNotFoundException if an exception is thrown while
* obtaining a class file.
*/
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classfile;
try {
if (translator != null) {
try {
classfile = translator.transform(name);
} catch (ClassNotFoundException e) {
return null;
}
} else {
String jarname = "/" + JavaAssistUtils.javaNameToJvmName(name) + ".class";
InputStream in = this.getClass().getClassLoader().getResourceAsStream(jarname);
if (in == null) {
return null;
}
classfile = BytecodeUtils.readClass(in, true);
}
} catch (Exception e) {
throw new ClassNotFoundException("caught an exception while obtaining a class file for " + name, e);
}
final int i = name.lastIndexOf('.');
if (i != -1) {
String pname = name.substring(0, i);
if (getPackage(pname) == null)
try {
definePackage(pname, null, null, null, null, null, null, null);
} catch (IllegalArgumentException e) {
// ignore. maybe the package object for the same
// name has been created just right away.
}
}
if (domain == null) {
if (logger.isLoggable(Level.FINE)) {
this.logger.fine("defineClass:" + name);
}
return defineClass(name, classfile, 0, classfile.length);
}
else {
if (logger.isLoggable(Level.FINE)) {
this.logger.fine("defineClass:" + name);
}
return defineClass(name, classfile, 0, classfile.length, domain);
}
}
protected Class<?> loadClassByDelegation(String name)
throws ClassNotFoundException {
/* The swing components must be loaded by a system
* class loader.
* javax.swing.UIManager loads a (concrete) subclass
* of LookAndFeel by a system class loader and cast
* an instance of the class to LookAndFeel for
* (maybe) a security reason. To avoid failure of
* type conversion, LookAndFeel must not be loaded
* by this class loader.
*/
Class<?> c = null;
if (doDelegation) {
if (isJdkPackage(name) || notDelegated(name))
c = delegateToParent(name);
}
return c;
}
private boolean isJdkPackage(String name) {
return name.startsWith("java.")
|| name.startsWith("javax.")
|| name.startsWith("sun.")
|| name.startsWith("com.sun.")
|| name.startsWith("org.w3c.")
|| name.startsWith("org.xml.");
}
private boolean notDelegated(String name) {
if (notDefinedClass.contains(name)) {
return true;
}
for (String notDefinedPackage : notDefinedPackages) {
if (name.startsWith(notDefinedPackage)) {
return true;
}
}
return false;
}
protected Class<?> delegateToParent(String classname)
throws ClassNotFoundException {
ClassLoader cl = getParent();
if (cl != null) {
return cl.loadClass(classname);
} else {
return findSystemClass(classname);
}
}
}