| /* |
| * 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.openapi.util.io.win32.IdeaWin32; |
| import com.intellij.util.Function; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import sun.misc.Resource; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| 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"; |
| |
| 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 boolean myAllowBootstrapResources = false; |
| |
| 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 Builder allowBootstrapResources() { myAllowBootstrapResources = true; 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; |
| private final boolean myAllowBootstrapResources; |
| |
| /** @deprecated use {@link #build()} (to remove in IDEA 14) */ |
| public UrlClassLoader(@NotNull ClassLoader parent) { |
| this(build().urls(((URLClassLoader)parent).getURLs()).parent(parent.getParent()).allowLock().useCache()); |
| } |
| |
| /** @deprecated use {@link #build()} (to remove in IDEA 14) */ |
| public UrlClassLoader(List<URL> urls, @Nullable ClassLoader parent) { |
| this(build().urls(urls).parent(parent)); |
| } |
| |
| /** @deprecated use {@link #build()} (to remove in IDEA 14) */ |
| public UrlClassLoader(URL[] urls, @Nullable ClassLoader parent) { |
| this(build().urls(urls).parent(parent)); |
| } |
| |
| /** @deprecated use {@link #build()} (to remove in IDEA 14) */ |
| public UrlClassLoader(List<URL> urls, @Nullable ClassLoader parent, boolean lockJars, boolean useCache) { |
| this(build().urls(urls).parent(parent).allowLock(lockJars).useCache(useCache)); |
| } |
| |
| /** @deprecated use {@link #build()} (to remove in IDEA 14) */ |
| public UrlClassLoader(List<URL> urls, @Nullable ClassLoader parent, boolean lockJars, boolean useCache, boolean allowUnescaped, boolean preload) { |
| super(parent); |
| myURLs = ContainerUtil.map(urls, new Function<URL, URL>() { |
| @Override |
| public URL fun(URL url) { |
| return internProtocol(url); |
| } |
| }); |
| myClassPath = new ClassPath(myURLs, lockJars, useCache, allowUnescaped, preload); |
| myAllowBootstrapResources = false; |
| } |
| |
| 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); |
| myAllowBootstrapResources = builder.myAllowBootstrapResources; |
| } |
| |
| 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) { |
| if (myAllowBootstrapResources) return super.getResourceAsStream(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()) { |
| String platform = getPlatformName(); |
| if (!new File(libPath = PathManager.getHomePath() + "/community/bin/" + platform + libFileName).exists()) { |
| if (!new File(libPath = PathManager.getHomePath() + "/bin/" + platform + libFileName).exists()) { |
| if (!new File(libPath = PathManager.getHomePathFor(IdeaWin32.class) + "/bin/" + libFileName).exists()) { |
| File libDir = new File(PathManager.getBinPath()); |
| throw new UnsatisfiedLinkError("'" + libFileName + "' not found in '" + libDir + "' among " + Arrays.toString(libDir.list())); |
| } |
| } |
| } |
| } |
| |
| 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 ""; |
| } |
| } |