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 }