001 /* 002 * Copyright 2010-2015 JetBrains s.r.o. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package org.jetbrains.kotlin.load.kotlin; 018 019 import com.intellij.openapi.util.Ref; 020 import kotlin.jvm.functions.Function3; 021 import org.jetbrains.annotations.NotNull; 022 import org.jetbrains.annotations.Nullable; 023 import org.jetbrains.kotlin.descriptors.SourceElement; 024 import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader; 025 import org.jetbrains.kotlin.load.kotlin.header.ReadKotlinClassHeaderAnnotationVisitor; 026 import org.jetbrains.kotlin.name.ClassId; 027 import org.jetbrains.kotlin.name.FqName; 028 import org.jetbrains.kotlin.name.Name; 029 import org.jetbrains.org.objectweb.asm.ClassReader; 030 import org.jetbrains.org.objectweb.asm.ClassVisitor; 031 import org.jetbrains.org.objectweb.asm.FieldVisitor; 032 import org.jetbrains.org.objectweb.asm.MethodVisitor; 033 034 import java.util.*; 035 036 import static org.jetbrains.org.objectweb.asm.ClassReader.*; 037 import static org.jetbrains.org.objectweb.asm.Opcodes.ASM5; 038 039 public abstract class FileBasedKotlinClass implements KotlinJvmBinaryClass { 040 private final ClassId classId; 041 private final KotlinClassHeader classHeader; 042 private final InnerClassesInfo innerClasses; 043 044 protected FileBasedKotlinClass( 045 @NotNull ClassId classId, 046 @NotNull KotlinClassHeader classHeader, 047 @NotNull InnerClassesInfo innerClasses 048 ) { 049 this.classId = classId; 050 this.classHeader = classHeader; 051 this.innerClasses = innerClasses; 052 } 053 054 private static class OuterAndInnerName { 055 public final String outerInternalName; 056 public final String innerSimpleName; 057 058 private OuterAndInnerName(@Nullable String outerInternalName, @Nullable String innerSimpleName) { 059 this.outerInternalName = outerInternalName; 060 this.innerSimpleName = innerSimpleName; 061 } 062 } 063 064 protected static class InnerClassesInfo { 065 private Map<String, OuterAndInnerName> map = null; 066 067 public void add(@NotNull String name, @Nullable String outerName, @Nullable String innerName) { 068 if (map == null) { 069 map = new HashMap<String, OuterAndInnerName>(); 070 } 071 map.put(name, new OuterAndInnerName(outerName, innerName)); 072 } 073 074 @Nullable 075 public OuterAndInnerName get(@NotNull String name) { 076 return map == null ? null : map.get(name); 077 } 078 } 079 080 @NotNull 081 protected abstract byte[] getFileContents(); 082 083 // TODO public to be accessible in companion object of subclass, workaround for KT-3974 084 @Nullable 085 public static <T extends FileBasedKotlinClass> T create( 086 @NotNull byte[] fileContents, 087 @NotNull Function3<ClassId, KotlinClassHeader, InnerClassesInfo, T> factory 088 ) { 089 final ReadKotlinClassHeaderAnnotationVisitor readHeaderVisitor = new ReadKotlinClassHeaderAnnotationVisitor(); 090 final Ref<String> classNameRef = Ref.create(); 091 final InnerClassesInfo innerClasses = new InnerClassesInfo(); 092 new ClassReader(fileContents).accept(new ClassVisitor(ASM5) { 093 @Override 094 public void visit(int version, int access, @NotNull String name, String signature, String superName, String[] interfaces) { 095 classNameRef.set(name); 096 } 097 098 @Override 099 public void visitInnerClass(@NotNull String name, String outerName, String innerName, int access) { 100 innerClasses.add(name, outerName, innerName); 101 } 102 103 @Override 104 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) { 105 return convertAnnotationVisitor(readHeaderVisitor, desc, innerClasses); 106 } 107 108 @Override 109 public void visitEnd() { 110 readHeaderVisitor.visitEnd(); 111 } 112 }, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES); 113 114 String className = classNameRef.get(); 115 if (className == null) return null; 116 117 KotlinClassHeader header = readHeaderVisitor.createHeader(); 118 if (header == null) return null; 119 120 ClassId id = resolveNameByInternalName(className, innerClasses); 121 return factory.invoke(id, header, innerClasses); 122 } 123 124 @NotNull 125 @Override 126 public ClassId getClassId() { 127 return classId; 128 } 129 130 @NotNull 131 @Override 132 public KotlinClassHeader getClassHeader() { 133 return classHeader; 134 } 135 136 @Override 137 public void loadClassAnnotations(@NotNull final AnnotationVisitor annotationVisitor) { 138 new ClassReader(getFileContents()).accept(new ClassVisitor(ASM5) { 139 @Override 140 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) { 141 return convertAnnotationVisitor(annotationVisitor, desc, innerClasses); 142 } 143 144 @Override 145 public void visitEnd() { 146 annotationVisitor.visitEnd(); 147 } 148 }, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES); 149 } 150 151 @Nullable 152 private static org.jetbrains.org.objectweb.asm.AnnotationVisitor convertAnnotationVisitor( 153 @NotNull AnnotationVisitor visitor, @NotNull String desc, @NotNull InnerClassesInfo innerClasses 154 ) { 155 AnnotationArgumentVisitor v = visitor.visitAnnotation(resolveNameByDesc(desc, innerClasses), SourceElement.NO_SOURCE); 156 return v == null ? null : convertAnnotationVisitor(v, innerClasses); 157 } 158 159 @NotNull 160 private static org.jetbrains.org.objectweb.asm.AnnotationVisitor convertAnnotationVisitor( 161 @NotNull final AnnotationArgumentVisitor v, @NotNull final InnerClassesInfo innerClasses 162 ) { 163 return new org.jetbrains.org.objectweb.asm.AnnotationVisitor(ASM5) { 164 @Override 165 public void visit(String name, @NotNull Object value) { 166 v.visit(name == null ? null : Name.identifier(name), value); 167 } 168 169 @Override 170 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitArray(String name) { 171 final AnnotationArrayArgumentVisitor arv = v.visitArray(Name.guess(name)); 172 return arv == null ? null : new org.jetbrains.org.objectweb.asm.AnnotationVisitor(ASM5) { 173 @Override 174 public void visit(String name, @NotNull Object value) { 175 arv.visit(value); 176 } 177 178 @Override 179 public void visitEnum(String name, @NotNull String desc, @NotNull String value) { 180 arv.visitEnum(resolveNameByDesc(desc, innerClasses), Name.identifier(value)); 181 } 182 183 @Override 184 public void visitEnd() { 185 arv.visitEnd(); 186 } 187 }; 188 } 189 190 @Override 191 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(String name, @NotNull String desc) { 192 AnnotationArgumentVisitor arv = v.visitAnnotation(Name.guess(name), resolveNameByDesc(desc, innerClasses)); 193 return arv == null ? null : convertAnnotationVisitor(arv, innerClasses); 194 } 195 196 @Override 197 public void visitEnum(String name, @NotNull String desc, @NotNull String value) { 198 v.visitEnum(Name.identifier(name), resolveNameByDesc(desc, innerClasses), Name.identifier(value)); 199 } 200 201 @Override 202 public void visitEnd() { 203 v.visitEnd(); 204 } 205 }; 206 } 207 208 @Override 209 public void visitMembers(@NotNull final MemberVisitor memberVisitor) { 210 new ClassReader(getFileContents()).accept(new ClassVisitor(ASM5) { 211 @Override 212 public FieldVisitor visitField(int access, @NotNull String name, @NotNull String desc, String signature, Object value) { 213 final AnnotationVisitor v = memberVisitor.visitField(Name.guess(name), desc, value); 214 if (v == null) return null; 215 216 return new FieldVisitor(ASM5) { 217 @Override 218 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) { 219 return convertAnnotationVisitor(v, desc, innerClasses); 220 } 221 222 @Override 223 public void visitEnd() { 224 v.visitEnd(); 225 } 226 }; 227 } 228 229 @Override 230 public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) { 231 final MethodAnnotationVisitor v = memberVisitor.visitMethod(Name.guess(name), desc); 232 if (v == null) return null; 233 234 return new MethodVisitor(ASM5) { 235 @Override 236 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) { 237 return convertAnnotationVisitor(v, desc, innerClasses); 238 } 239 240 @Override 241 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitParameterAnnotation(int parameter, @NotNull String desc, boolean visible) { 242 AnnotationArgumentVisitor av = v.visitParameterAnnotation(parameter, resolveNameByDesc(desc, innerClasses), SourceElement.NO_SOURCE); 243 return av == null ? null : convertAnnotationVisitor(av, innerClasses); 244 } 245 246 @Override 247 public void visitEnd() { 248 v.visitEnd(); 249 } 250 }; 251 } 252 }, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES); 253 } 254 255 @NotNull 256 private static ClassId resolveNameByDesc(@NotNull String desc, @NotNull InnerClassesInfo innerClasses) { 257 assert desc.startsWith("L") && desc.endsWith(";") : "Not a JVM descriptor: " + desc; 258 String name = desc.substring(1, desc.length() - 1); 259 return resolveNameByInternalName(name, innerClasses); 260 } 261 262 @NotNull 263 private static ClassId resolveNameByInternalName(@NotNull String name, @NotNull InnerClassesInfo innerClasses) { 264 if (!name.contains("$")) { 265 return ClassId.topLevel(new FqName(name.replace('/', '.'))); 266 } 267 268 List<String> classes = new ArrayList<String>(1); 269 boolean local = false; 270 271 while (true) { 272 OuterAndInnerName outer = innerClasses.get(name); 273 if (outer == null) break; 274 if (outer.outerInternalName == null) { 275 local = true; 276 break; 277 } 278 classes.add(outer.innerSimpleName); 279 name = outer.outerInternalName; 280 } 281 282 FqName outermostClassFqName = new FqName(name.replace('/', '.')); 283 classes.add(outermostClassFqName.shortName().asString()); 284 285 Collections.reverse(classes); 286 287 FqName packageFqName = outermostClassFqName.parent(); 288 FqName relativeClassName = FqName.fromSegments(classes); 289 return new ClassId(packageFqName, relativeClassName, local); 290 } 291 292 @Override 293 public abstract int hashCode(); 294 295 @Override 296 public abstract boolean equals(Object obj); 297 298 @Override 299 public abstract String toString(); 300 }