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