blob: 7bb318192ba984bb89cd27695213c013de4f6f20 [file] [log] [blame]
/*
* Copyright 2000-2014 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.codeInsight.daemon.impl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.UserDataHolderEx;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiMatcherImpl;
import com.intellij.psi.util.PsiMatchers;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.containers.BidirectionalMap;
import com.intellij.util.containers.ConcurrentHashMap;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.SoftReference;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
public class RefCountHolder {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.RefCountHolder");
private final PsiFile myFile;
private final BidirectionalMap<PsiReference,PsiElement> myLocalRefsMap = new BidirectionalMap<PsiReference, PsiElement>();
private final Map<PsiAnchor, Boolean> myDclsUsedMap = ContainerUtil.newConcurrentMap();
private final Map<PsiReference, PsiImportStatementBase> myImportStatements = ContainerUtil.newConcurrentMap();
private final AtomicReference<ProgressIndicator> myState = new AtomicReference<ProgressIndicator>(VIRGIN);
private static final ProgressIndicator VIRGIN = new DaemonProgressIndicator(); // just created or cleared
private static final ProgressIndicator READY = new DaemonProgressIndicator();
private volatile ProgressIndicator analyzedUnder;
private static class HolderReference extends SoftReference<RefCountHolder> {
// Map holding hard references to RefCountHolder for each highlighting pass (identified by its progress indicator)
// there can be multiple passes running simultaneously (one actual and several passes just canceled and winding down but still alive)
// so there is a chance they overlap the usage of RCH
// As soon as everybody finished using RCH, map become empty and the RefCountHolder is eligible for gc
private final Map<ProgressIndicator, RefCountHolder> map = new ConcurrentHashMap<ProgressIndicator, RefCountHolder>();
public HolderReference(@NotNull RefCountHolder holder) {
super(holder);
}
private void acquire(@NotNull ProgressIndicator indicator) {
RefCountHolder holder = get();
assert holder != null: "no way";
map.put(indicator, holder);
holder = get();
assert holder != null: "can't be!";
}
private RefCountHolder release(@NotNull ProgressIndicator indicator) {
return map.remove(indicator);
}
}
private static final Key<HolderReference> REF_COUNT_HOLDER_IN_FILE_KEY = Key.create("REF_COUNT_HOLDER_IN_FILE_KEY");
private static RefCountHolder getInstance(@NotNull PsiFile file, @NotNull ProgressIndicator indicator, boolean acquire) {
HolderReference ref = file.getUserData(REF_COUNT_HOLDER_IN_FILE_KEY);
RefCountHolder holder = com.intellij.reference.SoftReference.dereference(ref);
if (holder == null && acquire) {
holder = new RefCountHolder(file);
HolderReference newRef = new HolderReference(holder);
while (true) {
boolean replaced = ((UserDataHolderEx)file).replace(REF_COUNT_HOLDER_IN_FILE_KEY, ref, newRef);
if (replaced) {
ref = newRef;
break;
}
ref = file.getUserData(REF_COUNT_HOLDER_IN_FILE_KEY);
RefCountHolder newHolder = com.intellij.reference.SoftReference.dereference(ref);
if (newHolder != null) {
holder = newHolder;
break;
}
}
}
if (ref != null) {
if (acquire) {
ref.acquire(indicator);
}
else {
ref.release(indicator);
}
}
return holder;
}
@NotNull
public static RefCountHolder startUsing(@NotNull PsiFile file, @NotNull ProgressIndicator indicator) {
return getInstance(file, indicator, true);
}
@Nullable("might be gced")
public static RefCountHolder endUsing(@NotNull PsiFile file, @NotNull ProgressIndicator indicator) {
return getInstance(file, indicator, false);
}
private RefCountHolder(@NotNull PsiFile file) {
myFile = file;
log("c: created: ", myState.get(), " for ", file);
}
private void clear() {
synchronized (myLocalRefsMap) {
myLocalRefsMap.clear();
}
myImportStatements.clear();
myDclsUsedMap.clear();
}
public void registerLocallyReferenced(@NotNull PsiNamedElement result) {
myDclsUsedMap.put(PsiAnchor.create(result), Boolean.TRUE);
}
public void registerReference(@NotNull PsiJavaReference ref, @NotNull JavaResolveResult resolveResult) {
PsiElement refElement = resolveResult.getElement();
PsiFile psiFile = refElement == null ? null : refElement.getContainingFile();
if (psiFile != null) psiFile = (PsiFile)psiFile.getNavigationElement(); // look at navigation elements because all references resolve into Cls elements when highlighting library source
if (refElement != null && psiFile != null && myFile.getViewProvider().equals(psiFile.getViewProvider())) {
registerLocalRef(ref, refElement.getNavigationElement());
}
PsiElement resolveScope = resolveResult.getCurrentFileResolveScope();
if (resolveScope instanceof PsiImportStatementBase) {
registerImportStatement(ref, (PsiImportStatementBase)resolveScope);
}
}
private void registerImportStatement(@NotNull PsiReference ref, @NotNull PsiImportStatementBase importStatement) {
myImportStatements.put(ref, importStatement);
}
public boolean isRedundant(@NotNull PsiImportStatementBase importStatement) {
return !myImportStatements.containsValue(importStatement);
}
private void registerLocalRef(@NotNull PsiReference ref, PsiElement refElement) {
if (refElement instanceof PsiMethod && PsiTreeUtil.isAncestor(refElement, ref.getElement(), true)) return; // filter self-recursive calls
if (refElement instanceof PsiClass && PsiTreeUtil.isAncestor(refElement, ref.getElement(), true)) return; // filter inner use of itself
synchronized (myLocalRefsMap) {
myLocalRefsMap.put(ref, refElement);
}
}
private void removeInvalidRefs() {
synchronized (myLocalRefsMap) {
for(Iterator<PsiReference> iterator = myLocalRefsMap.keySet().iterator(); iterator.hasNext();){
PsiReference ref = iterator.next();
if (!ref.getElement().isValid()){
PsiElement value = myLocalRefsMap.get(ref);
iterator.remove();
List<PsiReference> array = myLocalRefsMap.getKeysByValue(value);
LOG.assertTrue(array != null);
array.remove(ref);
}
}
}
for (Iterator<PsiReference> iterator = myImportStatements.keySet().iterator(); iterator.hasNext();) {
PsiReference ref = iterator.next();
if (!ref.getElement().isValid()) {
iterator.remove();
}
}
removeInvalidFrom(myDclsUsedMap.keySet());
}
private static void removeInvalidFrom(@NotNull Collection<? extends PsiAnchor> collection) {
for (Iterator<? extends PsiAnchor> it = collection.iterator(); it.hasNext();) {
PsiAnchor element = it.next();
if (element.retrieve() == null) it.remove();
}
}
public boolean isReferenced(@NotNull PsiNamedElement element) {
List<PsiReference> array;
synchronized (myLocalRefsMap) {
array = myLocalRefsMap.getKeysByValue(element);
}
if (array != null && !array.isEmpty() && !isParameterUsedRecursively(element, array)) return true;
Boolean usedStatus = myDclsUsedMap.get(PsiAnchor.create(element));
return usedStatus == Boolean.TRUE;
}
public boolean isReferencedByMethodReference(@NotNull PsiMethod method, @NotNull LanguageLevel languageLevel) {
if (!languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) return false;
List<PsiReference> array;
synchronized (myLocalRefsMap) {
array = myLocalRefsMap.getKeysByValue(method);
}
if (array != null && !array.isEmpty()) {
for (PsiReference reference : array) {
final PsiElement element = reference.getElement();
if (element != null && element instanceof PsiMethodReferenceExpression) {
return true;
}
}
}
return false;
}
private static boolean isParameterUsedRecursively(@NotNull PsiElement element, @NotNull List<PsiReference> array) {
if (!(element instanceof PsiParameter)) return false;
PsiParameter parameter = (PsiParameter)element;
PsiElement scope = parameter.getDeclarationScope();
if (!(scope instanceof PsiMethod)) return false;
PsiMethod method = (PsiMethod)scope;
int paramIndex = ArrayUtilRt.find(method.getParameterList().getParameters(), parameter);
for (PsiReference reference : array) {
if (!(reference instanceof PsiElement)) return false;
PsiElement argument = (PsiElement)reference;
PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)new PsiMatcherImpl(argument)
.dot(PsiMatchers.hasClass(PsiReferenceExpression.class))
.parent(PsiMatchers.hasClass(PsiExpressionList.class))
.parent(PsiMatchers.hasClass(PsiMethodCallExpression.class))
.getElement();
if (methodCallExpression == null) return false;
PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
if (method != methodExpression.resolve()) return false;
PsiExpressionList argumentList = methodCallExpression.getArgumentList();
PsiExpression[] arguments = argumentList.getExpressions();
int argumentIndex = ArrayUtilRt.find(arguments, argument);
if (paramIndex != argumentIndex) return false;
}
return true;
}
public boolean isReferencedForRead(@NotNull PsiVariable variable) {
List<PsiReference> array;
synchronized (myLocalRefsMap) {
array = myLocalRefsMap.getKeysByValue(variable);
}
if (array == null) return false;
for (PsiReference ref : array) {
PsiElement refElement = ref.getElement();
if (!(refElement instanceof PsiExpression)) { // possible with incomplete code
return true;
}
if (PsiUtil.isAccessedForReading((PsiExpression)refElement)) {
if (refElement.getParent() instanceof PsiExpression &&
refElement.getParent().getParent() instanceof PsiExpressionStatement &&
PsiUtil.isAccessedForWriting((PsiExpression)refElement)) {
continue; // "var++;"
}
return true;
}
}
return false;
}
public boolean isReferencedForWrite(@NotNull PsiVariable variable) {
List<PsiReference> array;
synchronized (myLocalRefsMap) {
array = myLocalRefsMap.getKeysByValue(variable);
}
if (array == null) return false;
for (PsiReference ref : array) {
final PsiElement refElement = ref.getElement();
if (!(refElement instanceof PsiExpression)) { // possible with incomplete code
return true;
}
if (PsiUtil.isAccessedForWriting((PsiExpression)refElement)) {
return true;
}
}
return false;
}
public boolean analyze(@NotNull PsiFile file, TextRange dirtyScope, @NotNull Runnable analyze, @NotNull ProgressIndicator indicator) {
ProgressIndicator old = myState.get();
if (old != VIRGIN && old != READY) return false;
if (!myState.compareAndSet(old, indicator)) {
log("a: failed to change ", old, "->", indicator);
return false;
}
log("a: changed ", old, "->", indicator);
analyzedUnder = null;
boolean completed = false;
try {
if (dirtyScope != null) {
if (dirtyScope.equals(file.getTextRange())) {
clear();
}
else {
removeInvalidRefs();
}
}
analyze.run();
analyzedUnder = indicator;
completed = true;
}
finally {
ProgressIndicator resultState = completed ? READY : VIRGIN;
boolean set = myState.compareAndSet(indicator, resultState);
assert set : myState.get();
log("a: changed after analyze", indicator, "->", resultState);
}
return true;
}
private static void log(@NonNls Object... s) {
//System.err.println("RFC: "+ Arrays.asList(s));
}
public boolean retrieveUnusedReferencesInfo(@NotNull ProgressIndicator indicator, @NotNull Runnable analyze) {
ProgressIndicator old = myState.get();
if (!myState.compareAndSet(READY, indicator)) {
log("r: failed to change ", old, "->", indicator);
return false;
}
log("r: changed ", old, "->", indicator);
try {
if (analyzedUnder != indicator) {
return false;
}
analyze.run();
}
finally {
boolean set = myState.compareAndSet(indicator, READY);
assert set : myState.get();
log("r: changed back ", indicator, "->", READY);
}
return true;
}
}