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 }