/* * 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.util.lang; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.SystemInfo; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import consulo.internal.sun.misc.Resource; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.List; public class UrlClassLoader extends ClassLoader { @NonNls static final String CLASS_EXTENSION = ".class"; private static final boolean HAS_PARALLEL_LOADERS = SystemInfo.isJavaVersionAtLeast("1.7") && !SystemInfo.isIbmJvm; static { if (HAS_PARALLEL_LOADERS) { try { //todo Patches.USE_REFLECTION_TO_ACCESS_JDK7 Method registerAsParallelCapable = ClassLoader.class.getDeclaredMethod("registerAsParallelCapable"); registerAsParallelCapable.setAccessible(true); registerAsParallelCapable.invoke(null); } catch (Exception ignored) { } } } public static boolean isRegisteredAsParallelCapable(@NotNull ClassLoader loader) { if (!HAS_PARALLEL_LOADERS) return false; try { //todo Patches.USE_REFLECTION_TO_ACCESS_JDK7 Field parallelLockMap = ClassLoader.class.getDeclaredField("parallelLockMap"); parallelLockMap.setAccessible(true); return parallelLockMap.get(loader) != null; } catch (Exception e) { throw new AssertionError("Internal error: ClassLoader implementation has been altered"); } } public static final class Builder { private List<URL> myURLs = ContainerUtil.emptyList(); private ClassLoader myParent = null; private boolean myLockJars = false; private boolean myUseCache = false; private boolean myAcceptUnescaped = false; private boolean myPreload = true; private Builder() { } public Builder urls(List<URL> urls) { myURLs = urls; return this; } public Builder urls(URL... urls) { myURLs = Arrays.asList(urls); return this; } public Builder parent(ClassLoader parent) { myParent = parent; return this; } public Builder allowLock() { myLockJars = true; return this; } public Builder allowLock(boolean lockJars) { myLockJars = lockJars; return this; } public Builder useCache() { myUseCache = true; return this; } public Builder useCache(boolean useCache) { myUseCache = useCache; return this; } public Builder allowUnescaped() { myAcceptUnescaped = true; return this; } public Builder noPreload() { myPreload = false; return this; } public UrlClassLoader get() { return new UrlClassLoader(this); } } public static Builder build() { return new Builder(); } private final List<URL> myURLs; private final ClassPath myClassPath; protected UrlClassLoader(@NotNull Builder builder) { super(builder.myParent); myURLs = ContainerUtil.map(builder.myURLs, new Function<URL, URL>() { @Override public URL fun(URL url) { return internProtocol(url); } }); myClassPath = new ClassPath(myURLs, builder.myLockJars, builder.myUseCache, builder.myAcceptUnescaped, builder.myPreload); } public static URL internProtocol(@NotNull URL url) { try { final String protocol = url.getProtocol(); if ("file".equals(protocol) || "jar".equals(protocol)) { return new URL(protocol.intern(), url.getHost(), url.getPort(), url.getFile()); } return url; } catch (MalformedURLException e) { Logger.getInstance(UrlClassLoader.class).error(e); return null; } } public void addURL(URL url) { myClassPath.addURL(url); myURLs.add(url); } public List<URL> getUrls() { return Collections.unmodifiableList(myURLs); } @Override protected Class findClass(final String name) throws ClassNotFoundException { Resource res = myClassPath.getResource(name.replace('.', '/').concat(CLASS_EXTENSION), false); if (res == null) { throw new ClassNotFoundException(name); } try { return defineClass(name, res); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } @Nullable protected Class _findClass(@NotNull String name) { Resource res = myClassPath.getResource(name.replace('.', '/').concat(CLASS_EXTENSION), false); if (res == null) { return null; } try { return defineClass(name, res); } catch (IOException e) { return null; } } private Class defineClass(String name, Resource res) throws IOException { int i = name.lastIndexOf('.'); if (i != -1) { String pkgName = name.substring(0, i); // Check if package already loaded. Package pkg = getPackage(pkgName); if (pkg == null) { try { definePackage(pkgName, null, null, null, null, null, null, null); } catch (IllegalArgumentException e) { // do nothing, package already defined by some other thread } } } byte[] b = res.getBytes(); return _defineClass(name, b); } protected Class _defineClass(final String name, final byte[] b) { return defineClass(name, b, 0, b.length); } @Override @Nullable // Accessed from PluginClassLoader via reflection // TODO do we need it? public URL findResource(final String name) { return findResourceImpl(name); } protected URL findResourceImpl(final String name) { Resource res = _getResource(name); return res != null ? res.getURL() : null; } @Nullable private Resource _getResource(final String name) { String n = name; if (n.startsWith("/")) n = n.substring(1); return myClassPath.getResource(n, true); } @Nullable @Override public InputStream getResourceAsStream(final String name) { try { Resource res = _getResource(name); if (res == null) return null; return res.getInputStream(); } catch (IOException e) { return null; } } // Accessed from PluginClassLoader via reflection // TODO do we need it? @Override protected Enumeration<URL> findResources(String name) throws IOException { return myClassPath.getResources(name, true); } public static void loadPlatformLibrary(@NotNull String libName) { String libFileName = mapLibraryName(libName); String libPath = PathManager.getBinPath() + "/" + libFileName; if (!new File(libPath).exists()) { throw new UnsatisfiedLinkError("'" + libFileName + "' not found among " + Arrays.toString(new File(PathManager.getBinPath()).listFiles())); } System.load(libPath); } private static String mapLibraryName(String libName) { String baseName = libName; if (SystemInfo.is64Bit) { baseName = baseName.replace("32", "") + "64"; } String fileName = System.mapLibraryName(baseName); if (SystemInfo.isMac) { fileName = fileName.replace(".jnilib", ".dylib"); } return fileName; } private static String getPlatformName() { if (SystemInfo.isWindows) { return "win/"; } else if (SystemInfo.isMac) { return "mac/"; } else if (SystemInfo.isLinux) { return "linux/"; } else { return ""; } } }