/*
* Copyright 2000-2009 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.util.ShutDownTracker;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.Stack;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import sun.misc.Resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
class ClassPath {
private final Stack<URL> myUrls = new Stack<URL>();
private final ArrayList<Loader> myLoaders = new ArrayList<Loader>();
private final HashMap<URL,Loader> myLoadersMap = new HashMap<URL, Loader>();
private final ClasspathCache myCache = new ClasspathCache();
@NonNls private static final String FILE_PROTOCOL = "file";
private static final boolean myDebugTime = false;
private static final boolean ourDumpOrder = "true".equals(System.getProperty("idea.dump.order"));
// private static final boolean ourPreloadClasses = "true".equals(System.getProperty("idea.preload.classes"));
private final boolean myCanLockJars;
private final boolean myCanUseCache;
private static final long NS_THRESHOLD = 10000000L;
private static PrintStream ourOrder;
private static long ourOrderSize;
private final static Set<String> ourOrderedUrls = new HashSet<String>();
private static final String HOME = FileUtil.toSystemIndependentName(PathManager.getHomePath());
private final boolean myAcceptUnescapedUrls;
private static synchronized void printOrder(Loader loader, String url, Resource resource) {
if (!ourOrderedUrls.add(url)) return;
try {
ourOrderSize += resource.getContentLength();
}
catch (IOException e) {
System.out.println(e);
}
if (ourOrder == null) {
final File orderFile = new File(PathManager.getBinPath() + File.separator + "order.txt");
try {
if (!FileUtil.ensureCanCreateFile(orderFile)) return;
ourOrder = new PrintStream(new FileOutputStream(orderFile, true));
ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
public void run() {
ourOrder.close();
System.out.println(ourOrderSize);
}
});
}
catch (IOException e) {
return;
}
}
if (ourOrder != null) {
String jarURL = FileUtil.toSystemIndependentName(loader.getBaseURL().getFile());
jarURL = StringUtil.trimStart(jarURL, "file:/");
if (jarURL.startsWith(HOME)) {
jarURL = jarURL.replaceFirst(HOME, "");
jarURL = StringUtil.trimEnd(jarURL, "!/");
ourOrder.println(url + ":" + jarURL);
}
}
}
public ClassPath(URL[] urls, boolean canLockJars, boolean canUseCache) {
this(urls, canLockJars, canUseCache, false);
}
public ClassPath(URL[] urls, boolean canLockJars, boolean canUseCache, boolean acceptUnescapedUrls) {
myCanLockJars = canLockJars;
myCanUseCache = canUseCache;
myAcceptUnescapedUrls = acceptUnescapedUrls;
push(urls);
}
void addURL(URL url) {
push(new URL[]{url});
}
@Nullable
public Resource getResource(String s, boolean flag) {
final long started = myDebugTime ? System.nanoTime():0;
try {
int i;
if (myCanUseCache) {
final List<Loader> loaders = myCache.getLoaders(s);
for (Loader loader : loaders) {
final Resource resource = loader.getResource(s, flag);
if (resource != null) {
if (ourDumpOrder) {
printOrder(loader, s, resource);
}
return resource;
}
}
synchronized (myUrls) {
if (myUrls.isEmpty()) return null;
}
i = myLoaders.size();
}
else {
i = 0;
}
for (Loader loader; (loader = getLoader(i)) != null; i++) {
Resource resource = loader.getResource(s, flag);
if (resource != null) {
return resource;
}
}
return null;
}
finally {
long doneFor = myDebugTime ? System.nanoTime() - started:0;
if (doneFor > NS_THRESHOLD) {
System.out.println((doneFor/1000000) + " ms for getResource:"+s+", flag:"+flag);
}
}
}
public Enumeration<URL> getResources(final String name, final boolean check) {
return new MyEnumeration(name, check);
}
@Nullable
private synchronized Loader getLoader(int i) {
while (myLoaders.size() < i + 1) {
URL url;
synchronized (myUrls) {
if (myUrls.empty()) return null;
url = myUrls.pop();
}
if (myLoadersMap.containsKey(url)) continue;
Loader loader;
try {
loader = getLoader(url);
if (loader == null) continue;
}
catch (IOException ioexception) {
continue;
}
myLoaders.add(loader);
myLoadersMap.put(url, loader);
}
return myLoaders.get(i);
}
@Nullable
private Loader getLoader(final URL url) throws IOException {
String s;
if (myAcceptUnescapedUrls) {
s = url.getFile();
} else {
try {
s = url.toURI().getSchemeSpecificPart();
} catch (URISyntaxException thisShouldNotHappen) {
thisShouldNotHappen.printStackTrace();
s = url.getFile();
}
}
Loader loader = null;
if (s != null && new File(s).isDirectory()) {
if (FILE_PROTOCOL.equals(url.getProtocol())) {
loader = new FileLoader(url);
}
}
else {
JarLoader jarLoader = new JarLoader(url, myCanLockJars);
jarLoader.preLoadClasses();
loader = jarLoader;
}
if (loader != null && myCanUseCache) {
try {
loader.buildCache(myCache);
}
catch (Throwable e) {
// TODO: log can't create loader
}
}
return loader;
}
private void push(URL[] urls) {
synchronized (myUrls) {
for (int i = urls.length - 1; i >= 0; i--) myUrls.push(urls[i]);
}
}
private class MyEnumeration implements Enumeration<URL> {
private int myIndex = 0;
private Resource myRes = null;
private final String myName;
private final boolean myCheck;
public MyEnumeration(String name, boolean check) {
myName = name;
myCheck = check;
}
private boolean next() {
if (myRes != null) return true;
Loader loader;
while ((loader = getLoader(myIndex++)) != null) {
myRes = loader.getResource(myName, myCheck);
if (myRes != null) return true;
}
return false;
}
public boolean hasMoreElements() {
return next();
}
public URL nextElement() {
if (!next()) {
throw new NoSuchElementException();
}
else {
Resource resource = myRes;
myRes = null;
return resource.getURL();
}
}
}
}