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    }