blob: ae65d00e209f8dc5dbde1e4c93e82f09783d9ffd [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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.intellij.ide.plugins.cl;
import com.intellij.diagnostic.PluginException;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.lang.UrlClassLoader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import sun.misc.CompoundEnumeration;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.ListIterator;
/**
* @author Eugene Zhuravlev
* @since 6.03.2003
*/
public class PluginClassLoader extends UrlClassLoader {
private final ClassLoader[] myParents;
private final PluginId myPluginId;
private final String myPluginVersion;
private final List<String> myLibDirectories;
public PluginClassLoader(@NotNull List<URL> urls,
@NotNull ClassLoader[] parents,
PluginId pluginId,
String version,
File pluginRoot) {
super(build().urls(urls).allowLock().useCache());
myParents = parents;
myPluginId = pluginId;
myPluginVersion = version;
myLibDirectories = ContainerUtil.newSmartList();
File libDir = new File(pluginRoot, "lib");
if (libDir.exists()) {
myLibDirectories.add(libDir.getAbsolutePath());
}
}
@Override
public Class loadClass(@NotNull String name, final boolean resolve) throws ClassNotFoundException {
Class c = tryLoadingClass(name, resolve);
if (c == null) {
throw new ClassNotFoundException(name + " " + this);
}
return c;
}
// Changed sequence in which classes are searched, this is essential if plugin uses library,
// a different version of which is used in IDEA.
@Nullable
private Class tryLoadingClass(@NotNull String name, final boolean resolve) {
Class c = loadClassInsideSelf(name);
if (c == null) {
c = loadClassFromParents(name);
}
if (c != null) {
if (resolve) {
resolveClass(c);
}
return c;
}
PluginManagerCore.addPluginClass(name, myPluginId, false);
return null;
}
@Nullable
private Class loadClassFromParents(final String name) {
for (ClassLoader parent : myParents) {
if (parent instanceof PluginClassLoader) {
Class c = ((PluginClassLoader)parent).tryLoadingClass(name, false);
if (c != null) {
return c;
}
continue;
}
try {
return parent.loadClass(name);
}
catch (ClassNotFoundException ignoreAndContinue) {
// Ignore and continue
}
}
return null;
}
@Nullable
private synchronized Class loadClassInsideSelf(@NotNull String name) {
Class c = findLoadedClass(name);
if (c != null) {
return c;
}
try {
c = _findClass(name);
}
catch (IncompatibleClassChangeError e) {
throw new PluginException(e, myPluginId);
}
catch (UnsupportedClassVersionError e) {
throw new PluginException(e, myPluginId);
}
if (c != null) {
PluginManagerCore.addPluginClass(c.getName(), myPluginId, true);
}
return c;
}
public boolean hasLoadedClass(String name) {
Class<?> aClass = findLoadedClass(name);
return aClass != null && aClass.getClassLoader() == this;
}
@Override
public URL findResource(final String name) {
final URL resource = findResourceImpl(name);
if (resource != null) return resource;
for (ClassLoader parent : myParents) {
final URL parentResource = fetchResource(parent, name);
if (parentResource != null) return parentResource;
}
return null;
}
@Nullable
@Override
public InputStream getResourceAsStream(final String name) {
final InputStream stream = super.getResourceAsStream(name);
if (stream != null) return stream;
for (ClassLoader parent : myParents) {
final InputStream inputStream = parent.getResourceAsStream(name);
if (inputStream != null) return inputStream;
}
return null;
}
@Override
public Enumeration<URL> findResources(final String name) throws IOException {
final Enumeration[] resources = new Enumeration[myParents.length + 1];
resources[0] = super.findResources(name);
for (int idx = 0; idx < myParents.length; idx++) {
resources[idx + 1] = fetchResources(myParents[idx], name);
}
return new CompoundEnumeration<URL>(resources);
}
@SuppressWarnings("UnusedDeclaration")
public void addLibDirectories(@NotNull Collection<String> libDirectories) {
myLibDirectories.addAll(libDirectories);
}
@Override
protected String findLibrary(String libName) {
if (!myLibDirectories.isEmpty()) {
String libFileName = System.mapLibraryName(libName);
ListIterator<String> i = myLibDirectories.listIterator(myLibDirectories.size());
while (i.hasPrevious()) {
File libFile = new File(i.previous(), libFileName);
if (libFile.exists()) {
return libFile.getAbsolutePath();
}
}
}
return null;
}
private static URL fetchResource(ClassLoader cl, String resourceName) {
try {
final Method findResourceMethod = getFindResourceMethod(cl.getClass(), "findResource");
return (URL)findResourceMethod.invoke(cl, resourceName);
}
catch (Exception e) {
Logger.getInstance(PluginClassLoader.class).error(e);
return null;
}
}
private static Enumeration fetchResources(ClassLoader cl, String resourceName) {
try {
final Method findResourceMethod = getFindResourceMethod(cl.getClass(), "findResources");
return findResourceMethod == null ? null : (Enumeration)findResourceMethod.invoke(cl, resourceName);
}
catch (Exception e) {
Logger.getInstance(PluginClassLoader.class).error(e);
return null;
}
}
private static Method getFindResourceMethod(final Class<?> clClass, final String methodName) {
try {
final Method declaredMethod = clClass.getDeclaredMethod(methodName, String.class);
declaredMethod.setAccessible(true);
return declaredMethod;
}
catch (NoSuchMethodException e) {
final Class superclass = clClass.getSuperclass();
if (superclass == null || superclass.equals(Object.class)) {
return null;
}
return getFindResourceMethod(superclass, methodName);
}
}
public PluginId getPluginId() {
return myPluginId;
}
@Override
public String toString() {
return "PluginClassLoader[" + myPluginId + ", " + myPluginVersion + "]";
}
}