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