2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.util.lang;
18 import com.intellij.openapi.application.PathManager;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.util.SystemInfo;
21 import com.intellij.openapi.util.io.win32.IdeaWin32;
22 import com.intellij.util.Function;
23 import com.intellij.util.containers.ContainerUtil;
24 import org.jetbrains.annotations.NonNls;
25 import org.jetbrains.annotations.NotNull;
26 import org.jetbrains.annotations.Nullable;
27 import sun.misc.Resource;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.net.MalformedURLException;
34 import java.net.URLClassLoader;
35 import java.util.Arrays;
36 import java.util.Collections;
37 import java.util.Enumeration;
38 import java.util.List;
40 public class UrlClassLoader extends ClassLoader {
41 @NonNls static final String CLASS_EXTENSION = ".class";
43 public static final class Builder {
44 private List<URL> myURLs = ContainerUtil.emptyList();
45 private ClassLoader myParent = null;
46 private boolean myLockJars = false;
47 private boolean myUseCache = false;
48 private boolean myAcceptUnescaped = false;
49 private boolean myPreload = true;
50 private boolean myAllowBootstrapResources = false;
54 public Builder urls(List<URL> urls) { myURLs = urls; return this; }
55 public Builder urls(URL... urls) { myURLs = Arrays.asList(urls); return this; }
56 public Builder parent(ClassLoader parent) { myParent = parent; return this; }
57 public Builder allowLock() { myLockJars = true; return this; }
58 public Builder allowLock(boolean lockJars) { myLockJars = lockJars; return this; }
59 public Builder useCache() { myUseCache = true; return this; }
60 public Builder useCache(boolean useCache) { myUseCache = useCache; return this; }
61 public Builder allowUnescaped() { myAcceptUnescaped = true; return this; }
62 public Builder noPreload() { myPreload = false; return this; }
63 public Builder allowBootstrapResources() { myAllowBootstrapResources = true; return this; }
65 public UrlClassLoader get() { return new UrlClassLoader(this); }
68 public static Builder build() {
72 private final List<URL> myURLs;
73 private final ClassPath myClassPath;
74 private final boolean myAllowBootstrapResources;
76 /** @deprecated use {@link #build()} (to remove in IDEA 14) */
77 public UrlClassLoader(@NotNull ClassLoader parent) {
78 this(build().urls(((URLClassLoader)parent).getURLs()).parent(parent.getParent()).allowLock().useCache());
81 /** @deprecated use {@link #build()} (to remove in IDEA 14) */
82 public UrlClassLoader(List<URL> urls, @Nullable ClassLoader parent) {
83 this(build().urls(urls).parent(parent));
86 /** @deprecated use {@link #build()} (to remove in IDEA 14) */
87 public UrlClassLoader(URL[] urls, @Nullable ClassLoader parent) {
88 this(build().urls(urls).parent(parent));
91 /** @deprecated use {@link #build()} (to remove in IDEA 14) */
92 public UrlClassLoader(List<URL> urls, @Nullable ClassLoader parent, boolean lockJars, boolean useCache) {
93 this(build().urls(urls).parent(parent).allowLock(lockJars).useCache(useCache));
96 /** @deprecated use {@link #build()} (to remove in IDEA 14) */
97 public UrlClassLoader(List<URL> urls, @Nullable ClassLoader parent, boolean lockJars, boolean useCache, boolean allowUnescaped, boolean preload) {
99 myURLs = ContainerUtil.map(urls, new Function<URL, URL>() {
101 public URL fun(URL url) {
102 return internProtocol(url);
105 myClassPath = new ClassPath(myURLs, lockJars, useCache, allowUnescaped, preload);
106 myAllowBootstrapResources = false;
109 protected UrlClassLoader(@NotNull Builder builder) {
110 super(builder.myParent);
111 myURLs = ContainerUtil.map(builder.myURLs, new Function<URL, URL>() {
113 public URL fun(URL url) {
114 return internProtocol(url);
117 myClassPath = new ClassPath(myURLs, builder.myLockJars, builder.myUseCache, builder.myAcceptUnescaped, builder.myPreload);
118 myAllowBootstrapResources = builder.myAllowBootstrapResources;
121 public static URL internProtocol(@NotNull URL url) {
123 final String protocol = url.getProtocol();
124 if ("file".equals(protocol) || "jar".equals(protocol)) {
125 return new URL(protocol.intern(), url.getHost(), url.getPort(), url.getFile());
129 catch (MalformedURLException e) {
130 Logger.getInstance(UrlClassLoader.class).error(e);
135 /** @deprecated to be removed in IDEA 15 */
136 @SuppressWarnings({"unused", "deprecation"})
137 public void addURL(URL url) {
138 myClassPath.addURL(url);
142 public List<URL> getUrls() {
143 return Collections.unmodifiableList(myURLs);
147 protected Class findClass(final String name) throws ClassNotFoundException {
148 Resource res = myClassPath.getResource(name.replace('.', '/').concat(CLASS_EXTENSION), false);
150 throw new ClassNotFoundException(name);
154 return defineClass(name, res);
156 catch (IOException e) {
157 throw new ClassNotFoundException(name, e);
162 protected Class _findClass(@NotNull String name) {
163 Resource res = myClassPath.getResource(name.replace('.', '/').concat(CLASS_EXTENSION), false);
169 return defineClass(name, res);
171 catch (IOException e) {
176 private Class defineClass(String name, Resource res) throws IOException {
177 int i = name.lastIndexOf('.');
179 String pkgName = name.substring(0, i);
180 // Check if package already loaded.
181 Package pkg = getPackage(pkgName);
184 definePackage(pkgName, null, null, null, null, null, null, null);
186 catch (IllegalArgumentException e) {
187 // do nothing, package already defined by some other thread
192 byte[] b = res.getBytes();
193 return _defineClass(name, b);
196 protected Class _defineClass(final String name, final byte[] b) {
197 return defineClass(name, b, 0, b.length);
201 @Nullable // Accessed from PluginClassLoader via reflection // TODO do we need it?
202 public URL findResource(final String name) {
203 return findResourceImpl(name);
206 protected URL findResourceImpl(final String name) {
207 Resource res = _getResource(name);
208 return res != null ? res.getURL() : null;
212 private Resource _getResource(final String name) {
214 if (n.startsWith("/")) n = n.substring(1);
215 return myClassPath.getResource(n, true);
220 public InputStream getResourceAsStream(final String name) {
221 if (myAllowBootstrapResources) return super.getResourceAsStream(name);
223 Resource res = _getResource(name);
224 if (res == null) return null;
225 return res.getInputStream();
227 catch (IOException e) {
232 // Accessed from PluginClassLoader via reflection // TODO do we need it?
234 protected Enumeration<URL> findResources(String name) throws IOException {
235 return myClassPath.getResources(name, true);
238 public static void loadPlatformLibrary(@NotNull String libName) {
239 String libFileName = mapLibraryName(libName);
240 String libPath = PathManager.getBinPath() + "/" + libFileName;
242 if (!new File(libPath).exists()) {
243 String platform = getPlatformName();
244 if (!new File(libPath = PathManager.getHomePath() + "/community/bin/" + platform + libFileName).exists()) {
245 if (!new File(libPath = PathManager.getHomePath() + "/bin/" + platform + libFileName).exists()) {
246 if (!new File(libPath = PathManager.getHomePathFor(IdeaWin32.class) + "/bin/" + libFileName).exists()) {
247 File libDir = new File(PathManager.getBinPath());
248 throw new UnsatisfiedLinkError("'" + libFileName + "' not found in '" + libDir + "' among " + Arrays.toString(libDir.list()));
254 System.load(libPath);
257 private static String mapLibraryName(String libName) {
258 String baseName = libName;
259 if (SystemInfo.is64Bit) {
260 baseName = baseName.replace("32", "") + "64";
262 String fileName = System.mapLibraryName(baseName);
263 if (SystemInfo.isMac) {
264 fileName = fileName.replace(".jnilib", ".dylib");
269 private static String getPlatformName() {
270 if (SystemInfo.isWindows) return "win/";
271 else if (SystemInfo.isMac) return "mac/";
272 else if (SystemInfo.isLinux) return "linux/";