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 private 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 AnnotationArgumentVisitor av = v.visitArray(Name.guess(name)); 161 return av == null ? null : convertAnnotationVisitor(av); 162 } 163 164 @Override 165 public void visitEnum(String name, String desc, String value) { 166 v.visitEnum(Name.identifier(name), classNameFromAsmDesc(desc), Name.identifier(value)); 167 } 168 169 @Override 170 public void visitEnd() { 171 v.visitEnd(); 172 } 173 }; 174 } 175 176 @Override 177 public void visitMembers(@NotNull final MemberVisitor memberVisitor) { 178 try { 179 new ClassReader(file.contentsToByteArray()).accept(new ClassVisitor(ASM5) { 180 @Override 181 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { 182 final AnnotationVisitor v = memberVisitor.visitField(Name.guess(name), desc, value); 183 if (v == null) return null; 184 185 return new FieldVisitor(ASM5) { 186 @Override 187 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(String desc, boolean visible) { 188 return convertAnnotationVisitor(v, desc); 189 } 190 191 @Override 192 public void visitEnd() { 193 v.visitEnd(); 194 } 195 }; 196 } 197 198 @Override 199 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 200 final MethodAnnotationVisitor v = memberVisitor.visitMethod(Name.guess(name), desc); 201 if (v == null) return null; 202 203 return new MethodVisitor(ASM5) { 204 @Override 205 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(String desc, boolean visible) { 206 return convertAnnotationVisitor(v, desc); 207 } 208 209 @Override 210 public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { 211 AnnotationArgumentVisitor av = v.visitParameterAnnotation(parameter, classNameFromAsmDesc(desc)); 212 return av == null ? null : convertAnnotationVisitor(av); 213 } 214 215 @Override 216 public void visitEnd() { 217 v.visitEnd(); 218 } 219 }; 220 } 221 }, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES); 222 } 223 catch (Throwable e) { 224 LOG.error(renderFileReadingErrorMessage(file), e); 225 throw UtilsPackage.rethrow(e); 226 } 227 } 228 229 @NotNull 230 private static JvmClassName classNameFromAsmDesc(@NotNull String desc) { 231 assert desc.startsWith("L") && desc.endsWith(";") : "Not a JVM descriptor: " + desc; 232 return JvmClassName.byInternalName(desc.substring(1, desc.length() - 1)); 233 } 234 235 @NotNull 236 private static String renderFileReadingErrorMessage(@NotNull VirtualFile file) { 237 return "Could not read file: " + file.getPath() + "; " 238 + "size in bytes: " + file.getLength() + "; " 239 + "file type: " + file.getFileType().getName(); 240 } 241 242 @Override 243 public int hashCode() { 244 return file.hashCode(); 245 } 246 247 @Override 248 public boolean equals(Object obj) { 249 return obj instanceof VirtualFileKotlinClass && ((VirtualFileKotlinClass) obj).file.equals(file); 250 } 251 252 @Override 253 public String toString() { 254 return getClass().getSimpleName() + ": " + file.toString(); 255 } 256 }