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