Cleanup (obsolete API deprecated)
[idea/community.git] / platform / util / src / com / intellij / util / lang / UrlClassLoader.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.util.lang;
17
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;
28
29 import java.io.File;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.net.MalformedURLException;
33 import java.net.URL;
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;
39
40 public class UrlClassLoader extends ClassLoader {
41   @NonNls static final String CLASS_EXTENSION = ".class";
42
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;
51
52     private Builder() { }
53
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; }
64
65     public UrlClassLoader get() { return new UrlClassLoader(this); }
66   }
67
68   public static Builder build() {
69     return new Builder();
70   }
71
72   private final List<URL> myURLs;
73   private final ClassPath myClassPath;
74   private final boolean myAllowBootstrapResources;
75
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());
79   }
80
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));
84   }
85
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));
89   }
90
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));
94   }
95
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) {
98     super(parent);
99     myURLs = ContainerUtil.map(urls, new Function<URL, URL>() {
100       @Override
101       public URL fun(URL url) {
102         return internProtocol(url);
103       }
104     });
105     myClassPath = new ClassPath(myURLs, lockJars, useCache, allowUnescaped, preload);
106     myAllowBootstrapResources = false;
107   }
108
109   protected UrlClassLoader(@NotNull Builder builder) {
110     super(builder.myParent);
111     myURLs = ContainerUtil.map(builder.myURLs, new Function<URL, URL>() {
112       @Override
113       public URL fun(URL url) {
114         return internProtocol(url);
115       }
116     });
117     myClassPath = new ClassPath(myURLs, builder.myLockJars, builder.myUseCache, builder.myAcceptUnescaped, builder.myPreload);
118     myAllowBootstrapResources = builder.myAllowBootstrapResources;
119   }
120
121   public static URL internProtocol(@NotNull URL url) {
122     try {
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());
126       }
127       return url;
128     }
129     catch (MalformedURLException e) {
130       Logger.getInstance(UrlClassLoader.class).error(e);
131       return null;
132     }
133   }
134
135   /** @deprecated to be removed in IDEA 15 */
136   @SuppressWarnings({"unused", "deprecation"})
137   public void addURL(URL url) {
138     myClassPath.addURL(url);
139     myURLs.add(url);
140   }
141
142   public List<URL> getUrls() {
143     return Collections.unmodifiableList(myURLs);
144   }
145
146   @Override
147   protected Class findClass(final String name) throws ClassNotFoundException {
148     Resource res = myClassPath.getResource(name.replace('.', '/').concat(CLASS_EXTENSION), false);
149     if (res == null) {
150       throw new ClassNotFoundException(name);
151     }
152
153     try {
154       return defineClass(name, res);
155     }
156     catch (IOException e) {
157       throw new ClassNotFoundException(name, e);
158     }
159   }
160
161   @Nullable
162   protected Class _findClass(@NotNull String name) {
163     Resource res = myClassPath.getResource(name.replace('.', '/').concat(CLASS_EXTENSION), false);
164     if (res == null) {
165       return null;
166     }
167
168     try {
169       return defineClass(name, res);
170     }
171     catch (IOException e) {
172       return null;
173     }
174   }
175
176   private Class defineClass(String name, Resource res) throws IOException {
177     int i = name.lastIndexOf('.');
178     if (i != -1) {
179       String pkgName = name.substring(0, i);
180       // Check if package already loaded.
181       Package pkg = getPackage(pkgName);
182       if (pkg == null) {
183         try {
184           definePackage(pkgName, null, null, null, null, null, null, null);
185         }
186         catch (IllegalArgumentException e) {
187           // do nothing, package already defined by some other thread
188         }
189       }
190     }
191
192     byte[] b = res.getBytes();
193     return _defineClass(name, b);
194   }
195
196   protected Class _defineClass(final String name, final byte[] b) {
197     return defineClass(name, b, 0, b.length);
198   }
199
200   @Override
201   @Nullable  // Accessed from PluginClassLoader via reflection // TODO do we need it?
202   public URL findResource(final String name) {
203     return findResourceImpl(name);
204   }
205
206   protected URL findResourceImpl(final String name) {
207     Resource res = _getResource(name);
208     return res != null ? res.getURL() : null;
209   }
210
211   @Nullable
212   private Resource _getResource(final String name) {
213     String n = name;
214     if (n.startsWith("/")) n = n.substring(1);
215     return myClassPath.getResource(n, true);
216   }
217
218   @Nullable
219   @Override
220   public InputStream getResourceAsStream(final String name) {
221     if (myAllowBootstrapResources) return super.getResourceAsStream(name);
222     try {
223       Resource res = _getResource(name);
224       if (res == null) return null;
225       return res.getInputStream();
226     }
227     catch (IOException e) {
228       return null;
229     }
230   }
231
232   // Accessed from PluginClassLoader via reflection // TODO do we need it?
233   @Override
234   protected Enumeration<URL> findResources(String name) throws IOException {
235     return myClassPath.getResources(name, true);
236   }
237
238   public static void loadPlatformLibrary(@NotNull String libName) {
239     String libFileName = mapLibraryName(libName);
240     String libPath = PathManager.getBinPath() + "/" + libFileName;
241
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()));
249           }
250         }
251       }
252     }
253
254     System.load(libPath);
255   }
256
257   private static String mapLibraryName(String libName) {
258     String baseName = libName;
259     if (SystemInfo.is64Bit) {
260       baseName = baseName.replace("32", "") + "64";
261     }
262     String fileName = System.mapLibraryName(baseName);
263     if (SystemInfo.isMac) {
264       fileName = fileName.replace(".jnilib", ".dylib");
265     }
266     return fileName;
267   }
268
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/";
273     else return "";
274   }
275 }