/* * 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.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.org.objectweb.asm.ClassReader; import org.jetbrains.org.objectweb.asm.ClassVisitor; import org.jetbrains.org.objectweb.asm.FieldVisitor; import org.jetbrains.org.objectweb.asm.MethodVisitor; import java.util.*; import static org.jetbrains.org.objectweb.asm.ClassReader.*; import static org.jetbrains.org.objectweb.asm.Opcodes.ASM5; 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 extends FileBasedKotlinClass> 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(ASM5) { @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(ASM5) { @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 private 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(ASM5) { @Override public void visit(String name, @NotNull Object value) { v.visit(name == null ? null : Name.identifier(name), value); } @Override public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitArray(String name) { AnnotationArrayArgumentVisitor arv = v.visitArray(Name.identifier(name)); return arv == null ? null : new org.jetbrains.org.objectweb.asm.AnnotationVisitor(ASM5) { @Override public void visit(String name, @NotNull Object value) { 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 void visitEnd() { arv.visitEnd(); } }; } @Override public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(String name, @NotNull String desc) { AnnotationArgumentVisitor arv = v.visitAnnotation(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.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(ASM5) { @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(ASM5) { @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; return new MethodVisitor(ASM5) { @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 visitParameterAnnotation(int parameter, @NotNull String desc, boolean visible) { AnnotationArgumentVisitor av = v.visitParameterAnnotation(parameter, resolveNameByDesc(desc, innerClasses), SourceElement.NO_SOURCE); return av == null ? null : convertAnnotationVisitor(av, innerClasses); } @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); } @NotNull private 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(); }