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