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