blob: 2cd57a04839ab350f383a295c5aceb9940a9aeb7 [file] [log] [blame]
/*
* 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.diagnostic.Logger;
import com.intellij.openapi.util.text.StringHash;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.BloomFilterBase;
import com.intellij.util.SmartList;
import com.intellij.util.containers.HashMap;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import gnu.trove.TIntHashSet;
import gnu.trove.TIntObjectHashMap;
import org.jetbrains.annotations.Nullable;
import sun.misc.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author max
*/
public class ClasspathCache {
static final Logger LOG = Logger.getInstance(ClasspathCache.class);
static final boolean doDebug = LOG.isDebugEnabled();
private final DebugInfo myDebugInfo;
private final TIntObjectHashMap<Object> myResourcePackagesCache = new TIntObjectHashMap<Object>();
private final TIntObjectHashMap<Object> myClassPackagesCache = new TIntObjectHashMap<Object>();
private Map<String, Set<Loader>> myResources2LoadersTempMap = new THashMap<String, Set<Loader>>();
private static final double PROBABILITY = 0.005d;
private Name2LoaderFilter myNameFilter;
private boolean myTempMapMode = true;
public ClasspathCache() {
myDebugInfo = doDebug ? new DebugInfo() : new NullDebugInfo();
}
public void addResourceEntry(String resourcePath, Loader loader) {
myDebugInfo.addResourceEntry(resourcePath, loader);
String packageName = getPackageName(resourcePath);
TIntObjectHashMap<Object> map = resourcePath.endsWith(UrlClassLoader.CLASS_EXTENSION) ?
myClassPackagesCache : myResourcePackagesCache;
int hash = packageName.hashCode();
Object o = map.get(hash);
if (o == null) map.put(hash, loader);
else if (o instanceof Loader) {
if (o != loader) map.put(hash, new Loader [] {(Loader)o, loader});
} else {
Loader[] loadersArray = (Loader[])o;
for(Loader l:loadersArray) {
if (l == loader) return;
}
map.put(hash, ArrayUtil.append(loadersArray, loader));
}
}
abstract static class LoaderIterator <ResultType, ParameterType, ParameterType2> {
@Nullable
abstract ResultType process(Loader loader, ParameterType parameter, ParameterType2 parameter2);
}
@Nullable <ResultType, ParameterType, ParameterType2> ResultType iterateLoaders(
String resourcePath,
LoaderIterator<ResultType, ParameterType, ParameterType2> iterator,
ParameterType parameter,
ParameterType2 parameter2) {
TIntObjectHashMap<Object> map = resourcePath.endsWith(UrlClassLoader.CLASS_EXTENSION) ?
myClassPackagesCache : myResourcePackagesCache;
String packageName = getPackageName(resourcePath);
int hash = packageName.hashCode();
Object o = map.get(hash);
myDebugInfo.checkLoadersCount(resourcePath, o);
if (o == null) return null;
if (o instanceof Loader) return iterator.process((Loader)o, parameter, parameter2);
Loader[] loaders = (Loader[])o;
for(Loader l:loaders) {
ResultType result = iterator.process(l, parameter, parameter2);
if (result != null) return result;
}
return null;
}
private static String getPackageName(String resourcePath) {
final int idx = resourcePath.lastIndexOf('/');
return idx > 0 ? resourcePath.substring(0, idx) : "";
}
private int registeredBeforeClose;
private int registeredAfterClose;
public void addNameEntry(String name, Loader loader) {
name = transformName(name);
myDebugInfo.addNameEntry(name, loader);
if (myTempMapMode) {
Set<Loader> loaders = myResources2LoadersTempMap.get(name);
if (loaders == null) myResources2LoadersTempMap.put(name, loaders = new THashSet<Loader>());
boolean added = loaders.add(loader);
if (doDebug && added) ++registeredBeforeClose;
} else {
if (doDebug) {
if (!myNameFilter.maybeContains(name, loader)) ++registeredAfterClose;
}
myNameFilter.add(name, loader);
}
}
public boolean loaderHasName(String name, String shortName, Loader loader) {
if (StringUtil.isEmpty(name)) return true;
boolean result;
if (myTempMapMode) {
++requests;
Set<Loader> loaders = myResources2LoadersTempMap.get(shortName);
result = loaders != null && loaders.contains(loader);
if (!result) ++hits;
if (doDebug) {
boolean result2 = myDebugInfo.loaderHasName(shortName, loader);
if (result2 != result) {
++diffs3;
}
Resource resource = loader.getResource(name, true);
if (resource != null && !result || resource == null && result) {
++falseHits;
}
}
if (requests % 1000 == 0 && doDebug) {
LOG.debug("Avoided disk hits: " + hits + " from " + requests + ", false hits:" + falseHits + ", bitmap diffs:" + diffs3);
}
}
else {
++requests2;
result = myNameFilter.maybeContains(shortName, loader);
if (!result) ++hits2;
if (doDebug) {
boolean result2 = myDebugInfo.loaderHasName(shortName, loader);
if (result2 != result) {
++diffs2;
}
Set<Loader> loaders = myResources2LoadersTempMap.get(shortName);
if (result != (loaders != null && loaders.contains(loader))) {
++diffs;
}
Resource resource = loader.getResource(name, true);
if (resource == null && result) {
++falseHits2;
}
if (resource != null && !result) {
++falseHits2;
}
}
if (requests2 % 1000 == 0 && doDebug) {
LOG.debug("Avoided disk hits2: " + hits2 + " from " + requests2 + "," + diffs + ", false hits:" + falseHits2 + ", bitmap diffs:" + diffs2);
}
}
return result;
}
static String transformName(String name) {
if (name.endsWith("/")) {
name = name.substring(0, name.length() - 1);
}
name = name.substring(name.lastIndexOf('/') + 1);
if (name.endsWith(UrlClassLoader.CLASS_EXTENSION)) {
String name1 = name;
int $ = name1.indexOf('$');
if ($ != -1) name1 = name1.substring(0, $);
else {
int index = name1.lastIndexOf('.');
if (index >= 0) name1 = name1.substring(0, index);
}
name = name1;
}
return name;
}
private static int hits;
private static int requests;
private static int falseHits;
private static int requests2;
private static int hits2;
private static int falseHits2;
private static int diffs;
private static int diffs2;
private static int diffs3;
void nameSymbolsLoaded() {
if (!myTempMapMode) {
if (doDebug && registeredAfterClose > 0) {
LOG.debug("Registered number of classes after close " + registeredAfterClose + " " + toString());
}
return;
}
if (doDebug) {
LOG.debug("Registered number of classes before classes " + registeredBeforeClose + " " + toString());
}
myTempMapMode = false;
int nBits = 0;
int uniques = 0;
for(Map.Entry<String, Set<Loader>> e:myResources2LoadersTempMap.entrySet()) {
int size = e.getValue().size();
if (size == 1) {
++uniques;
}
nBits += size;
}
if (nBits > 20000) {
nBits += (int)(nBits * 0.03d); // allow some growth for Idea main loader
}
myNameFilter = new Name2LoaderFilter(nBits, PROBABILITY);
for(Map.Entry<String, Set<Loader>> e:myResources2LoadersTempMap.entrySet()) {
final String name = e.getKey();
for(Loader loader: e.getValue()) {
myNameFilter.add(name, loader);
}
}
if (!doDebug) {
myResources2LoadersTempMap = null;
}
}
private static class Name2LoaderFilter extends BloomFilterBase {
private static final int SEED = 31;
Name2LoaderFilter(int nBits, double probability) {
super(nBits, probability);
}
private boolean maybeContains(String name, Loader loader) {
int hash = hashFromNameAndLoader(name, loader, StringHash.murmur(name, SEED));
int hash2 = hashFromNameAndLoader(name, loader, hash);
return maybeContains(hash, hash2);
}
private void add(String name, Loader loader) {
int hash = hashFromNameAndLoader(name, loader, StringHash.murmur(name, SEED));
int hash2 = hashFromNameAndLoader(name, loader, hash);
addIt(hash, hash2);
}
private static int hashFromNameAndLoader(String name, Loader loader, int n) {
int hash = StringHash.murmur(name, n);
int i = loader.getIndex();
while (i > 0) {
hash = hash * n + ((i % 10) + '0');
i /= 10;
}
return hash;
}
}
private static class DebugInfo {
private final HashMap<String, List<Loader>> myClassPackagesCache = new HashMap<String, List<Loader>>();
private final HashMap<String, List<Loader>> myResourcePackagesCache = new HashMap<String, List<Loader>>();
private final TIntHashSet myResourceIndex = new TIntHashSet();
private List<Loader> getLoaders(String resourcePath) {
boolean isClassFile = resourcePath.endsWith(UrlClassLoader.CLASS_EXTENSION);
final int idx = resourcePath.lastIndexOf('/');
String packageName = idx > 0 ? resourcePath.substring(0, idx) : "";
Map<String, List<Loader>> map = isClassFile ? myClassPackagesCache : myResourcePackagesCache;
List<Loader> list = map.get(packageName);
if (list == null) {
list = new SmartList<Loader>();
map.put(packageName, list);
}
return list;
}
protected void addResourceEntry(String resourcePath, Loader loader) {
final List<Loader> loaders = getLoaders(resourcePath);
if (!loaders.contains(loader)) { // TODO Make linked hash set instead?
loaders.add(loader);
}
}
protected void addNameEntry(String name, Loader loader) {
int hash = hashFromNameAndLoader(name, loader);
myResourceIndex.add(hash);
}
protected static int hashFromNameAndLoader(String name, Loader loader) {
int hash = name.hashCode();
int i = loader.getIndex();
while(i > 0) {
hash = hash * 31 + ((i % 10) + '0');
i /= 10;
}
return hash;
}
public void checkLoadersCount(String resourcePath, Object o) {
List<Loader> loaders1 = getLoaders(resourcePath);
if (o == null && !loaders1.isEmpty() ||
o instanceof Loader && loaders1.size() != 1 ||
o instanceof Loader[] && loaders1.size() != ((Loader[])o).length
) {
assert false;
}
}
protected boolean loaderHasName(String name, Loader loader) {
return myResourceIndex.contains(hashFromNameAndLoader(name, loader));
}
}
private static class NullDebugInfo extends DebugInfo {
@Override
public void checkLoadersCount(String resourcePath, Object o) {
}
@Override
protected void addResourceEntry(String resourcePath, Loader loader) {
}
@Override
protected void addNameEntry(String name, Loader loader) {
}
@Override
protected boolean loaderHasName(String name, Loader loader) {
return false;
}
}
}