001    /*
002     * Copyright 2010-2015 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.kotlin.load.kotlin;
018    
019    import com.intellij.openapi.util.Ref;
020    import kotlin.jvm.functions.Function3;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.kotlin.descriptors.SourceElement;
024    import org.jetbrains.kotlin.load.java.JvmAnnotationNames;
025    import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader;
026    import org.jetbrains.kotlin.load.kotlin.header.ReadKotlinClassHeaderAnnotationVisitor;
027    import org.jetbrains.kotlin.name.ClassId;
028    import org.jetbrains.kotlin.name.FqName;
029    import org.jetbrains.kotlin.name.Name;
030    import org.jetbrains.org.objectweb.asm.ClassReader;
031    import org.jetbrains.org.objectweb.asm.ClassVisitor;
032    import org.jetbrains.org.objectweb.asm.FieldVisitor;
033    import org.jetbrains.org.objectweb.asm.MethodVisitor;
034    
035    import java.util.*;
036    
037    import static org.jetbrains.org.objectweb.asm.ClassReader.*;
038    import static org.jetbrains.org.objectweb.asm.Opcodes.ASM5;
039    
040    public abstract class FileBasedKotlinClass implements KotlinJvmBinaryClass {
041        private final ClassId classId;
042        private final KotlinClassHeader classHeader;
043        private final InnerClassesInfo innerClasses;
044    
045        protected FileBasedKotlinClass(
046                @NotNull ClassId classId,
047                @NotNull KotlinClassHeader classHeader,
048                @NotNull InnerClassesInfo innerClasses
049        ) {
050            this.classId = classId;
051            this.classHeader = classHeader;
052            this.innerClasses = innerClasses;
053        }
054    
055        private static class OuterAndInnerName {
056            public final String outerInternalName;
057            public final String innerSimpleName;
058    
059            private OuterAndInnerName(@Nullable String outerInternalName, @Nullable String innerSimpleName) {
060                this.outerInternalName = outerInternalName;
061                this.innerSimpleName = innerSimpleName;
062            }
063        }
064    
065        protected static class InnerClassesInfo {
066            private Map<String, OuterAndInnerName> map = null;
067    
068            public void add(@NotNull String name, @Nullable String outerName, @Nullable String innerName) {
069                if (map == null) {
070                    map = new HashMap<String, OuterAndInnerName>();
071                }
072                map.put(name, new OuterAndInnerName(outerName, innerName));
073            }
074    
075            @Nullable
076            public OuterAndInnerName get(@NotNull String name) {
077                return map == null ? null : map.get(name);
078            }
079        }
080    
081        @NotNull
082        protected abstract byte[] getFileContents();
083    
084        // TODO public to be accessible in companion object of subclass, workaround for KT-3974
085        @Nullable
086        public static <T extends FileBasedKotlinClass> T create(
087                @NotNull byte[] fileContents,
088                @NotNull Function3<ClassId, KotlinClassHeader, InnerClassesInfo, T> factory
089        ) {
090            final ReadKotlinClassHeaderAnnotationVisitor readHeaderVisitor = new ReadKotlinClassHeaderAnnotationVisitor();
091            final Ref<String> classNameRef = Ref.create();
092            final InnerClassesInfo innerClasses = new InnerClassesInfo();
093            new ClassReader(fileContents).accept(new ClassVisitor(ASM5) {
094                @Override
095                public void visit(int version, int access, @NotNull String name, String signature, String superName, String[] interfaces) {
096                    classNameRef.set(name);
097                }
098    
099                @Override
100                public void visitInnerClass(@NotNull String name, String outerName, String innerName, int access) {
101                    innerClasses.add(name, outerName, innerName);
102                }
103    
104                @Override
105                public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) {
106                    return convertAnnotationVisitor(readHeaderVisitor, desc, innerClasses);
107                }
108    
109                @Override
110                public void visitEnd() {
111                    readHeaderVisitor.visitEnd();
112                }
113            }, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
114    
115            String className = classNameRef.get();
116            if (className == null) return null;
117    
118            KotlinClassHeader header = readHeaderVisitor.createHeader();
119            if (header == null) return null;
120    
121            ClassId id = resolveNameByInternalName(className, innerClasses);
122            return factory.invoke(id, header, innerClasses);
123        }
124    
125        @NotNull
126        @Override
127        public ClassId getClassId() {
128            return classId;
129        }
130    
131        @NotNull
132        @Override
133        public KotlinClassHeader getClassHeader() {
134            return classHeader;
135        }
136    
137        @Override
138        public void loadClassAnnotations(@NotNull final AnnotationVisitor annotationVisitor) {
139            new ClassReader(getFileContents()).accept(new ClassVisitor(ASM5) {
140                @Override
141                public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) {
142                    return convertAnnotationVisitor(annotationVisitor, desc, innerClasses);
143                }
144    
145                @Override
146                public void visitEnd() {
147                    annotationVisitor.visitEnd();
148                }
149            }, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
150        }
151    
152        @Nullable
153        private static org.jetbrains.org.objectweb.asm.AnnotationVisitor convertAnnotationVisitor(
154                @NotNull AnnotationVisitor visitor, @NotNull String desc, @NotNull InnerClassesInfo innerClasses
155        ) {
156            AnnotationArgumentVisitor v = visitor.visitAnnotation(resolveNameByDesc(desc, innerClasses), SourceElement.NO_SOURCE);
157            return v == null ? null : convertAnnotationVisitor(v, innerClasses);
158        }
159    
160        @NotNull
161        private static org.jetbrains.org.objectweb.asm.AnnotationVisitor convertAnnotationVisitor(
162                @NotNull final AnnotationArgumentVisitor v, @NotNull final InnerClassesInfo innerClasses
163        ) {
164            return new org.jetbrains.org.objectweb.asm.AnnotationVisitor(ASM5) {
165                @Override
166                public void visit(String name, @NotNull Object value) {
167                    v.visit(name == null ? null : Name.identifier(name), value);
168                }
169    
170                @Override
171                public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitArray(String name) {
172                    final AnnotationArrayArgumentVisitor arv = v.visitArray(Name.guess(name));
173                    return arv == null ? null : new org.jetbrains.org.objectweb.asm.AnnotationVisitor(ASM5) {
174                        @Override
175                        public void visit(String name, @NotNull Object value) {
176                            arv.visit(value);
177                        }
178    
179                        @Override
180                        public void visitEnum(String name, @NotNull String desc, @NotNull String value) {
181                            arv.visitEnum(resolveNameByDesc(desc, innerClasses), Name.identifier(value));
182                        }
183    
184                        @Override
185                        public void visitEnd() {
186                            arv.visitEnd();
187                        }
188                    };
189                }
190    
191                @Override
192                public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(String name, @NotNull String desc) {
193                    AnnotationArgumentVisitor arv = v.visitAnnotation(Name.guess(name), resolveNameByDesc(desc, innerClasses));
194                    return arv == null ? null : convertAnnotationVisitor(arv, innerClasses);
195                }
196    
197                @Override
198                public void visitEnum(String name, @NotNull String desc, @NotNull String value) {
199                    v.visitEnum(Name.identifier(name), resolveNameByDesc(desc, innerClasses), Name.identifier(value));
200                }
201    
202                @Override
203                public void visitEnd() {
204                    v.visitEnd();
205                }
206            };
207        }
208    
209        @Override
210        public void visitMembers(@NotNull final MemberVisitor memberVisitor) {
211            new ClassReader(getFileContents()).accept(new ClassVisitor(ASM5) {
212                @Override
213                public FieldVisitor visitField(int access, @NotNull String name, @NotNull String desc, String signature, Object value) {
214                    final AnnotationVisitor v = memberVisitor.visitField(Name.guess(name), desc, value);
215                    if (v == null) return null;
216    
217                    return new FieldVisitor(ASM5) {
218                        @Override
219                        public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) {
220                            return convertAnnotationVisitor(v, desc, innerClasses);
221                        }
222    
223                        @Override
224                        public void visitEnd() {
225                            v.visitEnd();
226                        }
227                    };
228                }
229    
230                @Override
231                public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) {
232                    final MethodAnnotationVisitor v = memberVisitor.visitMethod(Name.guess(name), desc);
233                    if (v == null) return null;
234    
235                    return new MethodVisitor(ASM5) {
236                        @Override
237                        public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) {
238                            return convertAnnotationVisitor(v, desc, innerClasses);
239                        }
240    
241                        @Override
242                        public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitParameterAnnotation(int parameter, @NotNull String desc, boolean visible) {
243                            AnnotationArgumentVisitor av = v.visitParameterAnnotation(parameter, resolveNameByDesc(desc, innerClasses), SourceElement.NO_SOURCE);
244                            return av == null ? null : convertAnnotationVisitor(av, innerClasses);
245                        }
246    
247                        @Override
248                        public void visitEnd() {
249                            v.visitEnd();
250                        }
251                    };
252                }
253            }, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
254        }
255    
256        @NotNull
257        private static ClassId resolveNameByDesc(@NotNull String desc, @NotNull InnerClassesInfo innerClasses) {
258            assert desc.startsWith("L") && desc.endsWith(";") : "Not a JVM descriptor: " + desc;
259            String name = desc.substring(1, desc.length() - 1);
260            return resolveNameByInternalName(name, innerClasses);
261        }
262    
263        @NotNull
264        private static ClassId resolveNameByInternalName(@NotNull String name, @NotNull InnerClassesInfo innerClasses) {
265            if (!name.contains("$")) {
266                return ClassId.topLevel(new FqName(name.replace('/', '.')));
267            }
268    
269            // TODO: this is a hack which can be dropped once JVM back-end begins to write InnerClasses attribute for all referenced classes
270            if (name.equals(JvmAnnotationNames.KotlinSyntheticClass.KIND_INTERNAL_NAME)) {
271                return JvmAnnotationNames.KotlinSyntheticClass.KIND_CLASS_ID;
272            }
273            else if (name.equals(JvmAnnotationNames.KotlinClass.KIND_INTERNAL_NAME)) {
274                return JvmAnnotationNames.KotlinClass.KIND_CLASS_ID;
275            }
276    
277            List<String> classes = new ArrayList<String>(1);
278            boolean local = false;
279            
280            while (true) {
281                OuterAndInnerName outer = innerClasses.get(name);
282                if (outer == null) break;
283                if (outer.outerInternalName == null) {
284                    local = true;
285                    break;
286                }
287                classes.add(outer.innerSimpleName);
288                name = outer.outerInternalName;
289            }
290    
291            FqName outermostClassFqName = new FqName(name.replace('/', '.'));
292            classes.add(outermostClassFqName.shortName().asString());
293    
294            Collections.reverse(classes);
295    
296            FqName packageFqName = outermostClassFqName.parent();
297            FqName relativeClassName = FqName.fromSegments(classes);
298            return new ClassId(packageFqName, relativeClassName, local);
299        }
300    
301        @Override
302        public abstract int hashCode();
303    
304        @Override
305        public abstract boolean equals(Object obj);
306    
307        @Override
308        public abstract String toString();
309    }