/* * 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.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.Pair; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.UserDataHolderEx; 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.ArrayUtil; import com.intellij.util.containers.BidirectionalMap; import com.intellij.util.containers.ConcurrentHashMap; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.lang.ref.SoftReference; import java.util.Iterator; import java.util.List; import java.util.Map; 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 myLocalRefsMap = new BidirectionalMap(); private final Map myDclsUsedMap = new ConcurrentHashMap(); private final Map myImportStatements = new ConcurrentHashMap(); private final Map myPossiblyDuplicateElements = new ConcurrentHashMap(); private final AtomicReference myState = new AtomicReference(VIRGIN); private static final ProgressIndicator VIRGIN = new DaemonProgressIndicator(); // just created or cleared private static final ProgressIndicator READY = new DaemonProgressIndicator(); private static class HolderReference extends SoftReference { @SuppressWarnings("UnusedDeclaration") private volatile RefCountHolder myHardRef; // to prevent gc public HolderReference(@NotNull RefCountHolder holder) { super(holder); myHardRef = holder; } private void makeHardReachable(boolean isHard) { RefCountHolder holder = get(); assert !isHard || holder != null : "hard: "+isHard +"; holder="+holder; myHardRef = isHard ? holder : null; } } private static final Key REF_COUNT_HOLDER_IN_FILE_KEY = Key.create("REF_COUNT_HOLDER_IN_FILE_KEY"); @NotNull private static Pair getInstance(@NotNull PsiFile file, boolean create) { HolderReference ref = file.getUserData(REF_COUNT_HOLDER_IN_FILE_KEY); RefCountHolder holder = ref == null ? null : ref.get(); if (holder == null && create) { 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 = ref == null ? null : ref.get(); if (newHolder != null) { holder = newHolder; break; } } } return Pair.create(holder, ref); } @NotNull public static RefCountHolder startUsing(@NotNull PsiFile file) { Pair pair = getInstance(file, true); HolderReference reference = pair.second; reference.makeHardReachable(true); // make sure RefCountHolder won't be gced during highlighting log("startUsing: " + pair.first.myState+" for "+file); return pair.first; } @Nullable("might be gced") public static RefCountHolder endUsing(@NotNull PsiFile file) { Pair pair = getInstance(file, false); HolderReference reference = pair.second; reference.makeHardReachable(false); // no longer needed, can be cleared RefCountHolder holder = pair.first; log("endUsing: " + (holder == null ? null : holder.myState)+" for "+file); return holder; } 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(); myPossiblyDuplicateElements.clear(); } public void registerLocallyReferenced(@NotNull PsiNamedElement result) { myDclsUsedMap.put(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 iterator = myLocalRefsMap.keySet().iterator(); iterator.hasNext();){ PsiReference ref = iterator.next(); if (!ref.getElement().isValid()){ PsiElement value = myLocalRefsMap.get(ref); iterator.remove(); List array = myLocalRefsMap.getKeysByValue(value); LOG.assertTrue(array != null); array.remove(ref); } } } for (Iterator iterator = myImportStatements.keySet().iterator(); iterator.hasNext();) { PsiReference ref = iterator.next(); if (!ref.getElement().isValid()) { iterator.remove(); } } removeInvalidFrom(myDclsUsedMap.keySet()); removeInvalidFrom(myPossiblyDuplicateElements.keySet()); } private static void removeInvalidFrom(Iterable collection) { for (Iterator it = collection.iterator(); it.hasNext();) { PsiElement element = it.next(); if (!element.isValid()) it.remove(); } } public boolean isReferenced(PsiNamedElement element) { List array; synchronized (myLocalRefsMap) { array = myLocalRefsMap.getKeysByValue(element); } if (array != null && !array.isEmpty() && !isParameterUsedRecursively(element, array)) return true; Boolean usedStatus = myDclsUsedMap.get(element); return usedStatus == Boolean.TRUE; } private static boolean isParameterUsedRecursively(final PsiElement element, final List 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 = ArrayUtil.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 = ArrayUtil.find(arguments, argument); if (paramIndex != argumentIndex) return false; } return true; } public boolean isReferencedForRead(@NotNull PsiElement element) { LOG.assertTrue(element instanceof PsiVariable); List array; synchronized (myLocalRefsMap) { array = myLocalRefsMap.getKeysByValue(element); } 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 PsiElement element) { LOG.assertTrue(element instanceof PsiVariable); List array; synchronized (myLocalRefsMap) { array = myLocalRefsMap.getKeysByValue(element); } 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); boolean finished = false; try { if (dirtyScope != null) { if (dirtyScope.equals(file.getTextRange())) { clear(); } else { removeInvalidRefs(); } } analyze.run(); finished = true; } finally { boolean set = myState.compareAndSet(indicator, finished ? READY : VIRGIN); assert set : myState.get(); log("a: changed back " + indicator + "->" + (finished ? READY : VIRGIN)); } return true; } private static void log(@NonNls String s) { //System.err.println("RFC: "+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 { analyze.run(); } finally { boolean set = myState.compareAndSet(indicator, READY); assert set : myState.get(); log("r: changed back " + indicator + "->" + READY); } return true; } }