summaryrefslogtreecommitdiff
path: root/java/java-impl/src/com/intellij/compilerOutputIndex/api
diff options
context:
space:
mode:
Diffstat (limited to 'java/java-impl/src/com/intellij/compilerOutputIndex/api')
-rw-r--r--java/java-impl/src/com/intellij/compilerOutputIndex/api/descriptor/ArrayListKeyDescriptor.java50
-rw-r--r--java/java-impl/src/com/intellij/compilerOutputIndex/api/descriptor/HashMapKeyDescriptor.java54
-rw-r--r--java/java-impl/src/com/intellij/compilerOutputIndex/api/descriptor/HashSetKeyDescriptor.java54
-rw-r--r--java/java-impl/src/com/intellij/compilerOutputIndex/api/fs/AsmUtil.java129
-rw-r--r--java/java-impl/src/com/intellij/compilerOutputIndex/api/fs/CompilerOutputFilesUtil.java64
-rw-r--r--java/java-impl/src/com/intellij/compilerOutputIndex/api/fs/FileVisitorService.java53
-rw-r--r--java/java-impl/src/com/intellij/compilerOutputIndex/api/indexer/CompilerOutputBaseIndex.java141
-rw-r--r--java/java-impl/src/com/intellij/compilerOutputIndex/api/indexer/CompilerOutputIndexUtil.java21
-rw-r--r--java/java-impl/src/com/intellij/compilerOutputIndex/api/indexer/CompilerOutputIndexer.java360
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;
+ }
+}