Move KotlinBinaryClassCache to separate module, so it can be used outside FE1.0
This commit is contained in:
@@ -11,6 +11,8 @@ dependencies {
|
||||
api("javax.annotation:jsr250-api:1.0")
|
||||
api(project(":compiler:frontend"))
|
||||
api(project(":compiler:resolution.common.jvm"))
|
||||
api(project(":compiler:frontend.common.jvm"))
|
||||
|
||||
compileOnly(intellijCore())
|
||||
compileOnly(commonDependency("org.jetbrains.intellij.deps:asm-all"))
|
||||
compileOnly(commonDependency("org.jetbrains.intellij.deps:trove4j"))
|
||||
|
||||
-368
@@ -1,368 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.load.kotlin;
|
||||
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import kotlin.jvm.functions.Function4;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap;
|
||||
import org.jetbrains.kotlin.descriptors.SourceElement;
|
||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader;
|
||||
import org.jetbrains.kotlin.load.kotlin.header.ReadKotlinClassHeaderAnnotationVisitor;
|
||||
import org.jetbrains.kotlin.name.ClassId;
|
||||
import org.jetbrains.kotlin.name.FqName;
|
||||
import org.jetbrains.kotlin.name.Name;
|
||||
import org.jetbrains.kotlin.resolve.constants.ClassLiteralValue;
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType;
|
||||
import org.jetbrains.org.objectweb.asm.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.jetbrains.org.objectweb.asm.ClassReader.*;
|
||||
import static org.jetbrains.org.objectweb.asm.Opcodes.API_VERSION;
|
||||
|
||||
public abstract class FileBasedKotlinClass implements KotlinJvmBinaryClass {
|
||||
private final ClassId classId;
|
||||
private final int classVersion;
|
||||
private final KotlinClassHeader classHeader;
|
||||
private final InnerClassesInfo innerClasses;
|
||||
|
||||
protected FileBasedKotlinClass(
|
||||
@NotNull ClassId classId,
|
||||
int classVersion,
|
||||
@NotNull KotlinClassHeader classHeader,
|
||||
@NotNull InnerClassesInfo innerClasses
|
||||
) {
|
||||
this.classId = classId;
|
||||
this.classVersion = classVersion;
|
||||
this.classHeader = classHeader;
|
||||
this.innerClasses = innerClasses;
|
||||
}
|
||||
|
||||
public static class OuterAndInnerName {
|
||||
public final String outerInternalName;
|
||||
public final String innerSimpleName;
|
||||
|
||||
private OuterAndInnerName(@Nullable String outerInternalName, @Nullable String innerSimpleName) {
|
||||
this.outerInternalName = outerInternalName;
|
||||
this.innerSimpleName = innerSimpleName;
|
||||
}
|
||||
}
|
||||
|
||||
public static class InnerClassesInfo {
|
||||
private Map<String, OuterAndInnerName> map = null;
|
||||
|
||||
public void add(@NotNull String name, @Nullable String outerName, @Nullable String innerName) {
|
||||
if (map == null) {
|
||||
map = new HashMap<>();
|
||||
}
|
||||
map.put(name, new OuterAndInnerName(outerName, innerName));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public OuterAndInnerName get(@NotNull String name) {
|
||||
return map == null ? null : map.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected abstract byte[] getFileContents();
|
||||
|
||||
// TODO public to be accessible in companion object of subclass, workaround for KT-3974
|
||||
@Nullable
|
||||
public static <T> T create(
|
||||
@NotNull byte[] fileContents,
|
||||
@NotNull Function4<ClassId, Integer, KotlinClassHeader, InnerClassesInfo, T> factory
|
||||
) {
|
||||
ReadKotlinClassHeaderAnnotationVisitor readHeaderVisitor = new ReadKotlinClassHeaderAnnotationVisitor();
|
||||
Ref<String> classNameRef = Ref.create();
|
||||
Ref<Integer> classVersion = Ref.create();
|
||||
InnerClassesInfo innerClasses = new InnerClassesInfo();
|
||||
new ClassReader(fileContents).accept(new ClassVisitor(API_VERSION) {
|
||||
@Override
|
||||
public void visit(int version, int access, @NotNull String name, String signature, String superName, String[] interfaces) {
|
||||
classNameRef.set(name);
|
||||
classVersion.set(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInnerClass(@NotNull String name, String outerName, String innerName, int access) {
|
||||
innerClasses.add(name, outerName, innerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) {
|
||||
return convertAnnotationVisitor(readHeaderVisitor, desc, innerClasses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
readHeaderVisitor.visitEnd();
|
||||
}
|
||||
}, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
|
||||
|
||||
String className = classNameRef.get();
|
||||
if (className == null) return null;
|
||||
|
||||
KotlinClassHeader header = readHeaderVisitor.createHeader();
|
||||
if (header == null) return null;
|
||||
|
||||
ClassId id = resolveNameByInternalName(className, innerClasses);
|
||||
return factory.invoke(id, classVersion.get(), header, innerClasses);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ClassId getClassId() {
|
||||
return classId;
|
||||
}
|
||||
|
||||
public int getClassVersion() {
|
||||
return classVersion;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public KotlinClassHeader getClassHeader() {
|
||||
return classHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadClassAnnotations(@NotNull AnnotationVisitor annotationVisitor, @Nullable byte[] cachedContents) {
|
||||
byte[] fileContents = cachedContents != null ? cachedContents : getFileContents();
|
||||
new ClassReader(fileContents).accept(new ClassVisitor(API_VERSION) {
|
||||
@Override
|
||||
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) {
|
||||
return convertAnnotationVisitor(annotationVisitor, desc, innerClasses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
annotationVisitor.visitEnd();
|
||||
}
|
||||
}, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static org.jetbrains.org.objectweb.asm.AnnotationVisitor convertAnnotationVisitor(
|
||||
@NotNull AnnotationVisitor visitor, @NotNull String desc, @NotNull InnerClassesInfo innerClasses
|
||||
) {
|
||||
AnnotationArgumentVisitor v = visitor.visitAnnotation(resolveNameByDesc(desc, innerClasses), SourceElement.NO_SOURCE);
|
||||
return v == null ? null : convertAnnotationVisitor(v, innerClasses);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static org.jetbrains.org.objectweb.asm.AnnotationVisitor convertAnnotationVisitor(
|
||||
@NotNull AnnotationArgumentVisitor v, @NotNull InnerClassesInfo innerClasses
|
||||
) {
|
||||
return new org.jetbrains.org.objectweb.asm.AnnotationVisitor(API_VERSION) {
|
||||
@Override
|
||||
public void visit(String name, @NotNull Object value) {
|
||||
Name identifier = name == null ? null : Name.identifier(name);
|
||||
if (value instanceof Type) {
|
||||
v.visitClassLiteral(identifier, resolveKotlinNameByType((Type) value, innerClasses));
|
||||
}
|
||||
else {
|
||||
v.visit(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitArray(String name) {
|
||||
AnnotationArrayArgumentVisitor arv = v.visitArray(name == null ? null : Name.identifier(name));
|
||||
return arv == null ? null : new org.jetbrains.org.objectweb.asm.AnnotationVisitor(API_VERSION) {
|
||||
@Override
|
||||
public void visit(String name, @NotNull Object value) {
|
||||
if (value instanceof Type) {
|
||||
arv.visitClassLiteral(resolveKotlinNameByType((Type) value, innerClasses));
|
||||
}
|
||||
else {
|
||||
arv.visit(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnum(String name, @NotNull String desc, @NotNull String value) {
|
||||
arv.visitEnum(resolveNameByDesc(desc, innerClasses), Name.identifier(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(String name, @NotNull String desc) {
|
||||
AnnotationArgumentVisitor aav = arv.visitAnnotation(resolveNameByDesc(desc, innerClasses));
|
||||
return aav == null ? null : convertAnnotationVisitor(aav, innerClasses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
arv.visitEnd();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(String name, @NotNull String desc) {
|
||||
AnnotationArgumentVisitor arv =
|
||||
v.visitAnnotation(name == null ? null : Name.identifier(name), resolveNameByDesc(desc, innerClasses));
|
||||
return arv == null ? null : convertAnnotationVisitor(arv, innerClasses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnum(String name, @NotNull String desc, @NotNull String value) {
|
||||
v.visitEnum(name == null ? null : Name.identifier(name), resolveNameByDesc(desc, innerClasses), Name.identifier(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
v.visitEnd();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMembers(@NotNull MemberVisitor memberVisitor, @Nullable byte[] cachedContents) {
|
||||
byte[] fileContents = cachedContents != null ? cachedContents : getFileContents();
|
||||
new ClassReader(fileContents).accept(new ClassVisitor(API_VERSION) {
|
||||
@Override
|
||||
public FieldVisitor visitField(int access, @NotNull String name, @NotNull String desc, String signature, Object value) {
|
||||
AnnotationVisitor v = memberVisitor.visitField(Name.identifier(name), desc, value);
|
||||
if (v == null) return null;
|
||||
|
||||
return new FieldVisitor(API_VERSION) {
|
||||
@Override
|
||||
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) {
|
||||
return convertAnnotationVisitor(v, desc, innerClasses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
v.visitEnd();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) {
|
||||
MethodAnnotationVisitor v = memberVisitor.visitMethod(Name.identifier(name), desc);
|
||||
if (v == null) return null;
|
||||
|
||||
int methodParamCount = Type.getArgumentTypes(desc).length;
|
||||
return new MethodVisitor(API_VERSION) {
|
||||
|
||||
private int visibleAnnotableParameterCount = methodParamCount;
|
||||
private int invisibleAnnotableParameterCount = methodParamCount;
|
||||
|
||||
@Override
|
||||
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) {
|
||||
return convertAnnotationVisitor(v, desc, innerClasses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotationDefault() {
|
||||
AnnotationArgumentVisitor av = v.visitAnnotationMemberDefaultValue();
|
||||
return av == null ? null : convertAnnotationVisitor(av, innerClasses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitParameterAnnotation(int parameter, @NotNull String desc, boolean visible) {
|
||||
int parameterIndex = parameter + methodParamCount - (visible ? visibleAnnotableParameterCount : invisibleAnnotableParameterCount);
|
||||
AnnotationArgumentVisitor av = v.visitParameterAnnotation(parameterIndex, resolveNameByDesc(desc, innerClasses), SourceElement.NO_SOURCE);
|
||||
return av == null ? null : convertAnnotationVisitor(av, innerClasses);
|
||||
}
|
||||
|
||||
public void visitAnnotableParameterCount(int parameterCount, boolean visible) {
|
||||
if (visible)
|
||||
visibleAnnotableParameterCount = parameterCount;
|
||||
else {
|
||||
invisibleAnnotableParameterCount = parameterCount;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
v.visitEnd();
|
||||
}
|
||||
};
|
||||
}
|
||||
}, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static ClassId resolveNameByDesc(@NotNull String desc, @NotNull InnerClassesInfo innerClasses) {
|
||||
assert desc.startsWith("L") && desc.endsWith(";") : "Not a JVM descriptor: " + desc;
|
||||
String name = desc.substring(1, desc.length() - 1);
|
||||
return resolveNameByInternalName(name, innerClasses);
|
||||
}
|
||||
|
||||
// See ReflectKotlinStructure.classLiteralValue
|
||||
@NotNull
|
||||
private static ClassLiteralValue resolveKotlinNameByType(@NotNull Type type, @NotNull InnerClassesInfo innerClasses) {
|
||||
String typeDesc = type.getDescriptor();
|
||||
int dimensions = typeDesc.charAt(0) == '[' ? type.getDimensions() : 0;
|
||||
String elementDesc = dimensions == 0 ? typeDesc : type.getElementType().getDescriptor();
|
||||
JvmPrimitiveType primType = JvmPrimitiveType.getByDesc(elementDesc);
|
||||
if (primType != null) {
|
||||
if (dimensions > 0) {
|
||||
// "int[][]" should be loaded as "Array<IntArray>", not as "Array<Array<Int>>"
|
||||
return new ClassLiteralValue(ClassId.topLevel(primType.getPrimitiveType().getArrayTypeFqName()), dimensions - 1);
|
||||
}
|
||||
return new ClassLiteralValue(ClassId.topLevel(primType.getPrimitiveType().getTypeFqName()), dimensions);
|
||||
}
|
||||
ClassId javaClassId = resolveNameByDesc(elementDesc, innerClasses);
|
||||
ClassId kotlinClassId = JavaToKotlinClassMap.INSTANCE.mapJavaToKotlin(javaClassId.asSingleFqName());
|
||||
return new ClassLiteralValue(kotlinClassId != null ? kotlinClassId : javaClassId, dimensions);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ClassId resolveNameByInternalName(@NotNull String name, @NotNull InnerClassesInfo innerClasses) {
|
||||
if (!name.contains("$")) {
|
||||
return ClassId.topLevel(new FqName(name.replace('/', '.')));
|
||||
}
|
||||
|
||||
List<String> classes = new ArrayList<>(1);
|
||||
boolean local = false;
|
||||
|
||||
while (true) {
|
||||
OuterAndInnerName outer = innerClasses.get(name);
|
||||
if (outer == null) break;
|
||||
if (outer.outerInternalName == null) {
|
||||
local = true;
|
||||
break;
|
||||
}
|
||||
classes.add(outer.innerSimpleName);
|
||||
name = outer.outerInternalName;
|
||||
}
|
||||
|
||||
FqName outermostClassFqName = new FqName(name.replace('/', '.'));
|
||||
classes.add(outermostClassFqName.shortName().asString());
|
||||
|
||||
Collections.reverse(classes);
|
||||
|
||||
FqName packageFqName = outermostClassFqName.parent();
|
||||
FqName relativeClassName = FqName.fromSegments(classes);
|
||||
return new ClassId(packageFqName, relativeClassName, local);
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract int hashCode();
|
||||
|
||||
@Override
|
||||
public abstract boolean equals(Object obj);
|
||||
|
||||
@Override
|
||||
public abstract String toString();
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.load.kotlin
|
||||
|
||||
import com.intellij.ide.highlighter.JavaClassFileType
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.util.Computable
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiJavaModule
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
class KotlinBinaryClassCache : Disposable {
|
||||
private val requestCaches = CopyOnWriteArrayList<WeakReference<RequestCache>>()
|
||||
|
||||
private class RequestCache {
|
||||
var virtualFile: VirtualFile? = null
|
||||
var modificationStamp: Long = 0
|
||||
var result: KotlinClassFinder.Result? = null
|
||||
|
||||
fun cache(
|
||||
file: VirtualFile,
|
||||
result: KotlinClassFinder.Result?
|
||||
): KotlinClassFinder.Result? {
|
||||
virtualFile = file
|
||||
this.result = result
|
||||
modificationStamp = file.modificationStamp
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private val cache = object : ThreadLocal<RequestCache>() {
|
||||
override fun initialValue(): RequestCache {
|
||||
return RequestCache().also {
|
||||
requestCaches.add(WeakReference(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
for (cache in requestCaches) {
|
||||
cache.get()?.run {
|
||||
result = null
|
||||
virtualFile = null
|
||||
}
|
||||
}
|
||||
requestCaches.clear()
|
||||
// This is only relevant for tests. We create a new instance of Application for each test, and so a new instance of this service is
|
||||
// also created for each test. However all tests share the same event dispatch thread, which would collect all instances of this
|
||||
// thread-local if they're not removed properly. Each instance would transitively retain VFS resulting in OutOfMemoryError
|
||||
cache.remove()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getKotlinBinaryClassOrClassFileContent(
|
||||
file: VirtualFile, fileContent: ByteArray? = null
|
||||
): KotlinClassFinder.Result? {
|
||||
if (file.fileType !== JavaClassFileType.INSTANCE) return null
|
||||
|
||||
if (file.name == PsiJavaModule.MODULE_INFO_CLS_FILE) return null
|
||||
|
||||
val service = ServiceManager.getService(KotlinBinaryClassCache::class.java)
|
||||
val requestCache = service.cache.get()
|
||||
|
||||
if (file.modificationStamp == requestCache.modificationStamp && file == requestCache.virtualFile) {
|
||||
return requestCache.result
|
||||
}
|
||||
|
||||
val aClass = ApplicationManager.getApplication().runReadAction(Computable {
|
||||
VirtualFileKotlinClass.create(file, fileContent)
|
||||
})
|
||||
|
||||
return requestCache.cache(file, aClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaClass
|
||||
import org.jetbrains.kotlin.load.java.structure.impl.VirtualFileBoundJavaClass
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.load.kotlin
|
||||
|
||||
import com.intellij.ide.highlighter.JavaClassFileType
|
||||
import com.intellij.openapi.diagnostic.ControlFlowException
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.load.kotlin.KotlinClassFinder.Result.KotlinClass
|
||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.util.PerformanceCounter
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
|
||||
class VirtualFileKotlinClass private constructor(
|
||||
val file: VirtualFile,
|
||||
className: ClassId,
|
||||
classVersion: Int,
|
||||
classHeader: KotlinClassHeader,
|
||||
innerClasses: InnerClassesInfo
|
||||
) : FileBasedKotlinClass(className, classVersion, classHeader, innerClasses) {
|
||||
|
||||
override val location: String
|
||||
get() = file.path
|
||||
|
||||
override val containingLibrary: String?
|
||||
get() = file.path.split("!/").firstOrNull()
|
||||
|
||||
override fun getFileContents(): ByteArray {
|
||||
try {
|
||||
return file.contentsToByteArray()
|
||||
} catch (e: IOException) {
|
||||
throw logFileReadingErrorMessage(e, file)
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?) = other is VirtualFileKotlinClass && other.file == file
|
||||
override fun hashCode() = file.hashCode()
|
||||
override fun toString() = "${this::class.java.simpleName}: $file"
|
||||
|
||||
companion object Factory {
|
||||
private val LOG = Logger.getInstance(VirtualFileKotlinClass::class.java)
|
||||
private val perfCounter = PerformanceCounter.create("Binary class from Kotlin file")
|
||||
|
||||
internal fun create(file: VirtualFile, fileContent: ByteArray?): KotlinClassFinder.Result? {
|
||||
return perfCounter.time {
|
||||
assert(file.fileType == JavaClassFileType.INSTANCE) { "Trying to read binary data from a non-class file $file" }
|
||||
|
||||
try {
|
||||
val byteContent = fileContent ?: file.contentsToByteArray(false)
|
||||
if (byteContent.isNotEmpty()) {
|
||||
val kotlinJvmBinaryClass = create(byteContent) { name, classVersion, header, innerClasses ->
|
||||
VirtualFileKotlinClass(file, name, classVersion, header, innerClasses)
|
||||
}
|
||||
|
||||
return@time kotlinJvmBinaryClass?.let { KotlinClass(it, byteContent) }
|
||||
?: KotlinClassFinder.Result.ClassFileContent(byteContent)
|
||||
}
|
||||
} catch (e: FileNotFoundException) {
|
||||
// Valid situation. User can delete jar file.
|
||||
} catch (e: Throwable) {
|
||||
if (e is ControlFlowException) throw e
|
||||
throw logFileReadingErrorMessage(e, file)
|
||||
}
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun logFileReadingErrorMessage(e: Throwable, file: VirtualFile): Throwable {
|
||||
val errorMessage = renderFileReadingErrorMessage(file)
|
||||
LOG.warn(errorMessage, e)
|
||||
return IllegalStateException(errorMessage, e)
|
||||
}
|
||||
|
||||
private fun renderFileReadingErrorMessage(file: VirtualFile): String =
|
||||
"Could not read file: ${file.path}; size in bytes: ${file.length}; file type: ${file.fileType.name}"
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VfsUtilCore
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClass
|
||||
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaPackageFragment
|
||||
import org.jetbrains.kotlin.modules.Module
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
|
||||
Reference in New Issue
Block a user