annotation processors:
[fedora-idea.git] / java / compiler / impl / src / com / intellij / compiler / make / DependencyCache.java
blobad4610fa9ce8c241cd5210877d243d1f2a6dd004
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.
17 /**
18 * created at Jan 7, 2002
19 * @author Jeka
21 package com.intellij.compiler.make;
23 import com.intellij.compiler.SymbolTable;
24 import com.intellij.compiler.classParsing.*;
25 import com.intellij.compiler.impl.CompilerUtil;
26 import com.intellij.compiler.impl.javaCompiler.DependencyProcessor;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.compiler.CompileContext;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.progress.ProcessCanceledException;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.util.Computable;
33 import com.intellij.openapi.util.Pair;
34 import com.intellij.openapi.vfs.VirtualFile;
35 import com.intellij.util.ArrayUtil;
36 import com.intellij.util.cls.ClsFormatException;
37 import com.intellij.util.cls.ClsUtil;
38 import gnu.trove.TIntHashSet;
39 import org.jetbrains.annotations.NonNls;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
43 import java.io.File;
44 import java.io.IOException;
45 import java.rmi.Remote;
46 import java.util.*;
48 public class DependencyCache {
49 private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.make.DependencyCache");
51 private Cache myCache;
52 private Cache myNewClassesCache;
54 private static final String REMOTE_INTERFACE_NAME = Remote.class.getName();
55 private TIntHashSet myToUpdate = new TIntHashSet(); // qName strings to be updated.
56 private final TIntHashSet myTraverseRoots = new TIntHashSet(); // Dependencies are calculated from these clasess
57 private final TIntHashSet myClassesWithSourceRemoved = new TIntHashSet();
58 private final TIntHashSet myPreviouslyRemoteClasses = new TIntHashSet(); // classes that were Remote, but became non-Remote for some reason
59 private final TIntHashSet myMarkedInfos = new TIntHashSet(); // classes to be recompiled
60 private final Set<VirtualFile> myMarkedFiles = new HashSet<VirtualFile>();
62 private DependencyCacheNavigator myCacheNavigator;
63 private SymbolTable mySymbolTable;
64 private final String mySymbolTableFilePath;
65 private final String myStoreDirectoryPath;
66 @NonNls private static final String SYMBOLTABLE_FILE_NAME = "symboltable.dat";
68 public DependencyCache(@NonNls String storeDirectoryPath) {
69 myStoreDirectoryPath = storeDirectoryPath;
70 LOG.assertTrue(myStoreDirectoryPath != null);
72 mySymbolTableFilePath = myStoreDirectoryPath + "/" + SYMBOLTABLE_FILE_NAME;
75 public DependencyCacheNavigator getCacheNavigator() throws CacheCorruptedException {
76 if (myCacheNavigator == null) {
77 myCacheNavigator = new DependencyCacheNavigator(getCache());
79 return myCacheNavigator;
82 public void wipe() throws CacheCorruptedException {
83 getCache().wipe();
84 getNewClassesCache().wipe();
87 public Cache getCache() throws CacheCorruptedException {
88 try {
89 if (myCache == null) {
90 // base number of cached record views of each type
91 myCache = new Cache(myStoreDirectoryPath, 512);
94 return myCache;
96 catch (IOException e) {
97 throw new CacheCorruptedException(e);
101 public Cache getNewClassesCache() throws CacheCorruptedException {
102 try {
103 if (myNewClassesCache == null) {
104 myNewClassesCache = new Cache(myStoreDirectoryPath + "/tmp", 16);
106 return myNewClassesCache;
108 catch (IOException e) {
109 throw new CacheCorruptedException(e);
113 public void addTraverseRoot(int qName) {
114 myTraverseRoots.add(qName);
117 public void clearTraverseRoots() {
118 myTraverseRoots.clear();
121 public boolean hasUnprocessedTraverseRoots() {
122 return !myTraverseRoots.isEmpty();
125 public void markSourceRemoved(int qName) {
126 myClassesWithSourceRemoved.add(qName);
129 public void addClassToUpdate(int qName) {
130 myToUpdate.add(qName);
133 public int reparseClassFile(@NotNull File file, final byte[] fileContent) throws ClsFormatException, CacheCorruptedException {
134 SymbolTable symbolTable = getSymbolTable();
136 final int qName = getNewClassesCache().importClassInfo(new ClassFileReader(file, symbolTable, fileContent), symbolTable);
137 addClassToUpdate(qName);
138 addTraverseRoot(qName);
139 return qName;
142 // for profiling purposes
144 private static void pause() {
145 System.out.println("PAUSED. ENTER A CHAR.");
146 byte[] buf = new byte[1];
147 try {
148 System.in.read(buf);
150 catch (IOException e) {
151 e.printStackTrace();
156 public void update() throws CacheCorruptedException {
157 if (myToUpdate.isEmpty()) {
158 return; // optimization
161 final long updateStart = System.currentTimeMillis();
162 //pause();
164 final int[] namesToUpdate = myToUpdate.toArray();
165 final Cache cache = getCache();
166 final Cache newCache = getNewClassesCache();
167 final DependencyCacheNavigator navigator = getCacheNavigator();
169 // remove unnecesary dependencies
170 for (final int qName : namesToUpdate) {
171 // process use-dependencies
172 for (int referencedClassQName : cache.getReferencedClasses(qName)) {
173 if (!cache.containsClass(referencedClassQName)) {
174 continue;
176 cache.removeClassReferencer(referencedClassQName, qName);
178 cache.clearReferencedClasses(qName);
179 // process inheritance dependencies
180 navigator.walkSuperClasses(qName, new ClassInfoProcessor() {
181 public boolean process(int classQName) throws CacheCorruptedException {
182 cache.removeSubclass(classQName, qName);
183 return true;
188 // do update of classInfos
189 for (final int qName : namesToUpdate) {
190 cache.importClassInfo(newCache, qName);
193 // build forward-dependencies for the new infos, all new class infos must be already in the main cache!
195 final SymbolTable symbolTable = getSymbolTable();
197 for (final int qName : namesToUpdate) {
198 if (!newCache.containsClass(qName)) {
199 continue;
201 buildForwardDependencies(qName, newCache.getReferences(qName));
202 boolean isRemote = false;
203 // "remote objects" are classes that _directly_ implement remote interfaces
204 final int[] superInterfaces = cache.getSuperInterfaces(qName);
205 if (superInterfaces.length > 0) {
206 final int remoteInterfaceName = symbolTable.getId(REMOTE_INTERFACE_NAME);
207 for (int superInterface : superInterfaces) {
208 if (isRemoteInterface(cache, superInterface, remoteInterfaceName)) {
209 isRemote = true;
210 break;
214 final boolean wasRemote = cache.isRemote(qName);
215 if (wasRemote && !isRemote) {
216 synchronized (myPreviouslyRemoteClasses) {
217 myPreviouslyRemoteClasses.add(qName);
220 cache.setRemote(qName, isRemote);
223 // building subclass dependencies
224 for (final int qName : namesToUpdate) {
225 buildSubclassDependencies(getCache(), qName, qName);
228 for (final int qName : myClassesWithSourceRemoved.toArray()) {
229 cache.removeClass(qName);
231 myToUpdate = new TIntHashSet();
233 CompilerUtil.logDuration("Dependency cache update", System.currentTimeMillis() - updateStart);
234 //pause();
237 private void buildForwardDependencies(final int classQName, final Collection<ReferenceInfo> references) throws CacheCorruptedException {
238 final Cache cache = getCache();
240 final int genericSignature = cache.getGenericSignature(classQName);
241 if (genericSignature != -1) {
242 final String genericClassSignature = resolve(genericSignature);
243 final int[] bounds = findBounds(genericClassSignature);
244 for (int boundClassQName : bounds) {
245 cache.addClassReferencer(boundClassQName, classQName);
249 buildAnnotationDependencies(classQName, cache.getRuntimeVisibleAnnotations(classQName));
250 buildAnnotationDependencies(classQName, cache.getRuntimeInvisibleAnnotations(classQName));
252 for (final ReferenceInfo refInfo : references) {
253 final int declaringClassName = getActualDeclaringClassForReference(refInfo);
254 if (declaringClassName == Cache.UNKNOWN) {
255 continue;
257 if (refInfo instanceof MemberReferenceInfo) {
258 final MemberInfo memberInfo = ((MemberReferenceInfo)refInfo).getMemberInfo();
259 if (memberInfo instanceof FieldInfo) {
260 cache.addFieldReferencer(declaringClassName, memberInfo.getName(), classQName);
262 else if (memberInfo instanceof MethodInfo) {
263 cache.addMethodReferencer(declaringClassName, memberInfo.getName(), memberInfo.getDescriptor(), classQName);
265 else {
266 LOG.error("Unknown member info class: " + memberInfo.getClass().getName());
269 else { // reference to class
270 cache.addClassReferencer(declaringClassName, classQName);
273 final SymbolTable symbolTable = getSymbolTable();
275 for (final FieldInfo fieldInfo : cache.getFields(classQName)) {
276 buildAnnotationDependencies(classQName, fieldInfo.getRuntimeVisibleAnnotations());
277 buildAnnotationDependencies(classQName, fieldInfo.getRuntimeInvisibleAnnotations());
279 String className = MakeUtil.parseObjectType(symbolTable.getSymbol(fieldInfo.getDescriptor()), 0);
280 if (className == null) {
281 continue;
283 final int cls = symbolTable.getId(className);
284 cache.addClassReferencer(cls, classQName);
287 for (final MethodInfo methodInfo : cache.getMethods(classQName)) {
288 buildAnnotationDependencies(classQName, methodInfo.getRuntimeVisibleAnnotations());
289 buildAnnotationDependencies(classQName, methodInfo.getRuntimeInvisibleAnnotations());
290 buildAnnotationDependencies(classQName, methodInfo.getRuntimeVisibleParameterAnnotations());
291 buildAnnotationDependencies(classQName, methodInfo.getRuntimeInvisibleParameterAnnotations());
293 if (methodInfo.isConstructor()) {
294 continue;
297 final String returnTypeClassName = MakeUtil.parseObjectType(methodInfo.getReturnTypeDescriptor(symbolTable), 0);
298 if (returnTypeClassName != null) {
299 final int returnTypeClassQName = symbolTable.getId(returnTypeClassName);
300 cache.addClassReferencer(returnTypeClassQName, classQName);
303 String[] parameterSignatures = CacheUtils.getParameterSignatures(methodInfo, symbolTable);
304 for (String parameterSignature : parameterSignatures) {
305 String paramClassName = MakeUtil.parseObjectType(parameterSignature, 0);
306 if (paramClassName != null) {
307 final int paramClassId = symbolTable.getId(paramClassName);
308 cache.addClassReferencer(paramClassId, classQName);
314 private static boolean isRemoteInterface(Cache cache, int ifaceName, final int remoteInterfaceName) throws CacheCorruptedException {
315 if (ifaceName == remoteInterfaceName) {
316 return true;
318 for (int superInterfaceName : cache.getSuperInterfaces(ifaceName)) {
319 if (isRemoteInterface(cache, superInterfaceName, remoteInterfaceName)) {
320 return true;
323 return false;
327 private void buildAnnotationDependencies(int classQName, AnnotationConstantValue[][] annotations) throws CacheCorruptedException {
328 if (annotations == null || annotations.length == 0) {
329 return;
331 for (AnnotationConstantValue[] annotation : annotations) {
332 buildAnnotationDependencies(classQName, annotation);
336 private void buildAnnotationDependencies(int classQName, AnnotationConstantValue[] annotations) throws CacheCorruptedException {
337 if (annotations == null || annotations.length == 0) {
338 return;
340 final Cache cache = getCache();
341 for (AnnotationConstantValue annotation : annotations) {
342 final int annotationQName = annotation.getAnnotationQName();
344 cache.addClassReferencer(annotationQName, classQName);
346 final AnnotationNameValuePair[] memberValues = annotation.getMemberValues();
347 for (final AnnotationNameValuePair nameValuePair : memberValues) {
348 for (MethodInfo annotationMember : cache.findMethodsByName(annotationQName, nameValuePair.getName())) {
349 cache.addMethodReferencer(annotationQName, annotationMember.getName(), annotationMember.getDescriptor(), classQName);
355 private int[] findBounds(final String genericClassSignature) throws CacheCorruptedException{
356 try {
357 final String[] boundInterfaces = BoundsParser.getBounds(genericClassSignature);
358 int[] ids = ArrayUtil.newIntArray(boundInterfaces.length);
359 for (int i = 0; i < boundInterfaces.length; i++) {
360 ids[i] = getSymbolTable().getId(boundInterfaces[i]);
362 return ids;
364 catch (SignatureParsingException e) {
365 return ArrayUtil.EMPTY_INT_ARRAY;
369 // fixes JDK 1.4 javac bug that generates references in the constant pool
370 // to the subclass even if the field was declared in a superclass
371 private int getActualDeclaringClassForReference(final ReferenceInfo refInfo) throws CacheCorruptedException {
372 if (!(refInfo instanceof MemberReferenceInfo)) {
373 return refInfo.getClassName();
375 final int declaringClassName = refInfo.getClassName();
376 final Cache cache = getCache();
377 final MemberInfo memberInfo = ((MemberReferenceInfo)refInfo).getMemberInfo();
378 if (memberInfo instanceof FieldInfo) {
379 if (cache.findFieldByName(declaringClassName, memberInfo.getName()) != null) {
380 return declaringClassName;
383 else if (memberInfo instanceof MethodInfo) {
384 if (cache.findMethod(declaringClassName, memberInfo.getName(), memberInfo.getDescriptor()) != null) {
385 return declaringClassName;
388 final DeclaringClassFinder finder = new DeclaringClassFinder(memberInfo);
389 getCacheNavigator().walkSuperClasses(declaringClassName, finder);
390 return finder.getDeclaringClassName();
394 * @return qualified names of the classes that should be additionally recompiled
396 public Pair<int[], Set<VirtualFile>> findDependentClasses(CompileContext context, Project project, Set<VirtualFile> successfullyCompiled, @Nullable final DependencyProcessor additionalProcessor)
397 throws CacheCorruptedException {
399 markDependencies(context, project, successfullyCompiled, additionalProcessor);
400 return new Pair<int[], Set<VirtualFile>>(myMarkedInfos.toArray(), Collections.unmodifiableSet(myMarkedFiles));
403 private void markDependencies(CompileContext context, Project project, final Set<VirtualFile> successfullyCompiled,
404 @Nullable final DependencyProcessor additionalProcessor) throws CacheCorruptedException {
405 try {
406 if (LOG.isDebugEnabled()) {
407 LOG.debug("====================Marking dependent files=====================");
409 // myToUpdate can be modified during the mark procedure, so use toArray() to iterate it
410 final int[] traverseRoots = myTraverseRoots.toArray();
411 final SourceFileFinder sourceFileFinder = new SourceFileFinder(project, context);
412 final CachingSearcher searcher = new CachingSearcher(project);
413 final ChangedRetentionPolicyDependencyProcessor changedRetentionPolicyDependencyProcessor = new ChangedRetentionPolicyDependencyProcessor(project, searcher, this);
414 for (final int qName : traverseRoots) {
415 if (!getCache().containsClass(qName)) {
416 continue;
418 if (getNewClassesCache().containsClass(qName)) { // there is a new class file created
419 new JavaDependencyProcessor(project, this, qName).run();
420 ArrayList<ChangedConstantsDependencyProcessor.FieldChangeInfo> changed =
421 new ArrayList<ChangedConstantsDependencyProcessor.FieldChangeInfo>();
422 ArrayList<ChangedConstantsDependencyProcessor.FieldChangeInfo> removed =
423 new ArrayList<ChangedConstantsDependencyProcessor.FieldChangeInfo>();
424 findModifiedConstants(qName, changed, removed);
425 if (!changed.isEmpty() || !removed.isEmpty()) {
426 new ChangedConstantsDependencyProcessor(
427 project, searcher, this, qName,
428 changed.toArray(new ChangedConstantsDependencyProcessor.FieldChangeInfo[changed.size()]),
429 removed.toArray(new ChangedConstantsDependencyProcessor.FieldChangeInfo[removed.size()])
430 ).run();
432 changedRetentionPolicyDependencyProcessor.checkAnnotationRetentionPolicyChanges(qName);
433 if (additionalProcessor != null) {
434 additionalProcessor.processDependencies(context, qName);
437 else {
438 boolean isSourceDeleted = false;
439 if (myClassesWithSourceRemoved.contains(qName)) { // no recompiled class file, check whether the classfile exists
440 isSourceDeleted = true;
442 else if (!new File(getCache().getPath(qName)).exists()) {
443 final String qualifiedName = resolve(qName);
444 final String sourceFileName = getCache().getSourceFileName(qName);
445 final boolean markAsRemovedSource = ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
446 public Boolean compute() {
447 VirtualFile sourceFile = sourceFileFinder.findSourceFile(qualifiedName, sourceFileName);
448 return sourceFile == null || successfullyCompiled.contains(sourceFile) ? Boolean.TRUE : Boolean.FALSE;
450 }).booleanValue();
451 if (markAsRemovedSource) {
452 // for Inner classes: sourceFile may exist, but the inner class declaration inside it may not,
453 // thus the source for the class info should be considered removed
454 isSourceDeleted = true;
455 markSourceRemoved(qName);
456 myMarkedInfos.remove(qName); // if the info has been marked already, the mark should be removed
459 if (isSourceDeleted) {
460 Dependency[] backDependencies = getCache().getBackDependencies(qName);
461 for (Dependency backDependency : backDependencies) {
462 if (markTargetClassInfo(backDependency)) {
463 if (LOG.isDebugEnabled()) {
464 LOG.debug(
465 "Mark dependent class " + backDependency.getClassQualifiedName() + "; reason: no class file found for " + qName);
472 if (LOG.isDebugEnabled()) {
473 LOG.debug("================================================================");
476 catch (ProcessCanceledException ignored) {
477 // deliberately suppressed
481 private void findModifiedConstants(
482 final int qName,
483 Collection<ChangedConstantsDependencyProcessor.FieldChangeInfo> changedConstants,
484 Collection<ChangedConstantsDependencyProcessor.FieldChangeInfo> removedConstants) throws CacheCorruptedException {
486 final Cache cache = getCache();
487 for (final FieldInfo field : cache.getFields(qName)) {
488 final int oldFlags = field.getFlags();
489 if (ClsUtil.isStatic(oldFlags) && ClsUtil.isFinal(oldFlags)) {
490 final Cache newClassesCache = getNewClassesCache();
491 FieldInfo newField = newClassesCache.findFieldByName(qName, field.getName());
492 if (newField == null) {
493 if (!ConstantValue.EMPTY_CONSTANT_VALUE.equals(field.getConstantValue())) {
494 // if the field was really compile time constant
495 removedConstants.add(new ChangedConstantsDependencyProcessor.FieldChangeInfo(field));
498 else {
499 final boolean visibilityRestricted = MakeUtil.isMoreAccessible(oldFlags, newField.getFlags());
500 if (!field.getConstantValue().equals(newField.getConstantValue()) || visibilityRestricted) {
501 changedConstants.add(new ChangedConstantsDependencyProcessor.FieldChangeInfo(field, visibilityRestricted));
508 private static void buildSubclassDependencies(Cache cache, final int qName, int targetClassId) throws CacheCorruptedException {
509 final int superQName = cache.getSuperQualifiedName(targetClassId);
510 if (superQName != Cache.UNKNOWN) {
511 cache.addSubclass(superQName, qName);
512 buildSubclassDependencies(cache, qName, superQName);
515 int[] interfaces = cache.getSuperInterfaces(targetClassId);
516 for (final int interfaceName : interfaces) {
517 cache.addSubclass(interfaceName, qName);
518 buildSubclassDependencies(cache, qName, interfaceName);
524 * Marks ClassInfo targeted by the dependency
525 * @return true if really added, false otherwise
527 public boolean markTargetClassInfo(Dependency dependency) throws CacheCorruptedException {
528 return markClassInfo(dependency.getClassQualifiedName(), false);
532 * Marks ClassInfo that corresponds to the specified qualified name
533 * If class info is already recompiled, it is not marked
534 * @return true if really added, false otherwise
536 public boolean markClass(int qualifiedName) throws CacheCorruptedException {
537 return markClass(qualifiedName, false);
541 * Marks ClassInfo that corresponds to the specified qualified name
542 * If class info is already recompiled, it is not marked unless force parameter is true
543 * @return true if really added, false otherwise
545 public boolean markClass(int qualifiedName, boolean force) throws CacheCorruptedException {
546 return markClassInfo(qualifiedName, force);
549 public boolean isTargetClassInfoMarked(Dependency dependency) {
550 return isClassInfoMarked(dependency.getClassQualifiedName());
553 public boolean isClassInfoMarked(int qName) {
554 return myMarkedInfos.contains(qName);
557 public void markFile(VirtualFile file) {
558 myMarkedFiles.add(file);
562 * @return true if really marked, false otherwise
564 private boolean markClassInfo(int qName, boolean force) throws CacheCorruptedException {
565 if (!getCache().containsClass(qName)) {
566 return false;
568 if (myClassesWithSourceRemoved.contains(qName)) {
569 return false; // no need to recompile since source has been removed
571 if (!force) {
572 if (getNewClassesCache().containsClass(qName)) { // already recompiled
573 return false;
576 return myMarkedInfos.add(qName);
579 public void resetState() {
580 final long start = System.currentTimeMillis();
582 try {
583 myClassesWithSourceRemoved.clear();
584 myMarkedFiles.clear();
585 myMarkedInfos.clear();
586 myToUpdate.clear();
587 myTraverseRoots.clear();
588 if (myNewClassesCache != null) {
589 myNewClassesCache.wipe();
590 myNewClassesCache = null;
592 myCacheNavigator = null;
593 try {
594 if (myCache != null) {
595 myCache.dispose();
596 myCache = null;
599 catch (CacheCorruptedException e) {
600 LOG.info(e);
602 try {
603 if (mySymbolTable != null) {
604 mySymbolTable.dispose();
605 mySymbolTable = null;
608 catch (CacheCorruptedException e) {
609 LOG.info(e);
612 finally {
613 CompilerUtil.logDuration("Dependency cache disposal", System.currentTimeMillis() - start);
618 public SymbolTable getSymbolTable() throws CacheCorruptedException {
619 if (mySymbolTable == null) {
620 mySymbolTable = new SymbolTable(new File(mySymbolTableFilePath));
622 return mySymbolTable;
625 public String resolve(int id) throws CacheCorruptedException {
626 return getSymbolTable().getSymbol(id);
629 public boolean wasRemote(int qName) {
630 return myPreviouslyRemoteClasses.contains(qName);
633 private class DeclaringClassFinder implements ClassInfoProcessor {
634 private final int myMemberName;
635 private final int myMemberDescriptor;
636 private int myDeclaringClass = Cache.UNKNOWN;
637 private final boolean myIsField;
639 private DeclaringClassFinder(MemberInfo memberInfo) {
640 myMemberName = memberInfo.getName();
641 myMemberDescriptor = memberInfo.getDescriptor();
642 myIsField = memberInfo instanceof FieldInfo;
645 public int getDeclaringClassName() {
646 return myDeclaringClass;
649 public boolean process(int classQName) throws CacheCorruptedException {
650 final Cache cache = getCache();
651 if (myIsField) {
652 final FieldInfo fieldId = cache.findField(classQName, myMemberName, myMemberDescriptor);
653 if (fieldId != null) {
654 myDeclaringClass = classQName;
655 return false;
658 else {
659 final MethodInfo methodId = cache.findMethod(classQName, myMemberName, myMemberDescriptor);
660 if (methodId != null) {
661 myDeclaringClass = classQName;
662 return false;
665 return true;