Cleanup (obsolete API deprecated)
[idea/community.git] / platform / util / src / com / intellij / util / lang / ClassPath.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.ShutDownTracker;
21 import com.intellij.openapi.util.io.FileUtil;
22 import com.intellij.openapi.util.text.StringUtil;
23 import com.intellij.util.SmartList;
24 import com.intellij.util.containers.HashMap;
25 import com.intellij.util.containers.Stack;
26 import com.intellij.util.io.URLUtil;
27 import org.jetbrains.annotations.Nullable;
28 import sun.misc.Resource;
29
30 import java.io.File;
31 import java.io.FileOutputStream;
32 import java.io.IOException;
33 import java.io.PrintStream;
34 import java.net.URISyntaxException;
35 import java.net.URL;
36 import java.util.*;
37
38 public class ClassPath {
39   private static final ResourceStringLoaderIterator ourCheckedIterator = new ResourceStringLoaderIterator(true);
40   private static final ResourceStringLoaderIterator ourUncheckedIterator = new ResourceStringLoaderIterator(false);
41   private static final LoaderCollector ourLoaderCollector = new LoaderCollector();
42
43   private final Stack<URL> myUrls = new Stack<URL>();
44   private final List<Loader> myLoaders = new ArrayList<Loader>();
45   private final Map<URL, Loader> myLoadersMap = new HashMap<URL, Loader>();
46   private final ClasspathCache myCache = new ClasspathCache();
47
48   private final boolean myCanLockJars;
49   private final boolean myCanUseCache;
50   private final boolean myAcceptUnescapedUrls;
51   private final boolean myPreloadJarContents;
52
53   public ClassPath(List<URL> urls, boolean canLockJars, boolean canUseCache, boolean acceptUnescapedUrls, boolean preloadJarContents) {
54     myCanLockJars = canLockJars;
55     myCanUseCache = canUseCache;
56     myAcceptUnescapedUrls = acceptUnescapedUrls;
57     myPreloadJarContents = preloadJarContents;
58     push(urls);
59   }
60
61   /** @deprecated to be removed in IDEA 15 */
62   void addURL(URL url) {
63     push(Collections.singletonList(url));
64   }
65
66   private void push(List<URL> urls) {
67     if (!urls.isEmpty()) {
68       synchronized (myUrls) {
69         for (int i = urls.size() - 1; i >= 0; i--) {
70           myUrls.push(urls.get(i));
71         }
72       }
73     }
74   }
75
76   @Nullable
77   public Resource getResource(String s, boolean flag) {
78     final long started = startTiming();
79     try {
80       int i;
81       if (myCanUseCache) {
82         Resource prevResource = myCache.iterateLoaders(s, flag ? ourCheckedIterator : ourUncheckedIterator, s, this);
83         if (prevResource != null) return prevResource;
84
85         synchronized (myUrls) {
86           if (myUrls.isEmpty()) return null;
87         }
88
89         i = myLoaders.size();
90       }
91       else {
92         i = 0;
93       }
94
95       String shortName = ClasspathCache.transformName(s);
96
97       Loader loader;
98       while ((loader = getLoader(i++)) != null) {
99         if (myCanUseCache) {
100           if (!myCache.loaderHasName(s, shortName, loader)) continue;
101         }
102         Resource resource = loader.getResource(s, flag);
103         if (resource != null) {
104           return resource;
105         }
106       }
107     }
108     finally {
109       logTiming(this, started, s);
110     }
111
112     return null;
113   }
114
115   public Enumeration<URL> getResources(final String name, final boolean check) {
116     return new MyEnumeration(name, check);
117   }
118
119   @Nullable
120   private synchronized Loader getLoader(int i) {
121     while (myLoaders.size() < i + 1) {
122       boolean lastOne;
123       URL url;
124       synchronized (myUrls) {
125         if (myUrls.empty()) {
126           if (myCanUseCache) myCache.nameSymbolsLoaded();
127           return null;
128         }
129         url = myUrls.pop();
130         lastOne = myUrls.isEmpty();
131       }
132
133       if (myLoadersMap.containsKey(url)) continue;
134
135       Loader loader;
136       try {
137         loader = getLoader(url, myLoaders.size());
138         if (loader == null) continue;
139       }
140       catch (IOException e) {
141         Logger.getInstance(ClassPath.class).debug("url: " + url, e);
142         continue;
143       }
144
145       myLoaders.add(loader);
146       myLoadersMap.put(url, loader);
147       if (lastOne && myCanUseCache) {
148         myCache.nameSymbolsLoaded();
149       }
150     }
151
152     return myLoaders.get(i);
153   }
154
155   @Nullable
156   private Loader getLoader(final URL url, int index) throws IOException {
157     String path;
158
159     if (myAcceptUnescapedUrls) {
160       path = url.getFile();
161     }
162     else {
163       try {
164         path = url.toURI().getSchemeSpecificPart();
165       }
166       catch (URISyntaxException e) {
167         Logger.getInstance(ClassPath.class).error("url: " + url, e);
168         path = url.getFile();
169       }
170     }
171
172     Loader loader = null;
173     if (path != null && URLUtil.FILE_PROTOCOL.equals(url.getProtocol())) {
174       File file = new File(path);
175       if (file.isDirectory()) {
176         loader = new FileLoader(url, index);
177       }
178       else if (file.isFile()) {
179         loader = new JarLoader(url, myCanLockJars, index, myPreloadJarContents);
180       }
181     }
182
183     if (loader != null && myCanUseCache) {
184       ClasspathCache.LoaderData loaderData = new ClasspathCache.LoaderData(loader);
185       loader.buildCache(loaderData);
186       myCache.applyLoaderData(loaderData);
187     }
188
189     return loader;
190   }
191
192   private class MyEnumeration implements Enumeration<URL> {
193     private int myIndex = 0;
194     private Resource myRes = null;
195     private final String myName;
196     private final String myShortName;
197     private final boolean myCheck;
198     private final List<Loader> myLoaders;
199
200     public MyEnumeration(String name, boolean check) {
201       myName = name;
202       myShortName = ClasspathCache.transformName(name);
203       myCheck = check;
204       List<Loader> loaders = null;
205
206       if (myCanUseCache) {
207         synchronized (myUrls) {
208           if (myUrls.isEmpty()) {
209             loaders = new SmartList<Loader>();
210             myCache.iterateLoaders(name, ourLoaderCollector, loaders, this);
211             if (!name.endsWith("/")) {
212               myCache.iterateLoaders(name.concat("/"), ourLoaderCollector, loaders, this);
213             }
214           }
215         }
216       }
217
218       myLoaders = loaders;
219     }
220
221     private boolean next() {
222       if (myRes != null) return true;
223
224       long started = startTiming();
225       try {
226         Loader loader;
227         if (myLoaders != null) {
228           while (myIndex < myLoaders.size()) {
229             loader = myLoaders.get(myIndex++);
230             if (!myCache.loaderHasName(myName, myShortName, loader)) {
231               myRes = null;
232               continue;
233             }
234             myRes = loader.getResource(myName, myCheck);
235             if (myRes != null) return true;
236           }
237         }
238         else {
239           while ((loader = getLoader(myIndex++)) != null) {
240             if (!myCache.loaderHasName(myName, myShortName, loader)) continue;
241             myRes = loader.getResource(myName, myCheck);
242             if (myRes != null) return true;
243           }
244         }
245       }
246       finally {
247         logTiming(ClassPath.this, started, myName);
248       }
249
250       return false;
251     }
252
253     public boolean hasMoreElements() {
254       return next();
255     }
256
257     public URL nextElement() {
258       if (!next()) {
259         throw new NoSuchElementException();
260       }
261       else {
262         Resource resource = myRes;
263         myRes = null;
264         return resource.getURL();
265       }
266     }
267   }
268
269   private static class ResourceStringLoaderIterator extends ClasspathCache.LoaderIterator<Resource, String, ClassPath> {
270     private final boolean myFlag;
271
272     private ResourceStringLoaderIterator(boolean flag) {
273       myFlag = flag;
274     }
275
276     @Override
277     Resource process(Loader loader, String s, ClassPath classPath) {
278       if (!classPath.myCache.loaderHasName(s, ClasspathCache.transformName(s), loader)) return null;
279       final Resource resource = loader.getResource(s, myFlag);
280       if (resource != null) {
281         printOrder(loader, s, resource);
282         return resource;
283       }
284       return null;
285     }
286   }
287
288   private static class LoaderCollector extends ClasspathCache.LoaderIterator<Object, List<Loader>, Object> {
289     @Override
290     Object process(Loader loader, List<Loader> parameter, Object parameter2) {
291       parameter.add(loader);
292       return null;
293     }
294   }
295
296   private static final boolean ourDumpOrder = "true".equals(System.getProperty("idea.dump.order"));
297   private static PrintStream ourOrder;
298   private static long ourOrderSize;
299   private static final Set<String> ourOrderedUrls = new HashSet<String>();
300
301   @SuppressWarnings("UseOfSystemOutOrSystemErr")
302   private static synchronized void printOrder(Loader loader, String url, Resource resource) {
303     if (!ourDumpOrder) return;
304     if (!ourOrderedUrls.add(url)) return;
305
306     String home = FileUtil.toSystemIndependentName(PathManager.getHomePath());
307     try {
308       ourOrderSize += resource.getContentLength();
309     }
310     catch (IOException e) {
311       e.printStackTrace(System.out);
312     }
313
314     if (ourOrder == null) {
315       final File orderFile = new File(PathManager.getBinPath() + File.separator + "order.txt");
316       try {
317         if (!FileUtil.ensureCanCreateFile(orderFile)) return;
318         ourOrder = new PrintStream(new FileOutputStream(orderFile, true));
319         ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
320           public void run() {
321             ourOrder.close();
322             System.out.println(ourOrderSize);
323           }
324         });
325       }
326       catch (IOException e) {
327         return;
328       }
329     }
330
331     if (ourOrder != null) {
332       String jarURL = FileUtil.toSystemIndependentName(loader.getBaseURL().getFile());
333       jarURL = StringUtil.trimStart(jarURL, "file:/");
334       if (jarURL.startsWith(home)) {
335         jarURL = jarURL.replaceFirst(home, "");
336         jarURL = StringUtil.trimEnd(jarURL, "!/");
337         ourOrder.println(url + ":" + jarURL);
338       }
339     }
340   }
341
342
343   private static final boolean ourLogTiming = Boolean.getBoolean("idea.print.classpath.timing");
344   private static long ourTotalTime = 0;
345   private static int ourTotalRequests = 0;
346
347   private static long startTiming() {
348     return ourLogTiming ? System.nanoTime() : 0;
349   }
350
351   @SuppressWarnings("UseOfSystemOutOrSystemErr")
352   private static void logTiming(ClassPath path, long started, String msg) {
353     if (!ourLogTiming) return;
354
355     long time = System.nanoTime() - started;
356     ourTotalTime += time;
357     ++ourTotalRequests;
358     if (time > 10000000L) {
359       System.out.println((time / 1000000) + " ms for " + msg);
360     }
361     if (ourTotalRequests % 1000 == 0) {
362       System.out.println(path.toString() + ", requests:" + ourTotalRequests + ", time:" + (ourTotalTime / 1000000) + "ms");
363     }
364   }
365 }