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
;
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
);
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
) {
67 LOG
.debug("RefCountHolder created for '"+ StringUtil
.first(file
.getText(), 30, true));
70 private void clear() {
72 myLocalRefsMap
.clear();
73 myImportStatements
.clear();
74 myDclsUsedMap
.clear();
75 myPossiblyDuplicateElements
.clear();
78 public void registerLocallyReferenced(@NotNull PsiNamedElement result
) {
80 myDclsUsedMap
.put(result
,Boolean
.TRUE
);
83 public void registerReference(@NotNull PsiJavaReference ref
, JavaResolveResult resolveResult
) {
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() {
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
);
123 List
<PsiReference
> array
= myLocalRefsMap
.getKeysByValue(value
);
124 LOG
.assertTrue(array
!= null);
129 for (Iterator
<PsiReference
> iterator
= myImportStatements
.keySet().iterator(); iterator
.hasNext();) {
130 PsiReference ref
= iterator
.next();
131 if (!ref
.getElement().isValid()) {
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))
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;
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
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++;"
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
215 if (PsiUtil
.isAccessedForWriting((PsiExpression
)refElement
)) {
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;
227 if (dirtyScope
!= null) {
228 if (dirtyScope
.equals(file
.getTextRange())) {
239 boolean set
= myState
.compareAndSet(State
.BEING_WRITTEN_BY_GHP
, State
.READY
);
240 assert set
: myState
.get();
245 public boolean retrieveUnusedReferencesInfo(Runnable analyze
) {
246 if (!myState
.compareAndSet(State
.READY
, State
.BEING_USED_BY_PHP
)) {
253 boolean set
= myState
.compareAndSet(State
.BEING_USED_BY_PHP
, State
.READY
);
254 assert set
: myState
.get();
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();