use VFS for caching, IDEA-97670, reduced memory, removed cached data duplication

This commit is contained in:
Alexey Kudravtsev
2013-01-15 13:57:05 +04:00
parent 444c493c98
commit ead4e15edd
8 changed files with 505 additions and 169 deletions
@@ -22,6 +22,7 @@ import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.ide.highlighter.XmlFileType;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.CommandProcessor;
@@ -54,6 +55,7 @@ import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
@@ -64,9 +66,11 @@ import com.intellij.psi.xml.XmlDocument;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.OptionsMessageDialog;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -77,6 +81,7 @@ import java.awt.event.ActionEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* @author anna
@@ -144,16 +149,17 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM
chooseRootAndAnnotateExternally(listOwner, annotationFQName, fromFile, project, packageName, roots, value);
}
else {
if (ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isHeadlessEnvironment()) {
Application application = ApplicationManager.getApplication();
if (application.isUnitTestMode() || application.isHeadlessEnvironment()) {
notifyAfterAnnotationChanging(listOwner, annotationFQName, false);
return;
}
SwingUtilities.invokeLater(new Runnable() {
application.invokeLater(new Runnable() {
@Override
public void run() {
setupRootAndAnnotateExternally(entry, project, listOwner, annotationFQName, fromFile, packageName, value);
}
});
}, project.getDisposed());
}
break;
}
@@ -204,12 +210,8 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM
else {
final XmlFile annotationsXml = createAnnotationsXml(newRoot, packageName);
if (annotationsXml != null) {
final List<PsiFile> createdFiles = new ArrayList<PsiFile>();
createdFiles.add(annotationsXml);
String fqn = getFQN(packageName, fromFile);
if (fqn != null) {
myExternalAnnotations.put(fqn, createdFiles);
}
List<PsiFile> createdFiles = new SmartList<PsiFile>(annotationsXml);
cacheExternalAnnotations(packageName, fromFile, createdFiles);
}
annotateExternally(listOwner, annotationFQName, annotationsXml, fromFile, value);
}
@@ -271,12 +273,12 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM
@NotNull
private static VirtualFile[] filterByReadOnliness(@NotNull VirtualFile[] files) {
List<VirtualFile> result = new ArrayList<VirtualFile>();
for (VirtualFile file : files) {
if (file.isInLocalFileSystem()) {
result.add(file);
List<VirtualFile> result = ContainerUtil.filter(files, new Condition<VirtualFile>() {
@Override
public boolean value(VirtualFile file) {
return file.isInLocalFileSystem();
}
}
});
return VfsUtilCore.toVirtualFileArray(result);
}
@@ -295,7 +297,7 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM
return;
}
final List<PsiFile> annotationFiles = xmlFiles == null ? new ArrayList<PsiFile>() : new ArrayList<PsiFile>(xmlFiles);
final Set<PsiFile> annotationFiles = xmlFiles == null ? new THashSet<PsiFile>() : new THashSet<PsiFile>(xmlFiles);
new WriteCommandAction(project) {
@Override
@@ -310,7 +312,7 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM
}
else {
annotationFiles.add(newXml);
myExternalAnnotations.put(getFQN(packageName, fromFile), annotationFiles);
cacheExternalAnnotations(packageName, fromFile, new SmartList<PsiFile>(annotationFiles));
annotateExternally(listOwner, annotationFQName, newXml, fromFile, value);
}
}
@@ -507,7 +509,7 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM
sdkModificator.addRoot(vFile, AnnotationOrderRootType.getInstance());
sdkModificator.commitChanges();
}
myExternalAnnotations.clear();
dropCache();
}
private void annotateExternally(@NotNull final PsiModifierListOwner listOwner,
@@ -528,16 +530,17 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM
final XmlTag rootTag = document.getRootTag();
final String externalName = getExternalName(listOwner, false);
if (rootTag != null) {
for (XmlTag tag : rootTag.getSubTags()) {
if (Comparing.strEqual(StringUtil.unescapeXml(tag.getAttributeValue("name")), externalName)) {
for (XmlTag annTag : tag.getSubTags()) {
if (Comparing.strEqual(annTag.getAttributeValue("name"), annotationFQName)) {
annTag.delete();
for (XmlTag item : rootTag.getSubTags()) {
if (Comparing.strEqual(StringUtil.unescapeXml(item.getAttributeValue("name")), externalName)) {
for (XmlTag annotation : item.getSubTags()) {
if (Comparing.strEqual(annotation.getAttributeValue("name"), annotationFQName)) {
annotation.delete();
break;
}
}
tag.add(XmlElementFactory.getInstance(myPsiManager.getProject()).createTagFromText(
createAnnotationTag(annotationFQName, values)));
XmlTag newTag = XmlElementFactory.getInstance(myPsiManager.getProject()).createTagFromText(
createAnnotationTag(annotationFQName, values));
item.add(newTag);
commitChanges(xmlFile);
notifyAfterAnnotationChanging(listOwner, annotationFQName, true);
return;
@@ -52,19 +52,20 @@ public class ReadableExternalAnnotationsManager extends BaseExternalAnnotationsM
return myHasAnyAnnotationsRoots == ThreeState.YES;
}
@NotNull
@Override
@NotNull
protected List<VirtualFile> getExternalAnnotationsRoots(@NotNull VirtualFile libraryFile) {
final List<OrderEntry> entries = ProjectRootManager.getInstance(myPsiManager.getProject()).getFileIndex().getOrderEntriesForFile(
libraryFile);
ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myPsiManager.getProject()).getFileIndex();
List<OrderEntry> entries = fileIndex.getOrderEntriesForFile(libraryFile);
List<VirtualFile> result = new ArrayList<VirtualFile>();
VirtualFileManager vfManager = VirtualFileManager.getInstance();
for (OrderEntry entry : entries) {
if (entry instanceof ModuleOrderEntry) {
continue;
}
final String[] externalUrls = AnnotationOrderRootType.getUrls(entry);
for (String url : externalUrls) {
VirtualFile root = VirtualFileManager.getInstance().findFileByUrl(url);
VirtualFile root = vfManager.findFileByUrl(url);
if (root != null) {
result.add(root);
}
@@ -15,21 +15,26 @@
*/
package com.intellij.codeInsight;
import com.intellij.lang.PsiBuilder;
import com.intellij.lang.java.parser.JavaParser;
import com.intellij.lang.java.parser.JavaParserUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.*;
import com.intellij.psi.util.PsiFormatUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ConcurrentSoftHashMap;
import com.intellij.util.containers.ConcurrentSoftValueHashMap;
import com.intellij.util.containers.MultiMap;
import gnu.trove.THashMap;
import com.intellij.util.SmartList;
import com.intellij.util.containers.*;
import gnu.trove.THashSet;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
@@ -40,11 +45,11 @@ import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
public abstract class BaseExternalAnnotationsManager extends ExternalAnnotationsManager{
private static final Logger LOG = Logger.getInstance("#" + BaseExternalAnnotationsManager.class.getName());
@NotNull private static final List<PsiFile> NULL = new ArrayList<PsiFile>();
@NotNull protected final ConcurrentMap<String, List<PsiFile>>
myExternalAnnotations = new ConcurrentSoftValueHashMap<String, List<PsiFile>>();
public abstract class BaseExternalAnnotationsManager extends ExternalAnnotationsManager {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.BaseExternalAnnotationsManager");
@NotNull private static final List<PsiFile> NULL_LIST = new ArrayList<PsiFile>(0);
@NotNull
private final ConcurrentMap<String, List<PsiFile>> myExternalAnnotations = new ConcurrentSoftValueHashMap<String, List<PsiFile>>(10, 0.75f, 2);
protected final PsiManager myPsiManager;
public BaseExternalAnnotationsManager(final PsiManager psiManager) {
@@ -57,7 +62,7 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
}
@Nullable
protected static String getFQN(@NotNull String packageName, @NotNull PsiFile psiFile) {
private static String getFQN(@NotNull String packageName, @NotNull PsiFile psiFile) {
VirtualFile virtualFile = psiFile.getVirtualFile();
if (virtualFile == null) return null;
return StringUtil.getQualifiedName(packageName, virtualFile.getNameWithoutExtension());
@@ -78,16 +83,26 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
}
final int idx = externalName.indexOf('(');
if (idx == -1) return externalName;
StringBuilder buf = new StringBuilder();
StringBuilder buf = new StringBuilder(externalName.length());
int rightIdx = externalName.indexOf(')');
String[] params = externalName.substring(idx + 1, rightIdx).split(",");
buf.append(externalName.substring(0, idx + 1));
buf.append(externalName, 0, idx + 1);
for (String param : params) {
param = param.trim();
final int spaceIdx = param.indexOf(' ');
buf.append(spaceIdx > -1 ? param.substring(0, spaceIdx) : param).append(", ");
int spaceIdx = param.indexOf(' ');
if (spaceIdx > -1) {
buf.append(param, 0, spaceIdx);
}
else {
buf.append(param);
}
buf.append(", ");
}
return StringUtil.trimEnd(buf.toString(), ", ") + externalName.substring(rightIdx);
if (StringUtil.endsWith(buf, ", ")) {
buf.delete(buf.length() - ", ".length(), buf.length());
}
buf.append(externalName, rightIdx, externalName.length());
return buf.toString();
}
protected abstract boolean hasAnyAnnotationsRoots();
@@ -95,72 +110,152 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
@Override
@Nullable
public PsiAnnotation findExternalAnnotation(@NotNull final PsiModifierListOwner listOwner, @NotNull final String annotationFQN) {
return collectExternalAnnotations(listOwner).get(annotationFQN);
List<AnnotationData> list = collectExternalAnnotations(listOwner);
AnnotationData data = findByFQN(list, annotationFQN);
return data == null ? null : data.getAnnotation();
}
@Override
public boolean isExternalAnnotationWritable(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFQN) {
public boolean isExternalAnnotationWritable(@NotNull PsiModifierListOwner listOwner, @NotNull final String annotationFQN) {
// note that this method doesn't cache it's result
Map<String, PsiAnnotation> map = doCollect(listOwner, true);
return map.containsKey(annotationFQN);
List<AnnotationData> map = doCollect(listOwner, true);
return findByFQN(map, annotationFQN) != null;
}
private static AnnotationData findByFQN(@NotNull List<AnnotationData> map, @NotNull final String annotationFQN) {
return ContainerUtil.find(map, new Condition<AnnotationData>() {
@Override
public boolean value(AnnotationData data) {
return data.annotationClassFqName.equals(annotationFQN);
}
});
}
@Override
@Nullable
public PsiAnnotation[] findExternalAnnotations(@NotNull final PsiModifierListOwner listOwner) {
final Map<String, PsiAnnotation> result = collectExternalAnnotations(listOwner);
return result.isEmpty() ? null : result.values().toArray(new PsiAnnotation[result.size()]);
final List<AnnotationData> result = collectExternalAnnotations(listOwner);
return result.isEmpty() ? null : ContainerUtil.map2Array(result, PsiAnnotation.EMPTY_ARRAY, new Function<AnnotationData, PsiAnnotation>() {
@Override
public PsiAnnotation fun(AnnotationData data) {
return data.getAnnotation();
}
});
}
private final ConcurrentMap<PsiModifierListOwner, Map<String, PsiAnnotation>> cache = new ConcurrentSoftHashMap<PsiModifierListOwner, Map<String, PsiAnnotation>>();
private static final List<AnnotationData> NO_DATA = new ArrayList<AnnotationData>(1);
private final ConcurrentMostlySingularMultiMap<PsiModifierListOwner, AnnotationData> cache = new ConcurrentMostlySingularMultiMap<PsiModifierListOwner, AnnotationData>();
private final CharTableImpl charTable = new CharTableImpl();
@NotNull
private Map<String, PsiAnnotation> collectExternalAnnotations(@NotNull final PsiModifierListOwner listOwner) {
if (!hasAnyAnnotationsRoots()) return Collections.emptyMap();
private List<AnnotationData> collectExternalAnnotations(@NotNull PsiModifierListOwner listOwner) {
if (!hasAnyAnnotationsRoots()) return Collections.emptyList();
Map<String, PsiAnnotation> map = cache.get(listOwner);
if (map == null) {
map = doCollect(listOwner, false);
map = ConcurrencyUtil.cacheOrGet(cache, listOwner, map);
List<AnnotationData> cached;
while (true) {
cached = (List<AnnotationData>)cache.get(listOwner);
if (cached == NO_DATA || !cached.isEmpty()) return cached;
List<AnnotationData> computed = doCollect(listOwner, false);
if (cache.replace(listOwner, cached, computed)) {
cached = computed;
break;
}
}
return map;
return cached;
}
private final ConcurrentMap<PsiFile, Pair<MultiMap<String, AnnotationData>, Long>> annotationsFileToDataAndModificationStamp = new ConcurrentSoftHashMap<PsiFile, Pair<MultiMap<String, AnnotationData>, Long>>();
private final Map<AnnotationData, AnnotationData> annotationDataCache = new WeakKeyWeakValueHashMap<AnnotationData, AnnotationData>();
@NotNull
private MultiMap<String, AnnotationData> getDataFromFile(@NotNull PsiFile file) {
Pair<MultiMap<String, AnnotationData>, Long> cached = annotationsFileToDataAndModificationStamp.get(file);
private AnnotationData internAnnotationData(@NotNull AnnotationData data) {
synchronized (annotationDataCache) {
AnnotationData interned = annotationDataCache.get(data);
if (interned == null) {
annotationDataCache.put(data, data);
interned = data;
}
return interned;
}
}
private final ConcurrentMap<PsiFile, Pair<MostlySingularMultiMap<String, AnnotationData>, Long>> annotationFileToDataAndModStamp = new ConcurrentSoftHashMap<PsiFile, Pair<MostlySingularMultiMap<String, AnnotationData>, Long>>();
@NotNull
private MostlySingularMultiMap<String, AnnotationData> getDataFromFile(@NotNull PsiFile file) {
Pair<MostlySingularMultiMap<String, AnnotationData>, Long> cached = annotationFileToDataAndModStamp.get(file);
if (cached != null && cached.getSecond() == file.getModificationStamp()) {
return cached.getFirst();
}
MultiMap<String, AnnotationData> data = new MultiMap<String, AnnotationData>();
MostlySingularMultiMap<String, AnnotationData> data = new MostlySingularMultiMap<String, AnnotationData>();
try {
VirtualFile virtualFile = file.getVirtualFile();
if (virtualFile != null) {
Document document = JDOMUtil.loadDocument(escapeAttributes(StreamUtil.readText(virtualFile.getInputStream())));
Element rootElement = document.getRootElement();
if (rootElement != null) {
//noinspection unchecked
for (Element element : (List<Element>) rootElement.getChildren()) {
String ownerName = element.getAttributeValue("name");
if (ownerName == null) continue;
//noinspection unchecked
for (Element annotationElement : (List<Element>) element.getChildren()) {
String annotationFQN = annotationElement.getAttributeValue("name");
if (StringUtil.isEmpty(annotationFQN)) continue;
StringBuilder buf = new StringBuilder();
//noinspection unchecked
for (Element annotationParameter : (List<Element>) annotationElement.getChildren()) {
buf.append(",");
String nameValue = annotationParameter.getAttributeValue("name");
if (nameValue != null) {
buf.append(nameValue).append("=");
}
buf.append(annotationParameter.getAttributeValue("val"));
}
String annotationText = "@" + annotationFQN + (buf.length() > 0 ? "(" + StringUtil.trimStart(buf.toString(), ",") + ")" : "");
data.putValue(ownerName, new AnnotationData(annotationFQN, annotationText));
}
Document document = JDOMUtil.loadDocument(escapeAttributes(file.getText()));
Element rootElement = document.getRootElement();
if (rootElement != null) {
boolean sorted = true;
boolean modified = false;
String prevItemName = null;
//noinspection unchecked
for (Element element : (List<Element>) rootElement.getChildren("item")) {
String externalName = element.getAttributeValue("name");
if (externalName == null) {
element.detach();
modified = true;
continue;
}
if (prevItemName != null && prevItemName.compareTo(externalName) > 0) {
sorted = false;
}
prevItemName = externalName;
//noinspection unchecked
for (Element annotationElement : (List<Element>) element.getChildren("annotation")) {
String annotationFQN = annotationElement.getAttributeValue("name");
if (StringUtil.isEmpty(annotationFQN)) continue;
annotationFQN = intern(annotationFQN);
//noinspection unchecked
List<Element> children = (List<Element>)annotationElement.getChildren();
StringBuilder buf = new StringBuilder(children.size() * "name=value,".length()); // just guess
for (Element annotationParameter : children) {
if (buf.length() != 0) {
buf.append(",");
}
String nameValue = annotationParameter.getAttributeValue("name");
if (nameValue != null) {
buf.append(nameValue);
buf.append("=");
}
buf.append(annotationParameter.getAttributeValue("val"));
}
String annotationParameters = buf.length() == 0 ? "" : intern(buf.toString());
for (AnnotationData existingData : data.get(externalName)) {
if (existingData.annotationClassFqName.equals(annotationFQN)) {
LOG.error("Duplicate annotation '" + annotationFQN+"' for signature: '" + externalName + "' in the file " + file.getVirtualFile().getPresentableUrl());
}
}
AnnotationData annData = internAnnotationData(new AnnotationData(annotationFQN, annotationParameters));
data.add(externalName, annData);
}
}
if (!sorted) {
modified = true;
List<Element> items = new ArrayList<Element>(rootElement.getChildren("item"));
rootElement.removeChildren("item");
Collections.sort(items, new Comparator<Element>() {
@Override
public int compare(Element item1, Element item2) {
String externalName1 = item1.getAttributeValue("name");
String externalName2 = item2.getAttributeValue("name");
return externalName1.compareTo(externalName2);
}
});
for (Element item : items) {
rootElement.addContent(item);
}
}
VirtualFile virtualFile = file.getVirtualFile();
if (modified && virtualFile.isInLocalFileSystem() && virtualFile.isWritable()) {
String lineSeparator = FileDocumentManager.getInstance().getLineSeparator(virtualFile, file.getProject());
JDOMUtil.writeDocument(document, virtualFile.getPath(), lineSeparator);
}
}
}
@@ -171,58 +266,74 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
LOG.error(e);
}
if (data.isEmpty()) {
data = MultiMap.emptyInstance();
data = MostlySingularMultiMap.emptyMap();
}
Pair<MultiMap<String, AnnotationData>, Long> pair = Pair.create(data, file.getModificationStamp());
pair = ConcurrencyUtil.cacheOrGet(annotationsFileToDataAndModificationStamp, file, pair);
data = pair.first;
data.compact();
Pair<MostlySingularMultiMap<String, AnnotationData>, Long> pair = Pair.create(data, file.getModificationStamp());
annotationFileToDataAndModStamp.put(file, pair);
return data;
}
@NotNull
private Map<String, PsiAnnotation> doCollect(@NotNull PsiModifierListOwner listOwner, boolean onlyWritable) {
private String intern(@NotNull String annotationFQN) {
return charTable.doIntern(annotationFQN).toString();
}
@NotNull
private List<AnnotationData> doCollect(@NotNull PsiModifierListOwner listOwner, boolean onlyWritable) {
final List<PsiFile> files = findExternalAnnotationsFiles(listOwner);
if (files == null) {
return Collections.emptyMap();
return NO_DATA;
}
Map<String, PsiAnnotation> result = new THashMap<String, PsiAnnotation>();
SmartList<AnnotationData> result = new SmartList<AnnotationData>();
String externalName = getExternalName(listOwner, false);
if (externalName == null) return NO_DATA;
String oldExternalName = getNormalizedExternalName(listOwner);
final PsiElementFactory factory = JavaPsiFacade.getInstance(myPsiManager.getProject()).getElementFactory();
for (PsiFile file : files) {
if (!file.isValid()) continue;
if (onlyWritable && !file.isWritable()) continue;
final MultiMap<String, AnnotationData> fileData = getDataFromFile(file);
MostlySingularMultiMap<String, AnnotationData> fileData = getDataFromFile(file);
collectAnnotations(result, fileData.get(externalName), factory);
collectAnnotations(result, fileData.get(oldExternalName), factory);
Collection<AnnotationData> data = (Collection<AnnotationData>)fileData.get(externalName);
for (AnnotationData ad : data) {
if (result.contains(ad)) {
LOG.error("Duplicate signature:\n" + externalName + "; in " + toVirtualFiles(files));
}
else {
result.add(ad);
}
}
if (oldExternalName != null && !externalName.equals(oldExternalName)) {
Collection<AnnotationData> oldCollection = (Collection<AnnotationData>)fileData.get(oldExternalName);
for (AnnotationData ad : oldCollection) {
if (result.contains(ad)) {
LOG.error("Duplicate signature o:\n" + oldExternalName + "; in " + toVirtualFiles(files));
}
else {
result.add(ad);
}
}
}
}
if (result.isEmpty()) {
return NO_DATA;
}
result.trimToSize();
return result;
}
private static void collectAnnotations(Map<String, PsiAnnotation> result,
Collection<AnnotationData> dataCollection,
PsiElementFactory factory) {
for (AnnotationData annotationData : dataCollection) {
// don't add annotation, if there already is one with this FQ name
if (result.containsKey(annotationData.annotationClassFqName)) continue;
try {
PsiAnnotation annotation = factory.createAnnotationFromText(annotationData.annotationText, null);
result.put(annotationData.annotationClassFqName, annotation);
static List<VirtualFile> toVirtualFiles(List<PsiFile> files) {
return ContainerUtil.map(files, new Function<PsiFile, VirtualFile>() {
@Override
public VirtualFile fun(PsiFile file) {
return file.getVirtualFile();
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
});
}
@NotNull
protected abstract List<VirtualFile> getExternalAnnotationsRoots(@NotNull VirtualFile libraryFile);
@Override
@Nullable
public List<PsiFile> findExternalAnnotationsFiles(@NotNull PsiModifierListOwner listOwner) {
@@ -236,7 +347,7 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
String fqn = getFQN(packageName, containingFile);
if (fqn == null) return null;
final List<PsiFile> files = myExternalAnnotations.get(fqn);
if (files == NULL) return null;
if (files == NULL_LIST) return null;
if (files != null) {
boolean allValid = true;
for (PsiFile file : files) {
@@ -251,7 +362,7 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
return null;
}
ArrayList<PsiFile> possibleAnnotationsXmls = new ArrayList<PsiFile>();
Set<PsiFile> possibleAnnotationsXmls = new THashSet<PsiFile>();
for (VirtualFile root : getExternalAnnotationsRoots(virtualFile)) {
final VirtualFile ext = root.findFileByRelativePath(packageName.replace(".", "/") + "/" + ANNOTATIONS_XML);
if (ext == null) continue;
@@ -259,10 +370,15 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
if (psiFile == null) continue;
possibleAnnotationsXmls.add(psiFile);
}
possibleAnnotationsXmls.trimToSize();
if (!possibleAnnotationsXmls.isEmpty()) {
List<PsiFile> result;
if (possibleAnnotationsXmls.isEmpty()) {
myExternalAnnotations.put(fqn, NULL_LIST);
result = null;
}
else {
result = new SmartList<PsiFile>(possibleAnnotationsXmls);
// sorting by writability: writable go first
Collections.sort(possibleAnnotationsXmls, new Comparator<PsiFile>() {
Collections.sort(result, new Comparator<PsiFile>() {
@Override
public int compare(PsiFile f1, PsiFile f2) {
boolean w1 = f1.isWritable();
@@ -274,16 +390,17 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
}
});
myExternalAnnotations.put(fqn, possibleAnnotationsXmls);
return possibleAnnotationsXmls;
myExternalAnnotations.put(fqn, result);
}
myExternalAnnotations.put(fqn, NULL);
return null;
return result;
}
@NotNull
protected abstract List<VirtualFile> getExternalAnnotationsRoots(@NotNull VirtualFile libraryFile);
protected void dropCache() {
myExternalAnnotations.clear();
annotationsFileToDataAndModificationStamp.clear();
annotationFileToDataAndModStamp.clear();
cache.clear();
}
@@ -294,7 +411,7 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
private static String escapeAttributes(@NotNull String invalidXml) {
// We assume that XML has single- and double-quote characters only for attribute values, therefore we don't any complex parsing,
// just have binary inAttribute state
StringBuilder buf = new StringBuilder();
StringBuilder buf = new StringBuilder(invalidXml.length());
boolean inAttribute = false;
for (int i = 0; i < invalidXml.length(); i++) {
char c = invalidXml.charAt(i);
@@ -340,13 +457,63 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
throw new UnsupportedOperationException();
}
private static class AnnotationData {
@NotNull public String annotationClassFqName;
@NotNull public String annotationText;
private AnnotationData(@NotNull String annotationClassFqName, @NotNull String annotationText) {
this.annotationClassFqName = annotationClassFqName;
this.annotationText = annotationText;
protected void cacheExternalAnnotations(@NotNull String packageName, @NotNull PsiFile fromFile, @NotNull List<PsiFile> annotationFiles) {
String fqn = getFQN(packageName, fromFile);
if (fqn != null) {
myExternalAnnotations.put(fqn, annotationFiles);
}
}
private class AnnotationData {
@NotNull private final String annotationClassFqName;
@NotNull private final String annotationParameters;
private PsiAnnotation annotation;
private AnnotationData(@NotNull String annotationClassFqName, @NotNull String annotationParameters) {
this.annotationClassFqName = annotationClassFqName;
this.annotationParameters = annotationParameters;
}
@NotNull
private PsiAnnotation getAnnotation() {
PsiAnnotation a = annotation;
if (a == null) {
annotation = a = createAnnotationFromText("@" + annotationClassFqName + (annotationParameters.isEmpty() ? "" : "("+annotationParameters+")"));
}
return a;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AnnotationData data = (AnnotationData)o;
return annotationClassFqName.equals(data.annotationClassFqName) && annotationParameters.equals(data.annotationParameters);
}
@Override
public int hashCode() {
int result = annotationClassFqName.hashCode();
result = 31 * result + annotationParameters.hashCode();
return result;
}
}
@NotNull
private PsiAnnotation createAnnotationFromText(@NotNull final String text) throws IncorrectOperationException {
final DummyHolder holder = DummyHolderFactory.createHolder(myPsiManager, new JavaDummyElement(text, ANNOTATION, LanguageLevel.HIGHEST), null, charTable);
final PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode());
if (!(element instanceof PsiAnnotation)) {
throw new IncorrectOperationException("Incorrect annotation \"" + text + "\".");
}
return (PsiAnnotation)element;
}
private static final JavaParserUtil.ParserWrapper ANNOTATION = new JavaParserUtil.ParserWrapper() {
@Override
public void parse(final PsiBuilder builder) {
JavaParser.INSTANCE.getDeclarationParser().parseAnnotation(builder);
}
};
}
@@ -18,10 +18,11 @@ package com.intellij.psi.impl.source;
import com.intellij.util.CharTable;
import com.intellij.util.containers.OpenTHashSet;
import com.intellij.util.text.CharArrayCharSequence;
import com.intellij.util.text.CharArrayUtil;
import com.intellij.util.text.CharSequenceHashingStrategy;
import com.intellij.util.text.CharSequenceSubSequence;
import com.intellij.util.text.StringFactory;
import org.jetbrains.annotations.NotNull;
/**
* @author max
@@ -33,19 +34,25 @@ public class CharTableImpl implements CharTable {
private final OpenTHashSet<CharSequence> entries = new OpenTHashSet<CharSequence>(10, 0.9f, HASHER);
@NotNull
@Override
public CharSequence intern(final CharSequence text) {
public CharSequence intern(@NotNull final CharSequence text) {
if (text.length() > INTERN_THRESHOLD) return createSequence(text);
int idx = STATIC_ENTRIES.index(text);
if (idx >= 0) {
return STATIC_ENTRIES.get(idx);
return doIntern(text);
}
@NotNull
public CharSequence doIntern(@NotNull CharSequence text) {
CharSequence interned = STATIC_ENTRIES.get(text);
if (interned != null) {
return interned;
}
synchronized(entries) {
idx = entries.index(text);
if (idx >= 0) {
return entries.get(idx);
interned = entries.get(text);
if (interned != null) {
return interned;
}
// We need to create separate string just to prevent referencing all character data when original is string or char sequence over string
@@ -57,19 +64,22 @@ public class CharTableImpl implements CharTable {
}
}
@NotNull
@Override
public CharSequence intern(final CharSequence baseText, final int startOffset, final int endOffset) {
public CharSequence intern(@NotNull final CharSequence baseText, final int startOffset, final int endOffset) {
if (endOffset - startOffset == baseText.length()) return baseText;
return intern(new CharSequenceSubSequence(baseText, startOffset, endOffset));
}
private static CharSequence createSequence(final CharSequence text) {
final char[] buf = new char[text.length()];
@NotNull
private static String createSequence(@NotNull CharSequence text) {
char[] buf = new char[text.length()];
CharArrayUtil.getChars(text, buf, 0);
return new CharArrayCharSequence(buf);
return StringFactory.createShared(buf); // this way the .toString() doesn't create another instance (as opposed to new CharArrayCharSequence())
}
public static void staticIntern(final String text) {
public static void staticIntern(@NotNull String text) {
synchronized(STATIC_ENTRIES) {
STATIC_ENTRIES.add(text);
}
@@ -47,7 +47,7 @@ public class SmartList<E> extends AbstractList<E> {
}
}
public SmartList(E... elements) {
public SmartList(@NotNull E... elements) {
if (elements.length == 1) {
add(elements[0]);
}
@@ -177,7 +177,7 @@ public class SmartList<E> extends AbstractList<E> {
myElem = null;
}
else {
final Object[] array = (Object[])myElem;
Object[] array = (Object[])myElem;
oldValue = (E)array[index];
if (mySize == 2) {
@@ -266,4 +266,18 @@ public class SmartList<E> extends AbstractList<E> {
//noinspection SuspiciousToArrayCall
return super.toArray(a);
}
}
/**
* Trims the capacity of this list to be the
* list's current size. An application can use this operation to minimize
* the storage of a list instance.
*/
public void trimToSize() {
if (mySize < 2) return;
Object[] array = (Object[])myElem;
int oldCapacity = array.length;
if (mySize < oldCapacity) {
modCount++;
myElem = Arrays.copyOf(array, mySize);
}
}}
@@ -0,0 +1,76 @@
/*
* Copyright 2000-2012 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.containers;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ConcurrencyUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
public class ConcurrentMostlySingularMultiMap<K, V> extends MostlySingularMultiMap<K, V> {
@NotNull
@Override
protected Map<K, Object> createMap() {
return new ConcurrentHashMap<K, Object>();
}
@Override
public void add(@NotNull K key, @NotNull V value) {
ConcurrentMap<K, Object> map = (ConcurrentMap<K, Object>)myMap;
while (true) {
Object current = map.get(key);
if (current == null) {
if (ConcurrencyUtil.cacheOrGet(map, key, value) == value) break;
}
else if (current instanceof Object[]) {
Object[] curArr = (Object[])current;
Object[] newArr = ArrayUtil.append(curArr, value, ArrayUtil.OBJECT_ARRAY_FACTORY);
if (map.replace(key, curArr, newArr)) break;
}
else {
Object[] newArr = {current, value};
if (map.replace(key, current, newArr)) break;
}
}
}
@Override
public void compact() {
// not implemented
}
public boolean replace(@NotNull K key, @NotNull Collection<V> expectedValue, @NotNull Collection<V> newValue) {
ConcurrentMap<K, Object> map = (ConcurrentMap<K, Object>)myMap;
Object[] newArray = ArrayUtil.toObjectArray(newValue);
Object newValueToPut = newArray.length == 0 ? null : newArray.length == 1 ? newArray[0] : newArray;
Object oldValue = map.get(key);
List<V> oldCollection = rawValueToCollection(oldValue);
if (!oldCollection.equals(expectedValue)) return false;
if (oldValue == null) {
return newValueToPut == null || map.putIfAbsent(key, newValueToPut) == newValueToPut;
}
if (newValueToPut == null) {
return map.remove(key, oldValue);
}
return map.replace(key, oldValue, newValueToPut);
}
}
@@ -22,22 +22,29 @@ package com.intellij.util.containers;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.*;
public class MostlySingularMultiMap<K, V> implements Serializable {
private static final long serialVersionUID = 2784448345881807109L;
private final THashMap<K, Object> myMap = new THashMap<K, Object>();
protected final Map<K, Object> myMap;
public void add(K key, V value) {
public MostlySingularMultiMap() {
myMap = createMap();
}
@NotNull
protected Map<K, Object> createMap() {
return new THashMap<K, Object>();
}
public void add(@NotNull K key, @NotNull V value) {
Object current = myMap.get(key);
if (current == null) {
myMap.put(key, value);
@@ -52,6 +59,7 @@ public class MostlySingularMultiMap<K, V> implements Serializable {
}
}
@NotNull
public Set<K> keySet() {
return myMap.keySet();
}
@@ -60,11 +68,11 @@ public class MostlySingularMultiMap<K, V> implements Serializable {
return myMap.isEmpty();
}
public boolean processForKey(K key, Processor<V> p) {
public boolean processForKey(@NotNull K key, @NotNull Processor<V> p) {
return processValue(p, myMap.get(key));
}
private boolean processValue(Processor<V> p, Object v) {
private boolean processValue(@NotNull Processor<V> p, Object v) {
if (v instanceof Object[]) {
for (Object o : (Object[])v) {
if (!p.process((V)o)) return false;
@@ -77,7 +85,7 @@ public class MostlySingularMultiMap<K, V> implements Serializable {
return true;
}
public boolean processAllValues(Processor<V> p) {
public boolean processAllValues(@NotNull Processor<V> p) {
for (Object v : myMap.values()) {
if (!processValue(p, v)) return false;
}
@@ -89,7 +97,7 @@ public class MostlySingularMultiMap<K, V> implements Serializable {
return myMap.size();
}
public int valuesForKey(K key) {
public int valuesForKey(@NotNull K key) {
Object current = myMap.get(key);
if (current == null) return 0;
if (current instanceof Object[]) return ((Object[])current).length;
@@ -97,19 +105,24 @@ public class MostlySingularMultiMap<K, V> implements Serializable {
}
@NotNull
public Iterable<V> get(K name) {
public Iterable<V> get(@NotNull K name) {
final Object value = myMap.get(name);
return rawValueToCollection(value);
}
@NotNull
protected List<V> rawValueToCollection(Object value) {
if (value == null) return Collections.emptyList();
if (value instanceof Object[]) {
return (Iterable<V>)Arrays.asList((Object[])value);
return (List<V>)Arrays.asList((Object[])value);
}
return Collections.singleton((V)value);
return Collections.singletonList((V)value);
}
public void compact() {
myMap.compact();
((THashMap)myMap).compact();
}
@Override
@@ -123,4 +136,57 @@ public class MostlySingularMultiMap<K, V> implements Serializable {
}
}, "; ") + "}";
}
public void clear() {
myMap.clear();
}
@NotNull
public static <K,V> MostlySingularMultiMap<K,V> emptyMap() {
//noinspection unchecked
return EMPTY;
}
private static final MostlySingularMultiMap EMPTY = new MostlySingularMultiMap() {
@Override
public void add(@NotNull Object key, @NotNull Object value) {
throw new IncorrectOperationException();
}
@NotNull
@Override
public Set keySet() {
return Collections.emptySet();
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public boolean processForKey(@NotNull Object key, @NotNull Processor p) {
return true;
}
@Override
public boolean processAllValues(@NotNull Processor p) {
return true;
}
@Override
public int size() {
return 0;
}
@Override
public int valuesForKey(@NotNull Object key) {
return 0;
}
@NotNull
@Override
public Iterable get(@NotNull Object name) {
return EmptyIterable.getInstance();
}
};
}
@@ -21,14 +21,13 @@ import org.jetbrains.annotations.NotNull;
* @author max
*/
public class StringInterner {
private final OpenTHashSet<String> mySet = new OpenTHashSet<String>();
@NotNull
public String intern(@NotNull String name) {
int idx = mySet.index(name);
if (idx >= 0) {
return mySet.get(idx);
String interned = mySet.get(name);
if (interned != null) {
return interned;
}
boolean added = mySet.add(name);