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.
18 * created at Feb 24, 2002
21 package com
.intellij
.compiler
.make
;
23 import com
.intellij
.compiler
.classParsing
.FieldInfo
;
24 import com
.intellij
.lang
.StdLanguages
;
25 import com
.intellij
.openapi
.application
.ApplicationManager
;
26 import com
.intellij
.openapi
.diagnostic
.Logger
;
27 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
28 import com
.intellij
.openapi
.project
.Project
;
29 import com
.intellij
.openapi
.vfs
.VirtualFile
;
30 import com
.intellij
.psi
.*;
31 import com
.intellij
.psi
.search
.*;
32 import com
.intellij
.psi
.util
.PsiUtil
;
33 import org
.jetbrains
.annotations
.NotNull
;
34 import org
.jetbrains
.annotations
.Nullable
;
36 import java
.util
.Collection
;
37 import java
.util
.HashSet
;
40 class ChangedConstantsDependencyProcessor
{
41 private static final Logger LOG
= Logger
.getInstance("#com.intellij.compiler.make.ChangedConstantsDependencyProcessor");
42 private final Project myProject
;
43 private final CachingSearcher mySearcher
;
44 private final DependencyCache myDependencyCache
;
45 private final int myQName
;
46 private final FieldChangeInfo
[] myChangedFields
;
47 private final FieldChangeInfo
[] myRemovedFields
;
48 private static final long ANALYSIS_DURATION_THRESHOLD_MILLIS
= 30000L /*30 sec*/;
51 public ChangedConstantsDependencyProcessor(Project project
, CachingSearcher searcher
, DependencyCache dependencyCache
, int qName
, FieldChangeInfo
[] changedFields
, FieldChangeInfo
[] removedFields
) {
53 mySearcher
= searcher
;
54 myDependencyCache
= dependencyCache
;
56 myChangedFields
= changedFields
;
57 myRemovedFields
= removedFields
;
60 public void run() throws CacheCorruptedException
{
61 final CacheCorruptedException
[] _ex
= new CacheCorruptedException
[] {null};
62 ApplicationManager
.getApplication().runReadAction(new Runnable() {
65 final String qName
= myDependencyCache
.resolve(myQName
);
66 PsiClass
[] classes
= JavaPsiFacade
.getInstance(myProject
).findClasses(qName
.replace('$', '.'), GlobalSearchScope
.allScope(myProject
));
67 for (PsiClass aClass
: classes
) {
68 PsiField
[] psiFields
= aClass
.getFields();
69 for (PsiField psiField
: psiFields
) {
70 final FieldChangeInfo changeInfo
= findChangeInfo(psiField
);
71 if (changeInfo
!= null) { // this field has been changed
72 processFieldChanged(psiField
, aClass
, changeInfo
.isAccessibilityChange
);
75 for (FieldChangeInfo removedField
: myRemovedFields
) {
76 processFieldRemoved(removedField
.fieldInfo
, aClass
);
80 catch (CacheCorruptedException e
) {
83 catch (ProcessCanceledException e
) {
84 // supressed deliberately
93 private void processFieldRemoved(FieldInfo info
, PsiClass aClass
) throws CacheCorruptedException
{
94 if (info
.isPrivate()) {
95 return; // optimization: don't need to search, cause may be used only in this class
97 SearchScope searchScope
= GlobalSearchScope
.projectScope(myProject
);
98 if (info
.isPackageLocal()) {
99 final PsiFile containingFile
= aClass
.getContainingFile();
100 if (containingFile
instanceof PsiJavaFile
) {
101 final String packageName
= ((PsiJavaFile
)containingFile
).getPackageName();
102 final PsiPackage aPackage
= JavaPsiFacade
.getInstance(myProject
).findPackage(packageName
);
103 if (aPackage
!= null) {
104 searchScope
= PackageScope
.packageScope(aPackage
, false);
105 searchScope
= searchScope
.intersectWith(aClass
.getUseScope());
109 final PsiSearchHelper psiSearchHelper
= PsiManager
.getInstance(myProject
).getSearchHelper();
111 final long analysisStart
= System
.currentTimeMillis();
112 boolean skipResolve
= false;
114 PsiIdentifier
[] identifiers
= findIdentifiers(psiSearchHelper
, myDependencyCache
.resolve(info
.getName()), searchScope
, UsageSearchContext
.IN_CODE
);
115 for (PsiIdentifier identifier
: identifiers
) {
116 PsiElement parent
= identifier
.getParent();
117 if (parent
instanceof PsiReferenceExpression
) {
118 PsiReferenceExpression refExpr
= (PsiReferenceExpression
)parent
;
119 PsiReference reference
= refExpr
.getReference();
120 skipResolve
= skipResolve
|| (System
.currentTimeMillis() - analysisStart
) > ANALYSIS_DURATION_THRESHOLD_MILLIS
;
121 if (skipResolve
|| reference
== null || reference
.resolve() == null) {
122 PsiClass ownerClass
= getOwnerClass(refExpr
);
123 if (ownerClass
!= null && !ownerClass
.equals(aClass
)) {
124 int qualifiedName
= myDependencyCache
.getSymbolTable().getId(ownerClass
.getQualifiedName());
125 // should force marking of the class no matter was it compiled or not
126 // This will ensure the class was recompiled _after_ all the constants get their new values
127 if (myDependencyCache
.markClass(qualifiedName
, true)) {
128 if (LOG
.isDebugEnabled()) {
129 LOG
.debug("Mark dependent class " + myDependencyCache
.resolve(qualifiedName
) +
130 "; reason: some constants were removed from " + myDependencyCache
.resolve(myQName
));
140 private static PsiIdentifier
[] findIdentifiers(PsiSearchHelper helper
, @NotNull String identifier
, @NotNull SearchScope searchScope
, short searchContext
) {
141 PsiElementProcessor
.CollectElements
<PsiIdentifier
> processor
= new PsiElementProcessor
.CollectElements
<PsiIdentifier
>();
142 processIdentifiers(helper
, processor
, identifier
, searchScope
, searchContext
);
143 return processor
.toArray(PsiIdentifier
.EMPTY_ARRAY
);
146 private static boolean processIdentifiers(PsiSearchHelper helper
,
147 @NotNull final PsiElementProcessor
<PsiIdentifier
> processor
,
148 @NotNull final String identifier
,
149 @NotNull SearchScope searchScope
,
150 short searchContext
) {
151 TextOccurenceProcessor processor1
= new TextOccurenceProcessor() {
152 public boolean execute(PsiElement element
, int offsetInElement
) {
153 return !(element
instanceof PsiIdentifier
) || processor
.execute((PsiIdentifier
)element
);
156 return helper
.processElementsWithWord(processor1
, searchScope
, identifier
, searchContext
, true);
159 private void processFieldChanged(PsiField field
, PsiClass aClass
, final boolean isAccessibilityChange
) throws CacheCorruptedException
{
160 if (!isAccessibilityChange
&& field
.hasModifierProperty(PsiModifier
.PRIVATE
)) {
161 return; // optimization: don't need to search, cause may be used only in this class
163 Set
<PsiElement
> usages
= new HashSet
<PsiElement
>();
164 addUsages(field
, usages
, isAccessibilityChange
);
165 if (LOG
.isDebugEnabled()) {
166 LOG
.debug("++++++++++++++++++++++++++++++++++++++++++++++++");
167 LOG
.debug("Processing changed field: " + aClass
.getQualifiedName() + "." + field
.getName());
169 for (final PsiElement usage
: usages
) {
170 PsiClass ownerClass
= getOwnerClass(usage
);
171 if (LOG
.isDebugEnabled()) {
172 if (ownerClass
!= null) {
173 LOG
.debug("Usage " + usage
+ " found in class: " + ownerClass
.getQualifiedName());
176 LOG
.debug("Usage " + usage
+ " found in class: null");
179 if (ownerClass
!= null && !ownerClass
.equals(aClass
)) {
180 int qualifiedName
= myDependencyCache
.getSymbolTable().getId(ownerClass
.getQualifiedName());
181 // should force marking of the class no matter was it compiled or not
182 // This will ensure the class was recompiled _after_ all the constants get their new values
183 if (LOG
.isDebugEnabled()) {
184 LOG
.debug("Marking class id = [" + qualifiedName
+ "], name=[" + myDependencyCache
.resolve(qualifiedName
) + "]");
186 if (myDependencyCache
.markClass(qualifiedName
, true)) {
187 if (LOG
.isDebugEnabled()) {
188 LOG
.debug("Marked dependent class " + myDependencyCache
.resolve(qualifiedName
) + "; reason: constants changed in " +
189 myDependencyCache
.resolve(myQName
));
193 else if (ownerClass
== null) {
194 final PsiFile containingFile
= usage
.getContainingFile();
195 if (containingFile
!= null) {
196 final VirtualFile file
= containingFile
.getVirtualFile();
198 myDependencyCache
.markFile(file
);
203 if (LOG
.isDebugEnabled()) {
204 LOG
.debug("+++++++++++++++++++++++++++++++++++++++++++++++");
208 private void addUsages(PsiField psiField
, Collection
<PsiElement
> usages
, final boolean ignoreAccessScope
) {
209 Collection
<PsiReference
> references
= mySearcher
.findReferences(psiField
, ignoreAccessScope
)/*doFindReferences(searchHelper, psiField)*/;
210 for (final PsiReference ref
: references
) {
211 if (!(ref
instanceof PsiReferenceExpression
)) {
214 PsiElement e
= ref
.getElement();
216 PsiField ownerField
= getOwnerField(e
);
217 if (ownerField
!= null) {
218 if (ownerField
.hasModifierProperty(PsiModifier
.FINAL
)) {
219 PsiExpression initializer
= ownerField
.getInitializer();
220 if (initializer
!= null && PsiUtil
.isConstantExpression(initializer
)) {
221 // if the field depends on the compile-time-constant expression and is itself final
222 addUsages(ownerField
, usages
, ignoreAccessScope
);
230 private PsiReference[] doFindReferences(final PsiSearchHelper searchHelper, final PsiField psiField) {
231 final ProgressManager progressManager = ProgressManager.getInstance();
232 final ProgressIndicator currentProgress = progressManager.getProgressIndicator();
233 final PsiReference[][] references = new PsiReference[][] {null};
234 progressManager.runProcess(new Runnable() {
236 references[0] = searchHelper.findReferences(psiField, GlobalSearchScope.projectScope(myProject), false);
237 if (ENABLE_TRACING) {
238 System.out.println("Finding referencers for " + psiField);
241 }, new NonCancellableProgressAdapter(currentProgress));
242 return references[0];
246 private PsiField
getOwnerField(PsiElement element
) {
247 while (!(element
instanceof PsiFile
)) {
248 if (element
instanceof PsiClass
) {
251 if (element
instanceof PsiField
) { // top-level class
252 return (PsiField
)element
;
254 element
= element
.getParent();
259 private FieldChangeInfo
findChangeInfo(PsiField field
) throws CacheCorruptedException
{
260 String name
= field
.getName();
261 for (final FieldChangeInfo changeInfo
: myChangedFields
) {
262 if (name
.equals(myDependencyCache
.resolve(changeInfo
.fieldInfo
.getName()))) {
270 private static PsiClass
getOwnerClass(PsiElement element
) {
271 while (!(element
instanceof PsiFile
)) {
272 if (element
instanceof PsiClass
&& element
.getParent() instanceof PsiJavaFile
) { // top-level class
273 final PsiClass psiClass
= (PsiClass
)element
;
274 if (JspPsiUtil
.isInJspFile(psiClass
)) {
277 final PsiFile containingFile
= psiClass
.getContainingFile();
278 if (containingFile
== null) {
281 return StdLanguages
.JAVA
.equals(containingFile
.getLanguage())? psiClass
: null;
283 element
= element
.getParent();
288 public static class FieldChangeInfo
{
289 final FieldInfo fieldInfo
;
290 final boolean isAccessibilityChange
;
292 public FieldChangeInfo(final FieldInfo fieldId
) {
293 this(fieldId
, false);
296 public FieldChangeInfo(final FieldInfo fieldInfo
, final boolean accessibilityChange
) {
297 this.fieldInfo
= fieldInfo
;
298 isAccessibilityChange
= accessibilityChange
;
301 public boolean equals(Object o
) {
302 if (this == o
) return true;
303 if (o
== null || getClass() != o
.getClass()) return false;
305 final FieldChangeInfo fieldChangeInfo
= (FieldChangeInfo
)o
;
307 if (isAccessibilityChange
!= fieldChangeInfo
.isAccessibilityChange
) return false;
308 if (!fieldInfo
.equals(fieldChangeInfo
.fieldInfo
)) return false;
313 public int hashCode() {
315 result
= fieldInfo
.hashCode();
316 result
= 29 * result
+ (isAccessibilityChange ?
1 : 0);