invoke translating compilers per-module chunk
[fedora-idea.git] / java / compiler / impl / src / com / intellij / compiler / make / DependencyCache.java
blob7e9270ba30eba26acedd3dc468af630d0dfb608e
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 void markSourceRemoved(int qName) {
122 myClassesWithSourceRemoved.add(qName);
125 public void addClassToUpdate(int qName) {
126 myToUpdate.add(qName);
129 public int reparseClassFile(@NotNull File file, final byte[] fileContent) throws ClsFormatException, CacheCorruptedException {
130 SymbolTable symbolTable = getSymbolTable();
132 final int qName = getNewClassesCache().importClassInfo(new ClassFileReader(file, symbolTable, fileContent), symbolTable);
133 addClassToUpdate(qName);
134 addTraverseRoot(qName);
135 return qName;
138 // for profiling purposes
140 private static void pause() {
141 System.out.println("PAUSED. ENTER A CHAR.");
142 byte[] buf = new byte[1];
143 try {
144 System.in.read(buf);
146 catch (IOException e) {
147 e.printStackTrace();
152 public void update() throws CacheCorruptedException {
153 if (myToUpdate.isEmpty()) {
154 return; // optimization
157 final long updateStart = System.currentTimeMillis();
158 //pause();
160 final int[] namesToUpdate = myToUpdate.toArray();
161 final Cache cache = getCache();
162 final Cache newCache = getNewClassesCache();
163 final DependencyCacheNavigator navigator = getCacheNavigator();
165 // remove unnecesary dependencies
166 for (final int qName : namesToUpdate) {
167 // process use-dependencies
168 for (int referencedClassQName : cache.getReferencedClasses(qName)) {
169 if (!cache.containsClass(referencedClassQName)) {
170 continue;
172 cache.removeClassReferencer(referencedClassQName, qName);
174 cache.clearReferencedClasses(qName);
175 // process inheritance dependencies
176 navigator.walkSuperClasses(qName, new ClassInfoProcessor() {
177 public boolean process(int classQName) throws CacheCorruptedException {
178 cache.removeSubclass(classQName, qName);
179 return true;
184 // do update of classInfos
185 for (final int qName : namesToUpdate) {
186 cache.importClassInfo(newCache, qName);
189 // build forward-dependencies for the new infos, all new class infos must be already in the main cache!
191 final SymbolTable symbolTable = getSymbolTable();
193 for (final int qName : namesToUpdate) {
194 if (!newCache.containsClass(qName)) {
195 continue;
197 buildForwardDependencies(qName, newCache.getReferences(qName));
198 boolean isRemote = false;
199 // "remote objects" are classes that _directly_ implement remote interfaces
200 final int[] superInterfaces = cache.getSuperInterfaces(qName);
201 if (superInterfaces.length > 0) {
202 final int remoteInterfaceName = symbolTable.getId(REMOTE_INTERFACE_NAME);
203 for (int superInterface : superInterfaces) {
204 if (isRemoteInterface(cache, superInterface, remoteInterfaceName)) {
205 isRemote = true;
206 break;
210 final boolean wasRemote = cache.isRemote(qName);
211 if (wasRemote && !isRemote) {
212 synchronized (myPreviouslyRemoteClasses) {
213 myPreviouslyRemoteClasses.add(qName);
216 cache.setRemote(qName, isRemote);
219 // building subclass dependencies
220 for (final int qName : namesToUpdate) {
221 buildSubclassDependencies(getCache(), qName, qName);
224 for (final int qName : myClassesWithSourceRemoved.toArray()) {
225 cache.removeClass(qName);
227 myToUpdate = new TIntHashSet();
229 CompilerUtil.logDuration("Dependency cache update", System.currentTimeMillis() - updateStart);
230 //pause();
233 private void buildForwardDependencies(final int classQName, final Collection<ReferenceInfo> references) throws CacheCorruptedException {
234 final Cache cache = getCache();
236 final int genericSignature = cache.getGenericSignature(classQName);
237 if (genericSignature != -1) {
238 final String genericClassSignature = resolve(genericSignature);
239 final int[] bounds = findBounds(genericClassSignature);
240 for (int boundClassQName : bounds) {
241 cache.addClassReferencer(boundClassQName, classQName);
245 buildAnnotationDependencies(classQName, cache.getRuntimeVisibleAnnotations(classQName));
246 buildAnnotationDependencies(classQName, cache.getRuntimeInvisibleAnnotations(classQName));
248 for (final ReferenceInfo refInfo : references) {
249 final int declaringClassName = getActualDeclaringClassForReference(refInfo);
250 if (declaringClassName == Cache.UNKNOWN) {
251 continue;
253 if (refInfo instanceof MemberReferenceInfo) {
254 final MemberInfo memberInfo = ((MemberReferenceInfo)refInfo).getMemberInfo();
255 if (memberInfo instanceof FieldInfo) {
256 cache.addFieldReferencer(declaringClassName, memberInfo.getName(), classQName);
258 else if (memberInfo instanceof MethodInfo) {
259 cache.addMethodReferencer(declaringClassName, memberInfo.getName(), memberInfo.getDescriptor(), classQName);
261 else {
262 LOG.error("Unknown member info class: " + memberInfo.getClass().getName());
265 else { // reference to class
266 cache.addClassReferencer(declaringClassName, classQName);
269 final SymbolTable symbolTable = getSymbolTable();
271 for (final FieldInfo fieldInfo : cache.getFields(classQName)) {
272 buildAnnotationDependencies(classQName, fieldInfo.getRuntimeVisibleAnnotations());
273 buildAnnotationDependencies(classQName, fieldInfo.getRuntimeInvisibleAnnotations());
275 String className = MakeUtil.parseObjectType(symbolTable.getSymbol(fieldInfo.getDescriptor()), 0);
276 if (className == null) {
277 continue;
279 final int cls = symbolTable.getId(className);
280 cache.addClassReferencer(cls, classQName);
283 for (final MethodInfo methodInfo : cache.getMethods(classQName)) {
284 buildAnnotationDependencies(classQName, methodInfo.getRuntimeVisibleAnnotations());
285 buildAnnotationDependencies(classQName, methodInfo.getRuntimeInvisibleAnnotations());
286 buildAnnotationDependencies(classQName, methodInfo.getRuntimeVisibleParameterAnnotations());
287 buildAnnotationDependencies(classQName, methodInfo.getRuntimeInvisibleParameterAnnotations());
289 if (methodInfo.isConstructor()) {
290 continue;
293 final String returnTypeClassName = MakeUtil.parseObjectType(methodInfo.getReturnTypeDescriptor(symbolTable), 0);
294 if (returnTypeClassName != null) {
295 final int returnTypeClassQName = symbolTable.getId(returnTypeClassName);
296 cache.addClassReferencer(returnTypeClassQName, classQName);
299 String[] parameterSignatures = CacheUtils.getParameterSignatures(methodInfo, symbolTable);
300 for (String parameterSignature : parameterSignatures) {
301 String paramClassName = MakeUtil.parseObjectType(parameterSignature, 0);
302 if (paramClassName != null) {
303 final int paramClassId = symbolTable.getId(paramClassName);
304 cache.addClassReferencer(paramClassId, classQName);
310 private static boolean isRemoteInterface(Cache cache, int ifaceName, final int remoteInterfaceName) throws CacheCorruptedException {
311 if (ifaceName == remoteInterfaceName) {
312 return true;
314 for (int superInterfaceName : cache.getSuperInterfaces(ifaceName)) {
315 if (isRemoteInterface(cache, superInterfaceName, remoteInterfaceName)) {
316 return true;
319 return false;
323 private void buildAnnotationDependencies(int classQName, AnnotationConstantValue[][] annotations) throws CacheCorruptedException {
324 if (annotations == null || annotations.length == 0) {
325 return;
327 for (AnnotationConstantValue[] annotation : annotations) {
328 buildAnnotationDependencies(classQName, annotation);
332 private void buildAnnotationDependencies(int classQName, AnnotationConstantValue[] annotations) throws CacheCorruptedException {
333 if (annotations == null || annotations.length == 0) {
334 return;
336 final Cache cache = getCache();
337 for (AnnotationConstantValue annotation : annotations) {
338 final int annotationQName = annotation.getAnnotationQName();
340 cache.addClassReferencer(annotationQName, classQName);
342 final AnnotationNameValuePair[] memberValues = annotation.getMemberValues();
343 for (final AnnotationNameValuePair nameValuePair : memberValues) {
344 for (MethodInfo annotationMember : cache.findMethodsByName(annotationQName, nameValuePair.getName())) {
345 cache.addMethodReferencer(annotationQName, annotationMember.getName(), annotationMember.getDescriptor(), classQName);
351 private int[] findBounds(final String genericClassSignature) throws CacheCorruptedException{
352 try {
353 final String[] boundInterfaces = BoundsParser.getBounds(genericClassSignature);
354 int[] ids = ArrayUtil.newIntArray(boundInterfaces.length);
355 for (int i = 0; i < boundInterfaces.length; i++) {
356 ids[i] = getSymbolTable().getId(boundInterfaces[i]);
358 return ids;
360 catch (SignatureParsingException e) {
361 return ArrayUtil.EMPTY_INT_ARRAY;
365 // fixes JDK 1.4 javac bug that generates references in the constant pool
366 // to the subclass even if the field was declared in a superclass
367 private int getActualDeclaringClassForReference(final ReferenceInfo refInfo) throws CacheCorruptedException {
368 if (!(refInfo instanceof MemberReferenceInfo)) {
369 return refInfo.getClassName();
371 final int declaringClassName = refInfo.getClassName();
372 final Cache cache = getCache();
373 final MemberInfo memberInfo = ((MemberReferenceInfo)refInfo).getMemberInfo();
374 if (memberInfo instanceof FieldInfo) {
375 if (cache.findFieldByName(declaringClassName, memberInfo.getName()) != null) {
376 return declaringClassName;
379 else if (memberInfo instanceof MethodInfo) {
380 if (cache.findMethod(declaringClassName, memberInfo.getName(), memberInfo.getDescriptor()) != null) {
381 return declaringClassName;
384 final DeclaringClassFinder finder = new DeclaringClassFinder(memberInfo);
385 getCacheNavigator().walkSuperClasses(declaringClassName, finder);
386 return finder.getDeclaringClassName();
390 * @return qualified names of the classes that should be additionally recompiled
392 public Pair<int[], Set<VirtualFile>> findDependentClasses(CompileContext context, Project project, Set<VirtualFile> successfullyCompiled, @Nullable final DependencyProcessor additionalProcessor)
393 throws CacheCorruptedException {
395 markDependencies(context, project, successfullyCompiled, additionalProcessor);
396 return new Pair<int[], Set<VirtualFile>>(myMarkedInfos.toArray(), Collections.unmodifiableSet(myMarkedFiles));
399 private void markDependencies(CompileContext context, Project project, final Set<VirtualFile> successfullyCompiled,
400 @Nullable final DependencyProcessor additionalProcessor) throws CacheCorruptedException {
401 try {
402 if (LOG.isDebugEnabled()) {
403 LOG.debug("====================Marking dependent files=====================");
405 // myToUpdate can be modified during the mark procedure, so use toArray() to iterate it
406 final int[] traverseRoots = myTraverseRoots.toArray();
407 final SourceFileFinder sourceFileFinder = new SourceFileFinder(project, context);
408 final CachingSearcher searcher = new CachingSearcher(project);
409 final ChangedRetentionPolicyDependencyProcessor changedRetentionPolicyDependencyProcessor = new ChangedRetentionPolicyDependencyProcessor(project, searcher, this);
410 for (final int qName : traverseRoots) {
411 if (!getCache().containsClass(qName)) {
412 continue;
414 if (getNewClassesCache().containsClass(qName)) { // there is a new class file created
415 new JavaDependencyProcessor(project, this, qName).run();
416 ArrayList<ChangedConstantsDependencyProcessor.FieldChangeInfo> changed =
417 new ArrayList<ChangedConstantsDependencyProcessor.FieldChangeInfo>();
418 ArrayList<ChangedConstantsDependencyProcessor.FieldChangeInfo> removed =
419 new ArrayList<ChangedConstantsDependencyProcessor.FieldChangeInfo>();
420 findModifiedConstants(qName, changed, removed);
421 if (!changed.isEmpty() || !removed.isEmpty()) {
422 new ChangedConstantsDependencyProcessor(
423 project, searcher, this, qName,
424 changed.toArray(new ChangedConstantsDependencyProcessor.FieldChangeInfo[changed.size()]),
425 removed.toArray(new ChangedConstantsDependencyProcessor.FieldChangeInfo[removed.size()])
426 ).run();
428 changedRetentionPolicyDependencyProcessor.checkAnnotationRetentionPolicyChanges(qName);
429 if (additionalProcessor != null) {
430 additionalProcessor.processDependencies(context, qName);
433 else {
434 boolean isSourceDeleted = false;
435 if (myClassesWithSourceRemoved.contains(qName)) { // no recompiled class file, check whether the classfile exists
436 isSourceDeleted = true;
438 else if (!new File(getCache().getPath(qName)).exists()) {
439 final String qualifiedName = resolve(qName);
440 final String sourceFileName = getCache().getSourceFileName(qName);
441 final boolean markAsRemovedSource = ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
442 public Boolean compute() {
443 VirtualFile sourceFile = sourceFileFinder.findSourceFile(qualifiedName, sourceFileName);
444 return sourceFile == null || successfullyCompiled.contains(sourceFile) ? Boolean.TRUE : Boolean.FALSE;
446 }).booleanValue();
447 if (markAsRemovedSource) {
448 // for Inner classes: sourceFile may exist, but the inner class declaration inside it may not,
449 // thus the source for the class info should be considered removed
450 isSourceDeleted = true;
451 markSourceRemoved(qName);
452 myMarkedInfos.remove(qName); // if the info has been marked already, the mark should be removed
455 if (isSourceDeleted) {
456 Dependency[] backDependencies = getCache().getBackDependencies(qName);
457 for (Dependency backDependency : backDependencies) {
458 if (markTargetClassInfo(backDependency)) {
459 if (LOG.isDebugEnabled()) {
460 LOG.debug(
461 "Mark dependent class " + backDependency.getClassQualifiedName() + "; reason: no class file found for " + qName);
468 if (LOG.isDebugEnabled()) {
469 LOG.debug("================================================================");
472 catch (ProcessCanceledException ignored) {
473 // deliberately suppressed
477 private void findModifiedConstants(
478 final int qName,
479 Collection<ChangedConstantsDependencyProcessor.FieldChangeInfo> changedConstants,
480 Collection<ChangedConstantsDependencyProcessor.FieldChangeInfo> removedConstants) throws CacheCorruptedException {
482 final Cache cache = getCache();
483 for (final FieldInfo field : cache.getFields(qName)) {
484 final int oldFlags = field.getFlags();
485 if (ClsUtil.isStatic(oldFlags) && ClsUtil.isFinal(oldFlags)) {
486 final Cache newClassesCache = getNewClassesCache();
487 FieldInfo newField = newClassesCache.findFieldByName(qName, field.getName());
488 if (newField == null) {
489 if (!ConstantValue.EMPTY_CONSTANT_VALUE.equals(field.getConstantValue())) {
490 // if the field was really compile time constant
491 removedConstants.add(new ChangedConstantsDependencyProcessor.FieldChangeInfo(field));
494 else {
495 final boolean visibilityRestricted = MakeUtil.isMoreAccessible(oldFlags, newField.getFlags());
496 if (!field.getConstantValue().equals(newField.getConstantValue()) || visibilityRestricted) {
497 changedConstants.add(new ChangedConstantsDependencyProcessor.FieldChangeInfo(field, visibilityRestricted));
504 private static void buildSubclassDependencies(Cache cache, final int qName, int targetClassId) throws CacheCorruptedException {
505 final int superQName = cache.getSuperQualifiedName(targetClassId);
506 if (superQName != Cache.UNKNOWN) {
507 cache.addSubclass(superQName, qName);
508 buildSubclassDependencies(cache, qName, superQName);
511 int[] interfaces = cache.getSuperInterfaces(targetClassId);
512 for (final int interfaceName : interfaces) {
513 cache.addSubclass(interfaceName, qName);
514 buildSubclassDependencies(cache, qName, interfaceName);
520 * Marks ClassInfo targeted by the dependency
521 * @return true if really added, false otherwise
523 public boolean markTargetClassInfo(Dependency dependency) throws CacheCorruptedException {
524 return markClassInfo(dependency.getClassQualifiedName(), false);
528 * Marks ClassInfo that corresponds to the specified qualified name
529 * If class info is already recompiled, it is not marked
530 * @return true if really added, false otherwise
532 public boolean markClass(int qualifiedName) throws CacheCorruptedException {
533 return markClass(qualifiedName, false);
537 * Marks ClassInfo that corresponds to the specified qualified name
538 * If class info is already recompiled, it is not marked unless force parameter is true
539 * @return true if really added, false otherwise
541 public boolean markClass(int qualifiedName, boolean force) throws CacheCorruptedException {
542 return markClassInfo(qualifiedName, force);
545 public boolean isTargetClassInfoMarked(Dependency dependency) {
546 return isClassInfoMarked(dependency.getClassQualifiedName());
549 public boolean isClassInfoMarked(int qName) {
550 return myMarkedInfos.contains(qName);
553 public void markFile(VirtualFile file) {
554 myMarkedFiles.add(file);
558 * @return true if really marked, false otherwise
560 private boolean markClassInfo(int qName, boolean force) throws CacheCorruptedException {
561 if (!getCache().containsClass(qName)) {
562 return false;
564 if (myClassesWithSourceRemoved.contains(qName)) {
565 return false; // no need to recompile since source has been removed
567 if (!force) {
568 if (getNewClassesCache().containsClass(qName)) { // already recompiled
569 return false;
572 return myMarkedInfos.add(qName);
575 public void resetState() {
576 final long start = System.currentTimeMillis();
578 try {
579 myClassesWithSourceRemoved.clear();
580 myMarkedFiles.clear();
581 myMarkedInfos.clear();
582 myToUpdate.clear();
583 myTraverseRoots.clear();
584 if (myNewClassesCache != null) {
585 myNewClassesCache.wipe();
586 myNewClassesCache = null;
588 myCacheNavigator = null;
589 try {
590 if (myCache != null) {
591 myCache.dispose();
592 myCache = null;
595 catch (CacheCorruptedException e) {
596 LOG.info(e);
598 try {
599 if (mySymbolTable != null) {
600 mySymbolTable.dispose();
601 mySymbolTable = null;
604 catch (CacheCorruptedException e) {
605 LOG.info(e);
608 finally {
609 CompilerUtil.logDuration("Dependency cache disposal", System.currentTimeMillis() - start);
614 public SymbolTable getSymbolTable() throws CacheCorruptedException {
615 if (mySymbolTable == null) {
616 mySymbolTable = new SymbolTable(new File(mySymbolTableFilePath));
618 return mySymbolTable;
621 public String resolve(int id) throws CacheCorruptedException {
622 return getSymbolTable().getSymbol(id);
625 public boolean wasRemote(int qName) {
626 return myPreviouslyRemoteClasses.contains(qName);
629 private class DeclaringClassFinder implements ClassInfoProcessor {
630 private final int myMemberName;
631 private final int myMemberDescriptor;
632 private int myDeclaringClass = Cache.UNKNOWN;
633 private final boolean myIsField;
635 private DeclaringClassFinder(MemberInfo memberInfo) {
636 myMemberName = memberInfo.getName();
637 myMemberDescriptor = memberInfo.getDescriptor();
638 myIsField = memberInfo instanceof FieldInfo;
641 public int getDeclaringClassName() {
642 return myDeclaringClass;
645 public boolean process(int classQName) throws CacheCorruptedException {
646 final Cache cache = getCache();
647 if (myIsField) {
648 final FieldInfo fieldId = cache.findField(classQName, myMemberName, myMemberDescriptor);
649 if (fieldId != null) {
650 myDeclaringClass = classQName;
651 return false;
654 else {
655 final MethodInfo methodId = cache.findMethod(classQName, myMemberName, myMemberDescriptor);
656 if (methodId != null) {
657 myDeclaringClass = classQName;
658 return false;
661 return true;