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                    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    }