diff options
Diffstat (limited to 'java/java-impl/src/com/intellij/compilerOutputIndex/api')
9 files changed, 926 insertions, 0 deletions
diff --git a/java/java-impl/src/com/intellij/compilerOutputIndex/api/descriptor/ArrayListKeyDescriptor.java b/java/java-impl/src/com/intellij/compilerOutputIndex/api/descriptor/ArrayListKeyDescriptor.java new file mode 100644 index 000000000000..0f2d3de6e0a0 --- /dev/null +++ b/java/java-impl/src/com/intellij/compilerOutputIndex/api/descriptor/ArrayListKeyDescriptor.java @@ -0,0 +1,50 @@ +package com.intellij.compilerOutputIndex.api.descriptor; + +import com.intellij.util.io.DataExternalizer; +import com.intellij.util.io.KeyDescriptor; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Dmitry Batkovich <dmitry.batkovich@jetbrains.com> + */ +public class ArrayListKeyDescriptor<E> implements KeyDescriptor<List<E>> { + + private final DataExternalizer<E> myDataExternalizer; + + public ArrayListKeyDescriptor(final DataExternalizer<E> dataExternalizer) { + myDataExternalizer = dataExternalizer; + } + + @Override + public void save(final DataOutput out, final List<E> list) throws IOException { + out.writeInt(list.size()); + for (final E element : list) { + myDataExternalizer.save(out, element); + } + } + + @Override + public ArrayList<E> read(final DataInput in) throws IOException { + final int size = in.readInt(); + final ArrayList<E> list = new ArrayList<E>(size); + for (int i = 0; i < size; i++) { + list.add(myDataExternalizer.read(in)); + } + return list; + } + + @Override + public int getHashCode(final List<E> value) { + return value.hashCode(); + } + + @Override + public boolean isEqual(final List<E> val1, final List<E> val2) { + return val1.equals(val2); + } +} diff --git a/java/java-impl/src/com/intellij/compilerOutputIndex/api/descriptor/HashMapKeyDescriptor.java b/java/java-impl/src/com/intellij/compilerOutputIndex/api/descriptor/HashMapKeyDescriptor.java new file mode 100644 index 000000000000..1f012dd68a44 --- /dev/null +++ b/java/java-impl/src/com/intellij/compilerOutputIndex/api/descriptor/HashMapKeyDescriptor.java @@ -0,0 +1,54 @@ +package com.intellij.compilerOutputIndex.api.descriptor; + +import com.intellij.util.io.DataExternalizer; +import com.intellij.util.io.KeyDescriptor; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Dmitry Batkovich + */ +public class HashMapKeyDescriptor<K, V> implements KeyDescriptor<Map<K, V>> { + + private final DataExternalizer<K> myKeyDataExternalizer; + private final DataExternalizer<V> myValueDataExternalizer; + + public HashMapKeyDescriptor(final DataExternalizer<K> keyDataExternalizer, final DataExternalizer<V> valueDataExternalizer) { + myKeyDataExternalizer = keyDataExternalizer; + myValueDataExternalizer = valueDataExternalizer; + } + + @Override + public void save(final DataOutput out, final Map<K, V> map) throws IOException { + final int size = map.size(); + out.writeInt(size); + for (final Map.Entry<K, V> e : map.entrySet()) { + myKeyDataExternalizer.save(out, e.getKey()); + myValueDataExternalizer.save(out, e.getValue()); + } + } + + @Override + public Map<K, V> read(final DataInput in) throws IOException { + final int size = in.readInt(); + final HashMap<K, V> map = new HashMap<K, V>(size); + for (int i = 0; i < size; i++) { + map.put(myKeyDataExternalizer.read(in), myValueDataExternalizer.read(in)); + } + return map; + } + + @Override + public int getHashCode(final Map<K, V> map) { + return map.hashCode(); + } + + @Override + public boolean isEqual(final Map<K, V> val1, final Map<K, V> val2) { + return val1.equals(val2); + } +} diff --git a/java/java-impl/src/com/intellij/compilerOutputIndex/api/descriptor/HashSetKeyDescriptor.java b/java/java-impl/src/com/intellij/compilerOutputIndex/api/descriptor/HashSetKeyDescriptor.java new file mode 100644 index 000000000000..1201ccf98446 --- /dev/null +++ b/java/java-impl/src/com/intellij/compilerOutputIndex/api/descriptor/HashSetKeyDescriptor.java @@ -0,0 +1,54 @@ +package com.intellij.compilerOutputIndex.api.descriptor; + +import com.intellij.util.io.DataExternalizer; +import com.intellij.util.io.KeyDescriptor; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Dmitry Batkovich + */ +public class HashSetKeyDescriptor<K> implements KeyDescriptor<Set<K>> { + + private final DataExternalizer<K> keyDataExternalizer; + + public HashSetKeyDescriptor(final DataExternalizer<K> keyDataExternalizer) { + this.keyDataExternalizer = keyDataExternalizer; + } + + @Override + public void save(final DataOutput out, final Set<K> set) throws IOException { + out.writeInt(set.size()); + for (final K k : set) { + keyDataExternalizer.save(out, k); + } + } + + @Override + public HashSet<K> read(final DataInput in) throws IOException { + final int size = in.readInt(); + final HashSet<K> set = new HashSet<K>(size); + for (int i = 0; i < size; i++) { + set.add(keyDataExternalizer.read(in)); + } + return set; + } + + public static <K> HashSetKeyDescriptor<K> of(final DataExternalizer<K> keyDataExternalizer) { + return new HashSetKeyDescriptor<K>(keyDataExternalizer); + } + + @Override + public int getHashCode(final Set<K> value) { + return value.hashCode(); + } + + @Override + public boolean isEqual(final Set<K> val1, final Set<K> val2) { + return val1.equals(val2); + } +} diff --git a/java/java-impl/src/com/intellij/compilerOutputIndex/api/fs/AsmUtil.java b/java/java-impl/src/com/intellij/compilerOutputIndex/api/fs/AsmUtil.java new file mode 100644 index 000000000000..45f761e3203f --- /dev/null +++ b/java/java-impl/src/com/intellij/compilerOutputIndex/api/fs/AsmUtil.java @@ -0,0 +1,129 @@ +package com.intellij.compilerOutputIndex.api.fs; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.asm4.Opcodes; +import org.jetbrains.asm4.Type; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Dmitry Batkovich <dmitry.batkovich@jetbrains.com> + */ +public final class AsmUtil implements Opcodes { + + private AsmUtil() {} + + public static boolean isStaticMethodDeclaration(final int access) { + return (access & Opcodes.ACC_STATIC) != 0; + } + + public static boolean isStaticMethodInvocation(final int opcode) { + return opcode == Opcodes.INVOKESTATIC; + } + + public static String getQualifiedClassName(final String name) { + return asJavaInnerClassQName(Type.getObjectType(name).getClassName()); + } + + public static String getReturnType(final String desc) { + return asJavaInnerClassQName(Type.getReturnType(desc).getClassName()); + } + + public static String[] getQualifiedClassNames(final String[] classNames, final String... yetAnotherClassNames) { + final List<String> qualifiedClassNames = new ArrayList<String>(classNames.length + yetAnotherClassNames.length); + for (final String className : classNames) { + qualifiedClassNames.add(getQualifiedClassName(className)); + } + for (final String className : yetAnotherClassNames) { + if (className != null) { + qualifiedClassNames.add(getQualifiedClassName(className)); + } + } + return ArrayUtil.toStringArray(qualifiedClassNames); + } + + public static String[] getParamsTypes(final String desc) { + final Type[] types = Type.getArgumentTypes(desc); + final String[] typesAsString = new String[types.length]; + for (int i = 0; i < types.length; i++) { + typesAsString[i] = types[i].getClassName(); + } + return typesAsString; + } + + @Nullable + public static String getSignature(final PsiMethod psiMethod) { + final PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); + final StringBuilder sb = new StringBuilder(); + sb.append("("); + for (final PsiParameter p : parameters) { + final String desc = getDescriptor(p); + if (desc == null) { + return null; + } + sb.append(desc); + } + sb.append(")"); + final String desc = getDescriptor(psiMethod.getReturnType()); + if (desc == null) { + return null; + } + sb.append(desc); + return sb.toString(); + } + + @Nullable + private static String getDescriptor(final PsiParameter parameter) { + return getDescriptor(parameter.getType()); + } + + @Nullable + private static String getDescriptor(@Nullable final PsiType type) { + if (type == null) { + return null; + } + if (type instanceof PsiPrimitiveType) { + final PsiPrimitiveType primitiveType = (PsiPrimitiveType) type; + if (PsiType.INT.equals(primitiveType)) { + return "I"; + } else if (primitiveType.equals(PsiType.VOID)) { + return "V"; + } else if (primitiveType.equals(PsiType.BOOLEAN)) { + return "Z"; + } else if (primitiveType.equals(PsiType.BYTE)) { + return "B"; + } else if (primitiveType.equals(PsiType.CHAR)) { + return "C"; + } else if (primitiveType.equals(PsiType.SHORT)) { + return "S"; + } else if (primitiveType.equals(PsiType.DOUBLE)) { + return "D"; + } else if (primitiveType.equals(PsiType.FLOAT)) { + return "F"; + } else /* if (primitiveType.equals(PsiType.LONG)) */ { + return "J"; + } + } else if (type instanceof PsiArrayType) { + return "[" + getDescriptor(((PsiArrayType) type).getComponentType()); + } else { + final PsiClassType classType = (PsiClassType) type; + final PsiClass aClass = classType.resolve(); + if (aClass == null) { + return null; + } + final String qName = aClass.getQualifiedName(); + if (qName == null) { + return null; + } + return "L" + StringUtil.replace(qName, ".", "/") + ";"; + } + } + + private static String asJavaInnerClassQName(final String byteCodeClassQName) { + return StringUtil.replaceChar(byteCodeClassQName, '$', '.'); + } +} diff --git a/java/java-impl/src/com/intellij/compilerOutputIndex/api/fs/CompilerOutputFilesUtil.java b/java/java-impl/src/com/intellij/compilerOutputIndex/api/fs/CompilerOutputFilesUtil.java new file mode 100644 index 000000000000..ef4137079f95 --- /dev/null +++ b/java/java-impl/src/com/intellij/compilerOutputIndex/api/fs/CompilerOutputFilesUtil.java @@ -0,0 +1,64 @@ +package com.intellij.compilerOutputIndex.api.fs; + +import com.intellij.openapi.compiler.CompilerPaths; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Dmitry Batkovich <dmitry.batkovich@jetbrains.com> + */ +public final class CompilerOutputFilesUtil { + + private CompilerOutputFilesUtil() {} + + public final static String CLASS_FILES_SUFFIX = ".class"; + + public static void iterateProjectClassFiles(@NotNull final Project project, @NotNull final Consumer<File> fileConsumer) { + for (final Module module : ModuleManager.getInstance(project).getModules()) { + iterateModuleClassFiles(module, fileConsumer); + } + } + + public static void iterateModuleClassFiles(@NotNull final Module module, @NotNull final Consumer<File> fileConsumer) { + final VirtualFile moduleOutputDirectory = CompilerPaths.getModuleOutputDirectory(module, false); + if (moduleOutputDirectory == null) { + return; + } + final String canonicalPath = moduleOutputDirectory.getCanonicalPath(); + if (canonicalPath == null) { + return; + } + final File root = new File(canonicalPath); + iterateClassFilesOverRoot(root, fileConsumer); + } + + public static void iterateClassFilesOverRoot(@NotNull final File file, final Consumer<File> fileConsumer) { + iterateClassFilesOverRoot(file, fileConsumer, new HashSet<File>()); + } + + private static void iterateClassFilesOverRoot(@NotNull final File file, final Consumer<File> fileConsumer, final Set<File> visited) { + if (file.isDirectory()) { + final File[] files = file.listFiles(); + if (files != null) { + for (final File childFile : files) { + if (visited.add(childFile)) { + iterateClassFilesOverRoot(childFile.getAbsoluteFile(), fileConsumer, visited); + } + } + } + } + else { + if (file.getName().endsWith(CLASS_FILES_SUFFIX)) { + fileConsumer.consume(file); + } + } + } +} diff --git a/java/java-impl/src/com/intellij/compilerOutputIndex/api/fs/FileVisitorService.java b/java/java-impl/src/com/intellij/compilerOutputIndex/api/fs/FileVisitorService.java new file mode 100644 index 000000000000..9a7f00be3dc3 --- /dev/null +++ b/java/java-impl/src/com/intellij/compilerOutputIndex/api/fs/FileVisitorService.java @@ -0,0 +1,53 @@ +package com.intellij.compilerOutputIndex.api.fs; + +import com.intellij.openapi.project.Project; +import com.intellij.util.Consumer; + +import java.io.File; + +/** + * @author Dmitry Batkovich <dmitry.batkovich@jetbrains.com> + */ +public interface FileVisitorService { + + interface Visitor { + void visit(File file); + } + + void visit(final Consumer<File> visitor); + + class ProjectClassFiles implements FileVisitorService { + private final Project myProject; + + public ProjectClassFiles(final Project project) { + myProject = project; + } + + @Override + public void visit(final Consumer<File> visitor) { + CompilerOutputFilesUtil.iterateProjectClassFiles(myProject, visitor); + } + } + + class DirectoryClassFiles implements FileVisitorService { + private final File myDir; + + public DirectoryClassFiles(final File dir) { + if (!dir.isDirectory()) { + throw new IllegalArgumentException(); + } + myDir = dir; + } + + @Override + public void visit(final Consumer<File> visitor) { + //noinspection ConstantConditions + for (final File file : myDir.listFiles()) { + if (file.getName().endsWith(CompilerOutputFilesUtil.CLASS_FILES_SUFFIX)) { + visitor.consume(file); + } + } + } + } +} + diff --git a/java/java-impl/src/com/intellij/compilerOutputIndex/api/indexer/CompilerOutputBaseIndex.java b/java/java-impl/src/com/intellij/compilerOutputIndex/api/indexer/CompilerOutputBaseIndex.java new file mode 100644 index 000000000000..a37368b03d86 --- /dev/null +++ b/java/java-impl/src/com/intellij/compilerOutputIndex/api/indexer/CompilerOutputBaseIndex.java @@ -0,0 +1,141 @@ +package com.intellij.compilerOutputIndex.api.indexer; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Factory; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.indexing.*; +import com.intellij.util.io.DataExternalizer; +import com.intellij.util.io.KeyDescriptor; +import com.intellij.util.io.PersistentHashMap; +import org.jetbrains.asm4.ClassReader; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +import static com.intellij.util.indexing.IndexInfrastructure.*; + +/** + * @author Dmitry Batkovich + */ +public abstract class CompilerOutputBaseIndex<K, V> { + public final static ExtensionPointName<CompilerOutputBaseIndex> EXTENSION_POINT_NAME = + ExtensionPointName.create("com.intellij.java.compilerOutputIndex"); + + private final static Logger LOG = Logger.getInstance(CompilerOutputBaseIndex.class); + private final KeyDescriptor<K> myKeyDescriptor; + private final DataExternalizer<V> myValueExternalizer; + + protected volatile MapReduceIndex<K, V, ClassReader> myIndex; + + private volatile Project myProject; + + public CompilerOutputBaseIndex(final KeyDescriptor<K> keyDescriptor, final DataExternalizer<V> valueExternalizer) { + myKeyDescriptor = keyDescriptor; + myValueExternalizer = valueExternalizer; + } + + public final boolean init(final Project project) { + myProject = project; + final MapReduceIndex<K, V, ClassReader> index; + final Ref<Boolean> rewriteIndex = new Ref<Boolean>(false); + try { + final ID<K, V> indexId = getIndexId(); + if (!IndexInfrastructure.getIndexRootDir(indexId).exists()) { + rewriteIndex.set(true); + } + final File storageFile = IndexInfrastructure.getStorageFile(indexId); + final MapIndexStorage<K, V> indexStorage = new MapIndexStorage<K, V>(storageFile, myKeyDescriptor, myValueExternalizer, 1024); + index = new MapReduceIndex<K, V, ClassReader>(indexId, getIndexer(), indexStorage); + index.setInputIdToDataKeysIndex(new Factory<PersistentHashMap<Integer, Collection<K>>>() { + @Override + public PersistentHashMap<Integer, Collection<K>> create() { + Exception failCause = null; + for (int attempts = 0; attempts < 2; attempts++) { + try { + return FileBasedIndexImpl.createIdToDataKeysIndex(indexId, myKeyDescriptor, new MemoryIndexStorage<K, V>(indexStorage)); + } + catch (IOException e) { + failCause = e; + FileUtil.delete(IndexInfrastructure.getInputIndexStorageFile(getIndexId())); + rewriteIndex.set(true); + } + } + throw new RuntimeException("couldn't create index", failCause); + } + }); + final File versionFile = getVersionFile(indexId); + if (versionFile.exists()) { + if (versionDiffers(versionFile, getVersion())) { + rewriteVersion(versionFile, getVersion()); + rewriteIndex.set(true); + try { + LOG.info("clearing index for updating index version"); + index.clear(); + } + catch (StorageException e) { + LOG.error("couldn't clear index for reinitializing"); + throw new RuntimeException(e); + } + } + } + else if (versionFile.createNewFile()) { + rewriteVersion(versionFile, getVersion()); + rewriteIndex.set(true); + } + else { + LOG.error(String.format("problems while access to index version file to index %s ", indexId)); + } + } + catch (IOException e) { + LOG.error("couldn't initialize index"); + throw new RuntimeException(e); + } + myIndex = index; + return rewriteIndex.get(); + } + + protected abstract ID<K, V> getIndexId(); + + protected abstract int getVersion(); + + protected abstract DataIndexer<K, V, ClassReader> getIndexer(); + + public final void projectClosed() { + if (myIndex != null) { + try { + myIndex.flush(); + } + catch (StorageException ignored) { + } + myIndex.dispose(); + } + } + + public void update(final int id, final ClassReader classReader) { + Boolean result = myIndex.update(id, classReader).compute(); + if (result == Boolean.FALSE) throw new RuntimeException(); + } + + public void clear() { + try { + myIndex.clear(); + } + catch (StorageException e) { + throw new RuntimeException(e); + } + } + + protected final ID<K, V> generateIndexId(final String indexName) { + return CompilerOutputIndexUtil.generateIndexId(indexName, myProject); + } + + protected final ID<K, V> generateIndexId(final Class aClass) { + final String className = StringUtil.getShortName(aClass); + return generateIndexId(StringUtil.trimEnd(className, "Index")); + } +} diff --git a/java/java-impl/src/com/intellij/compilerOutputIndex/api/indexer/CompilerOutputIndexUtil.java b/java/java-impl/src/com/intellij/compilerOutputIndex/api/indexer/CompilerOutputIndexUtil.java new file mode 100644 index 000000000000..c096cc81a364 --- /dev/null +++ b/java/java-impl/src/com/intellij/compilerOutputIndex/api/indexer/CompilerOutputIndexUtil.java @@ -0,0 +1,21 @@ +package com.intellij.compilerOutputIndex.api.indexer; + +import com.intellij.compilerOutputIndex.impl.MethodIncompleteSignature; +import com.intellij.openapi.project.Project; +import com.intellij.util.indexing.ID; + +/** + * @author Dmitry Batkovich + */ +public final class CompilerOutputIndexUtil { + private CompilerOutputIndexUtil() {} + + public static <K, V> ID<K, V> generateIndexId(final String indexName, final Project project) { + return ID.create(String.format("compilerOutputIndex.%s.%d", indexName, Math.abs(project.getBasePath().hashCode()))); + } + + public static boolean isSetterOrConstructorMethodName(final String methodName) { + return MethodIncompleteSignature.CONSTRUCTOR_METHOD_NAME.equals(methodName) || methodName.startsWith("set"); + + } +} diff --git a/java/java-impl/src/com/intellij/compilerOutputIndex/api/indexer/CompilerOutputIndexer.java b/java/java-impl/src/com/intellij/compilerOutputIndex/api/indexer/CompilerOutputIndexer.java new file mode 100644 index 000000000000..83da82862c22 --- /dev/null +++ b/java/java-impl/src/com/intellij/compilerOutputIndex/api/indexer/CompilerOutputIndexer.java @@ -0,0 +1,360 @@ +package com.intellij.compilerOutputIndex.api.indexer; + +import com.intellij.compilerOutputIndex.api.fs.CompilerOutputFilesUtil; +import com.intellij.compilerOutputIndex.api.fs.FileVisitorService; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.compiler.CompileContext; +import com.intellij.openapi.compiler.CompileTask; +import com.intellij.openapi.compiler.CompilerManager; +import com.intellij.openapi.components.AbstractProjectComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.util.registry.RegistryValue; +import com.intellij.openapi.util.registry.RegistryValueListener; +import com.intellij.util.Consumer; +import com.intellij.util.indexing.ID; +import com.intellij.util.indexing.IndexInfrastructure; +import com.intellij.util.io.DataExternalizer; +import com.intellij.util.io.EnumeratorStringDescriptor; +import com.intellij.util.io.PersistentEnumeratorDelegate; +import com.intellij.util.io.PersistentHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; +import org.jetbrains.asm4.ClassReader; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author Dmitry Batkovich + */ +public class CompilerOutputIndexer extends AbstractProjectComponent { + private final static Logger LOG = Logger.getInstance(CompilerOutputIndexer.class); + + public final static String REGISTRY_KEY = "completion.enable.relevant.method.chain.suggestions"; + public final static String TITLE = "Compiler output indexer in progress..."; + + private volatile CompilerOutputBaseIndex[] myIndexes; + private volatile Map<String, CompilerOutputBaseIndex> myIndexTypeQNameToIndex; + private volatile PersistentHashMap<String, Long> myFileTimestampsIndex; + private volatile PersistentEnumeratorDelegate<String> myFileEnumerator; + private volatile boolean myInitialized = false; + + private final Lock myLock = new ReentrantLock(); + private final AtomicBoolean myInProgress = new AtomicBoolean(false); + private AtomicBoolean myEnabled = new AtomicBoolean(false); + + public static CompilerOutputIndexer getInstance(final Project project) { + return project.getComponent(CompilerOutputIndexer.class); + } + + protected CompilerOutputIndexer(final Project project) { + super(project); + } + + public boolean isEnabled() { + return myEnabled.get(); + } + + private ID<String, Long> getFileTimestampsIndexId() { + return CompilerOutputIndexUtil.generateIndexId("ProjectCompilerOutputClassFilesTimestamps", myProject); + } + + @Override + public final void projectOpened() { + Registry.get(REGISTRY_KEY).addListener(new RegistryValueListener.Adapter() { + @Override + public void afterValueChanged(final RegistryValue value) { + myEnabled.set(value.asBoolean()); + if (myEnabled.get()) { + doEnable(); + } + } + }, myProject); + + myEnabled = new AtomicBoolean(Registry.is(REGISTRY_KEY) || ApplicationManager.getApplication().isUnitTestMode()); + if (myEnabled.get()) { + doEnable(); + } + } + + private void doEnable() { + if (!myInitialized) { + myIndexes = Extensions.getExtensions(CompilerOutputBaseIndex.EXTENSION_POINT_NAME, myProject); + myIndexTypeQNameToIndex = new HashMap<String, CompilerOutputBaseIndex>(); + boolean needReindex = false; + for (final CompilerOutputBaseIndex index : myIndexes) { + if (index.init(myProject)) { + needReindex = true; + } + myIndexTypeQNameToIndex.put(index.getClass().getCanonicalName(), index); + } + initTimestampIndex(needReindex); + try { + myFileEnumerator = new PersistentEnumeratorDelegate<String>( + IndexInfrastructure.getStorageFile(CompilerOutputIndexUtil.generateIndexId("compilerOutputIndexFileId.enum", myProject)), + new EnumeratorStringDescriptor(), 2048); + } + catch (IOException e) { + throw new RuntimeException(e); + } + CompilerManager.getInstance(myProject).addAfterTask(new CompileTask() { + @Override + public boolean execute(final CompileContext context) { + if (myEnabled.get() && myInProgress.compareAndSet(false, true)) { + myLock.lock(); + try { + context.getProgressIndicator().setText("Compiler output indexing in progress"); + final Consumer<File> fileConsumer = new Consumer<File>() { + @Override + public void consume(final File file) { + try { + doIndexing(file, context.getProgressIndicator()); + } + catch (ProcessCanceledException e0) { + throw e0; + } + catch (RuntimeException e) { + LOG.error(e); + } + } + }; + for (final Module module : context.getCompileScope().getAffectedModules()) { + CompilerOutputFilesUtil.iterateModuleClassFiles(module, fileConsumer); + } + } + finally { + myLock.unlock(); + myInProgress.set(false); + } + } + return true; + } + }); + if (needReindex) { + reindexAllProjectInBackground(); + } + myInitialized = true; + } + } + + private void initTimestampIndex(final boolean needReindex) { + if (needReindex) { + FileUtil.delete(IndexInfrastructure.getIndexRootDir(getFileTimestampsIndexId())); + } + for (int attempts = 0; attempts < 2; attempts++) { + try { + myFileTimestampsIndex = new PersistentHashMap<String, Long>(IndexInfrastructure.getStorageFile(getFileTimestampsIndexId()), + new EnumeratorStringDescriptor(), new DataExternalizer<Long>() { + @Override + public void save(final DataOutput out, final Long value) throws IOException { + out.writeLong(value); + } + + @Override + public Long read(final DataInput in) throws IOException { + return in.readLong(); + } + }); + } + catch (IOException e) { + FileUtil.delete(IndexInfrastructure.getIndexRootDir(getFileTimestampsIndexId())); + } + if (myFileTimestampsIndex != null) { + return; + } + } + throw new RuntimeException("Timestamps index not initialized"); + } + + + public void reindex(final FileVisitorService visitorService, @NotNull final ProgressIndicator indicator) { + myLock.lock(); + try { + indicator.setText(TITLE); + visitorService.visit(new Consumer<File>() { + @Override + public void consume(final File file) { + try { + doIndexing(file, indicator); + } + catch (ProcessCanceledException e0) { + throw e0; + } + catch (RuntimeException e) { + LOG.error(e); + } + } + }); + } + finally { + myLock.unlock(); + } + } + + public void reindexAllProjectInBackground() { + if (myInProgress.compareAndSet(false, true)) { + ProgressManager.getInstance().run(new Task.Backgroundable(myProject, TITLE) { + + @Override + public void onCancel() { + myIndexTypeQNameToIndex.clear(); + myInProgress.set(false); + } + + @Override + public void onSuccess() { + myInProgress.set(false); + } + + @Override + public void run(@NotNull final ProgressIndicator indicator) { + reindexAllProject(indicator); + } + }); + } + } + + public void reindexAllProject(@NotNull final ProgressIndicator indicator) { + reindex(new FileVisitorService.ProjectClassFiles(myProject), indicator); + } + + @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") + private void doIndexing(@NotNull final File file, @NotNull final ProgressIndicator indicator) { + final String filePath; + try { + filePath = file.getCanonicalPath(); + } + catch (IOException e) { + LOG.error(e); + return; + } + final Long timestamp = getTimestamp(filePath); + ProgressManager.checkCanceled(); + final long currentTimeStamp = file.lastModified(); + if (timestamp == null || timestamp != currentTimeStamp) { + putTimestamp(filePath, currentTimeStamp); + final ClassReader reader; + InputStream is = null; + try { + is = new FileInputStream(file); + reader = new ClassReader(is); + } + catch (IOException e) { + removeTimestamp(filePath); + return; + } + finally { + if (is != null) { + try { + is.close(); + } + catch (IOException ignored) { + } + } + } + try { + indicator.setText2(filePath); + final int id = myFileEnumerator.enumerate(filePath); + for (final CompilerOutputBaseIndex index : myIndexes) { + index.update(id, reader); + } + } + catch (RuntimeException e) { + LOG.error(String.format("can't index file: %s", file.getAbsolutePath()), e); + } + catch (IOException e) { + LOG.error(String.format("can't index file: %s", file.getAbsolutePath()), e); + } + } + } + + public void clear() { + try { + myFileTimestampsIndex.close(); + } + catch (IOException e) { + throw new RuntimeException(e); + } + initTimestampIndex(true); + for (final CompilerOutputBaseIndex index : myIndexes) { + index.clear(); + } + } + + private void removeTimestamp(final String fileId) { + try { + myFileTimestampsIndex.remove(fileId); + } + catch (IOException e) { + LOG.error(e); + } + } + + @Nullable + private Long getTimestamp(final String fileName) { + try { + return myFileTimestampsIndex.get(fileName); + } + catch (IOException e) { + LOG.error(e); + return 0L; + } + } + + private void putTimestamp(final String fileName, final long timestamp) { + try { + myFileTimestampsIndex.put(fileName, timestamp); + } + catch (IOException e) { + LOG.error(e); + } + } + + + @Override + public void projectClosed() { + if (myInitialized) { + for (final CompilerOutputBaseIndex index : myIndexes) { + index.projectClosed(); + } + try { + myFileTimestampsIndex.close(); + myFileEnumerator.close(); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @TestOnly + public void removeIndexes() { + for (final CompilerOutputBaseIndex index : myIndexes) { + FileUtil.delete(IndexInfrastructure.getIndexRootDir(index.getIndexId())); + } + FileUtil.delete(IndexInfrastructure.getIndexRootDir(getFileTimestampsIndexId())); + } + + @SuppressWarnings("unchecked") + public <T extends CompilerOutputBaseIndex> T getIndex(final Class<T> tClass) { + final CompilerOutputBaseIndex index = myIndexTypeQNameToIndex.get(tClass.getCanonicalName()); + if (index == null) { + throw new RuntimeException(String.format("index class with name %s not found", tClass.getName())); + } + return (T)index; + } +} |