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.ide.highlighter.JavaClassFileType; 020 import com.intellij.openapi.diagnostic.Logger; 021 import com.intellij.openapi.util.Pair; 022 import com.intellij.openapi.util.Ref; 023 import com.intellij.openapi.vfs.VirtualFile; 024 import org.jetbrains.annotations.NotNull; 025 import org.jetbrains.annotations.Nullable; 026 import org.jetbrains.jet.lang.resolve.java.JvmClassName; 027 import org.jetbrains.jet.lang.resolve.kotlin.header.KotlinClassHeader; 028 import org.jetbrains.jet.lang.resolve.kotlin.header.ReadKotlinClassHeaderAnnotationVisitor; 029 import org.jetbrains.jet.lang.resolve.name.Name; 030 import org.jetbrains.jet.utils.UtilsPackage; 031 import org.jetbrains.org.objectweb.asm.ClassReader; 032 import org.jetbrains.org.objectweb.asm.ClassVisitor; 033 import org.jetbrains.org.objectweb.asm.FieldVisitor; 034 import org.jetbrains.org.objectweb.asm.MethodVisitor; 035 036 import static org.jetbrains.org.objectweb.asm.ClassReader.*; 037 import static org.jetbrains.org.objectweb.asm.Opcodes.ASM5; 038 039 public class VirtualFileKotlinClass implements KotlinJvmBinaryClass { 040 private final static Logger LOG = Logger.getInstance(VirtualFileKotlinClass.class); 041 042 private final VirtualFile file; 043 private final JvmClassName className; 044 private final KotlinClassHeader classHeader; 045 046 private VirtualFileKotlinClass(@NotNull VirtualFile file, @NotNull JvmClassName className, @NotNull KotlinClassHeader classHeader) { 047 this.file = file; 048 this.className = className; 049 this.classHeader = classHeader; 050 } 051 052 @Nullable 053 public static Pair<JvmClassName, KotlinClassHeader> readClassNameAndHeader(@NotNull byte[] fileContents) { 054 final ReadKotlinClassHeaderAnnotationVisitor readHeaderVisitor = new ReadKotlinClassHeaderAnnotationVisitor(); 055 final Ref<JvmClassName> classNameRef = Ref.create(); 056 new ClassReader(fileContents).accept(new ClassVisitor(ASM5) { 057 @Override 058 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 059 classNameRef.set(JvmClassName.byInternalName(name)); 060 } 061 062 @Override 063 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(String desc, boolean visible) { 064 return convertAnnotationVisitor(readHeaderVisitor, desc); 065 } 066 067 @Override 068 public void visitEnd() { 069 readHeaderVisitor.visitEnd(); 070 } 071 }, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES); 072 073 JvmClassName className = classNameRef.get(); 074 if (className == null) return null; 075 076 KotlinClassHeader header = readHeaderVisitor.createHeader(); 077 if (header == null) return null; 078 079 return Pair.create(className, header); 080 } 081 082 @Nullable 083 /* package */ static VirtualFileKotlinClass create(@NotNull VirtualFile file) { 084 assert file.getFileType() == JavaClassFileType.INSTANCE : "Trying to read binary data from a non-class file " + file; 085 try { 086 byte[] fileContents = file.contentsToByteArray(); 087 Pair<JvmClassName, KotlinClassHeader> nameAndHeader = readClassNameAndHeader(fileContents); 088 if (nameAndHeader == null) { 089 return null; 090 } 091 092 return new VirtualFileKotlinClass(file, nameAndHeader.first, nameAndHeader.second); 093 } 094 catch (Throwable e) { 095 LOG.warn(renderFileReadingErrorMessage(file)); 096 return null; 097 } 098 } 099 100 @Nullable 101 public static KotlinClassHeader readClassHeader(@NotNull byte[] fileContents) { 102 Pair<JvmClassName, KotlinClassHeader> pair = readClassNameAndHeader(fileContents); 103 return pair == null ? null : pair.second; 104 } 105 106 @NotNull 107 public VirtualFile getFile() { 108 return file; 109 } 110 111 @NotNull 112 @Override 113 public JvmClassName getClassName() { 114 return className; 115 } 116 117 @NotNull 118 @Override 119 public KotlinClassHeader getClassHeader() { 120 return classHeader; 121 } 122 123 @Override 124 public void loadClassAnnotations(@NotNull final AnnotationVisitor annotationVisitor) { 125 try { 126 new ClassReader(file.contentsToByteArray()).accept(new ClassVisitor(ASM5) { 127 @Override 128 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(String desc, boolean visible) { 129 return convertAnnotationVisitor(annotationVisitor, desc); 130 } 131 132 @Override 133 public void visitEnd() { 134 annotationVisitor.visitEnd(); 135 } 136 }, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES); 137 } 138 catch (Throwable e) { 139 LOG.error(renderFileReadingErrorMessage(file), e); 140 throw UtilsPackage.rethrow(e); 141 } 142 } 143 144 @Nullable 145 private static org.jetbrains.org.objectweb.asm.AnnotationVisitor convertAnnotationVisitor(@NotNull AnnotationVisitor visitor, @NotNull String desc) { 146 AnnotationArgumentVisitor v = visitor.visitAnnotation(classNameFromAsmDesc(desc)); 147 return v == null ? null : convertAnnotationVisitor(v); 148 } 149 150 @NotNull 151 private static org.jetbrains.org.objectweb.asm.AnnotationVisitor convertAnnotationVisitor(@NotNull final AnnotationArgumentVisitor v) { 152 return new org.jetbrains.org.objectweb.asm.AnnotationVisitor(ASM5) { 153 @Override 154 public void visit(String name, Object value) { 155 v.visit(name == null ? null : Name.identifier(name), value); 156 } 157 158 @Override 159 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitArray(String name) { 160 final AnnotationArrayArgumentVisitor arv = v.visitArray(Name.guess(name)); 161 return arv == null ? null : new org.jetbrains.org.objectweb.asm.AnnotationVisitor(ASM5) { 162 @Override 163 public void visit(String name, Object value) { 164 arv.visit(value); 165 } 166 167 @Override 168 public void visitEnum(String name, String desc, String value) { 169 arv.visitEnum(classNameFromAsmDesc(desc), Name.identifier(value)); 170 } 171 172 @Override 173 public void visitEnd() { 174 arv.visitEnd(); 175 } 176 }; 177 } 178 179 @Override 180 public void visitEnum(String name, String desc, String value) { 181 v.visitEnum(Name.identifier(name), classNameFromAsmDesc(desc), Name.identifier(value)); 182 } 183 184 @Override 185 public void visitEnd() { 186 v.visitEnd(); 187 } 188 }; 189 } 190 191 @Override 192 public void visitMembers(@NotNull final MemberVisitor memberVisitor) { 193 try { 194 new ClassReader(file.contentsToByteArray()).accept(new ClassVisitor(ASM5) { 195 @Override 196 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { 197 final AnnotationVisitor v = memberVisitor.visitField(Name.guess(name), desc, value); 198 if (v == null) return null; 199 200 return new FieldVisitor(ASM5) { 201 @Override 202 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(String desc, boolean visible) { 203 return convertAnnotationVisitor(v, desc); 204 } 205 206 @Override 207 public void visitEnd() { 208 v.visitEnd(); 209 } 210 }; 211 } 212 213 @Override 214 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 215 final MethodAnnotationVisitor v = memberVisitor.visitMethod(Name.guess(name), desc); 216 if (v == null) return null; 217 218 return new MethodVisitor(ASM5) { 219 @Override 220 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(String desc, boolean visible) { 221 return convertAnnotationVisitor(v, desc); 222 } 223 224 @Override 225 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { 226 AnnotationArgumentVisitor av = v.visitParameterAnnotation(parameter, classNameFromAsmDesc(desc)); 227 return av == null ? null : convertAnnotationVisitor(av); 228 } 229 230 @Override 231 public void visitEnd() { 232 v.visitEnd(); 233 } 234 }; 235 } 236 }, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES); 237 } 238 catch (Throwable e) { 239 LOG.error(renderFileReadingErrorMessage(file), e); 240 throw UtilsPackage.rethrow(e); 241 } 242 } 243 244 @NotNull 245 private static JvmClassName classNameFromAsmDesc(@NotNull String desc) { 246 assert desc.startsWith("L") && desc.endsWith(";") : "Not a JVM descriptor: " + desc; 247 return JvmClassName.byInternalName(desc.substring(1, desc.length() - 1)); 248 } 249 250 @NotNull 251 private static String renderFileReadingErrorMessage(@NotNull VirtualFile file) { 252 return "Could not read file: " + file.getPath() + "; " 253 + "size in bytes: " + file.getLength() + "; " 254 + "file type: " + file.getFileType().getName(); 255 } 256 257 @Override 258 public int hashCode() { 259 return file.hashCode(); 260 } 261 262 @Override 263 public boolean equals(Object obj) { 264 return obj instanceof VirtualFileKotlinClass && ((VirtualFileKotlinClass) obj).file.equals(file); 265 } 266 267 @Override 268 public String toString() { 269 return getClass().getSimpleName() + ": " + file.toString(); 270 } 271 }