update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInsight / daemon / impl / RefCountHolder.java
blobdd337716d1fccd6374a1b43604b5df3607314e5a
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.codeInsight.daemon.impl;
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.util.Key;
20 import com.intellij.openapi.util.TextRange;
21 import com.intellij.openapi.util.UserDataHolderEx;
22 import com.intellij.openapi.util.text.StringUtil;
23 import com.intellij.psi.*;
24 import com.intellij.psi.util.PsiMatcherImpl;
25 import com.intellij.psi.util.PsiMatchers;
26 import com.intellij.psi.util.PsiTreeUtil;
27 import com.intellij.psi.util.PsiUtil;
28 import com.intellij.util.ArrayUtil;
29 import com.intellij.util.containers.BidirectionalMap;
30 import com.intellij.util.containers.ConcurrentHashMap;
31 import org.jetbrains.annotations.NotNull;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.concurrent.atomic.AtomicReference;
38 public class RefCountHolder {
39 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.RefCountHolder");
41 private final PsiFile myFile;
42 private final BidirectionalMap<PsiReference,PsiElement> myLocalRefsMap = new BidirectionalMap<PsiReference, PsiElement>();
44 private final Map<PsiNamedElement, Boolean> myDclsUsedMap = new ConcurrentHashMap<PsiNamedElement, Boolean>();
45 private final Map<PsiReference, PsiImportStatementBase> myImportStatements = new ConcurrentHashMap<PsiReference, PsiImportStatementBase>();
46 private final Map<PsiElement,Boolean> myPossiblyDuplicateElements = new ConcurrentHashMap<PsiElement, Boolean>();
47 private final AtomicReference<State> myState = new AtomicReference<State>(State.VIRGIN);
49 private enum State {
50 VIRGIN, // just created or cleared
51 BEING_WRITTEN_BY_GHP, // general highlighting pass is storing references during analysis
52 READY, // may be used for higlighting unused stuff
53 BEING_USED_BY_PHP, // post highlighting pass is retrieving info
56 private static final Key<RefCountHolder> REF_COUNT_HOLDER_IN_FILE_KEY = Key.create("REF_COUNT_HOLDER_IN_FILE_KEY");
57 public static RefCountHolder getInstance(PsiFile file) {
58 RefCountHolder refCountHolder = file.getUserData(REF_COUNT_HOLDER_IN_FILE_KEY);
59 if (refCountHolder == null) {
60 refCountHolder = ((UserDataHolderEx)file).putUserDataIfAbsent(REF_COUNT_HOLDER_IN_FILE_KEY, new RefCountHolder(file));
62 return refCountHolder;
65 private RefCountHolder(@NotNull PsiFile file) {
66 myFile = file;
67 LOG.debug("RefCountHolder created for '"+ StringUtil.first(file.getText(), 30, true));
70 private void clear() {
71 assertIsAnalyzing();
72 myLocalRefsMap.clear();
73 myImportStatements.clear();
74 myDclsUsedMap.clear();
75 myPossiblyDuplicateElements.clear();
78 public void registerLocallyReferenced(@NotNull PsiNamedElement result) {
79 assertIsAnalyzing();
80 myDclsUsedMap.put(result,Boolean.TRUE);
83 public void registerReference(@NotNull PsiJavaReference ref, JavaResolveResult resolveResult) {
84 assertIsAnalyzing();
85 PsiElement refElement = resolveResult.getElement();
86 PsiFile psiFile = refElement == null ? null : refElement.getContainingFile();
87 if (psiFile != null) psiFile = (PsiFile)psiFile.getNavigationElement(); // look at navigation elements because all references resolve into Cls elements when highlighting library source
88 if (refElement != null && psiFile != null && myFile.getViewProvider().equals(psiFile.getViewProvider())) {
89 registerLocalRef(ref, refElement.getNavigationElement());
92 PsiElement resolveScope = resolveResult.getCurrentFileResolveScope();
93 if (resolveScope instanceof PsiImportStatementBase) {
94 registerImportStatement(ref, (PsiImportStatementBase)resolveScope);
98 private void registerImportStatement(@NotNull PsiReference ref, PsiImportStatementBase importStatement) {
99 myImportStatements.put(ref, importStatement);
102 public boolean isRedundant(PsiImportStatementBase importStatement) {
103 assertIsRetrieving();
104 return !myImportStatements.containsValue(importStatement);
107 private void registerLocalRef(@NotNull PsiReference ref, PsiElement refElement) {
108 if (refElement instanceof PsiMethod && PsiTreeUtil.isAncestor(refElement, ref.getElement(), true)) return; // filter self-recursive calls
109 if (refElement instanceof PsiClass && PsiTreeUtil.isAncestor(refElement, ref.getElement(), true)) return; // filter inner use of itself
110 synchronized (myLocalRefsMap) {
111 myLocalRefsMap.put(ref, refElement);
115 private void removeInvalidRefs() {
116 assertIsAnalyzing();
117 synchronized (myLocalRefsMap) {
118 for(Iterator<PsiReference> iterator = myLocalRefsMap.keySet().iterator(); iterator.hasNext();){
119 PsiReference ref = iterator.next();
120 if (!ref.getElement().isValid()){
121 PsiElement value = myLocalRefsMap.get(ref);
122 iterator.remove();
123 List<PsiReference> array = myLocalRefsMap.getKeysByValue(value);
124 LOG.assertTrue(array != null);
125 array.remove(ref);
129 for (Iterator<PsiReference> iterator = myImportStatements.keySet().iterator(); iterator.hasNext();) {
130 PsiReference ref = iterator.next();
131 if (!ref.getElement().isValid()) {
132 iterator.remove();
135 removeInvalidFrom(myDclsUsedMap.keySet());
136 removeInvalidFrom(myPossiblyDuplicateElements.keySet());
138 private static void removeInvalidFrom(Iterable<? extends PsiElement> collection) {
139 for (Iterator<? extends PsiElement> it = collection.iterator(); it.hasNext();) {
140 PsiElement element = it.next();
141 if (!element.isValid()) it.remove();
145 public boolean isReferenced(PsiNamedElement element) {
146 assertIsRetrieving();
147 List<PsiReference> array = myLocalRefsMap.getKeysByValue(element);
148 if (array != null && !array.isEmpty() && !isParameterUsedRecursively(element, array)) return true;
150 Boolean usedStatus = myDclsUsedMap.get(element);
151 return usedStatus == Boolean.TRUE;
154 private static boolean isParameterUsedRecursively(final PsiElement element, final List<PsiReference> array) {
155 if (!(element instanceof PsiParameter)) return false;
156 PsiParameter parameter = (PsiParameter)element;
157 PsiElement scope = parameter.getDeclarationScope();
158 if (!(scope instanceof PsiMethod)) return false;
159 PsiMethod method = (PsiMethod)scope;
160 int paramIndex = ArrayUtil.find(method.getParameterList().getParameters(), parameter);
162 for (PsiReference reference : array) {
163 if (!(reference instanceof PsiElement)) return false;
164 PsiElement argument = (PsiElement)reference;
166 PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)new PsiMatcherImpl(argument)
167 .dot(PsiMatchers.hasClass(PsiReferenceExpression.class))
168 .parent(PsiMatchers.hasClass(PsiExpressionList.class))
169 .parent(PsiMatchers.hasClass(PsiMethodCallExpression.class))
170 .getElement();
171 if (methodCallExpression == null) return false;
172 PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
173 if (method != methodExpression.resolve()) return false;
174 PsiExpressionList argumentList = methodCallExpression.getArgumentList();
175 PsiExpression[] arguments = argumentList.getExpressions();
176 int argumentIndex = ArrayUtil.find(arguments, argument);
177 if (paramIndex != argumentIndex) return false;
180 return true;
183 public boolean isReferencedForRead(PsiElement element) {
184 assertIsRetrieving();
185 LOG.assertTrue(element instanceof PsiVariable);
186 List<PsiReference> array = myLocalRefsMap.getKeysByValue(element);
187 if (array == null) return false;
188 for (PsiReference ref : array) {
189 PsiElement refElement = ref.getElement();
190 if (!(refElement instanceof PsiExpression)) { // possible with uncomplete code
191 return true;
193 if (PsiUtil.isAccessedForReading((PsiExpression)refElement)) {
194 if (refElement.getParent() instanceof PsiExpression &&
195 refElement.getParent().getParent() instanceof PsiExpressionStatement &&
196 PsiUtil.isAccessedForWriting((PsiExpression)refElement)) {
197 continue; // "var++;"
199 return true;
202 return false;
205 public boolean isReferencedForWrite(PsiElement element) {
206 assertIsRetrieving();
207 LOG.assertTrue(element instanceof PsiVariable);
208 List<PsiReference> array = myLocalRefsMap.getKeysByValue(element);
209 if (array == null) return false;
210 for (PsiReference ref : array) {
211 final PsiElement refElement = ref.getElement();
212 if (!(refElement instanceof PsiExpression)) { // possible with uncomplete code
213 return true;
215 if (PsiUtil.isAccessedForWriting((PsiExpression)refElement)) {
216 return true;
219 return false;
222 public boolean analyze(Runnable analyze, final TextRange dirtyScope, final PsiFile file) {
223 myState.compareAndSet(State.READY, State.VIRGIN);
224 if (!myState.compareAndSet(State.VIRGIN, State.BEING_WRITTEN_BY_GHP)) return false;
226 try {
227 if (dirtyScope != null) {
228 if (dirtyScope.equals(file.getTextRange())) {
229 clear();
231 else {
232 removeInvalidRefs();
236 analyze.run();
238 finally {
239 boolean set = myState.compareAndSet(State.BEING_WRITTEN_BY_GHP, State.READY);
240 assert set : myState.get();
242 return true;
245 public boolean retrieveUnusedReferencesInfo(Runnable analyze) {
246 if (!myState.compareAndSet(State.READY, State.BEING_USED_BY_PHP)) {
247 return false;
249 try {
250 analyze.run();
252 finally {
253 boolean set = myState.compareAndSet(State.BEING_USED_BY_PHP, State.READY);
254 assert set : myState.get();
256 return true;
259 private void assertIsAnalyzing() {
260 assert myState.get() == State.BEING_WRITTEN_BY_GHP : myState.get();
262 private void assertIsRetrieving() {
263 assert myState.get() == State.BEING_USED_BY_PHP : myState.get();