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.openapi.util.Ref;
020    import kotlin.Function3;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.jet.lang.resolve.java.JvmAnnotationNames;
024    import org.jetbrains.jet.lang.resolve.kotlin.header.KotlinClassHeader;
025    import org.jetbrains.jet.lang.resolve.kotlin.header.ReadKotlinClassHeaderAnnotationVisitor;
026    import org.jetbrains.jet.lang.resolve.name.ClassId;
027    import org.jetbrains.jet.lang.resolve.name.FqName;
028    import org.jetbrains.jet.lang.resolve.name.FqNameUnsafe;
029    import org.jetbrains.jet.lang.resolve.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(@NotNull String outerInternalName, @NotNull 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, @NotNull String outerName, @NotNull 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 class 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                    if (outerName != null && innerName != null) {
102                        innerClasses.add(name, outerName, innerName);
103                    }
104                }
105    
106                @Override
107                public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) {
108                    return convertAnnotationVisitor(readHeaderVisitor, desc, innerClasses);
109                }
110    
111                @Override
112                public void visitEnd() {
113                    readHeaderVisitor.visitEnd();
114                }
115            }, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
116    
117            String className = classNameRef.get();
118            if (className == null) return null;
119    
120            KotlinClassHeader header = readHeaderVisitor.createHeader();
121            if (header == null) return null;
122    
123            ClassId id = resolveNameByInternalName(className, innerClasses);
124            return factory.invoke(id, header, innerClasses);
125        }
126    
127        @NotNull
128        @Override
129        public ClassId getClassId() {
130            return classId;
131        }
132    
133        @NotNull
134        @Override
135        public KotlinClassHeader getClassHeader() {
136            return classHeader;
137        }
138    
139        @Override
140        public void loadClassAnnotations(@NotNull final AnnotationVisitor annotationVisitor) {
141            new ClassReader(getFileContents()).accept(new ClassVisitor(ASM5) {
142                @Override
143                public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) {
144                    return convertAnnotationVisitor(annotationVisitor, desc, innerClasses);
145                }
146    
147                @Override
148                public void visitEnd() {
149                    annotationVisitor.visitEnd();
150                }
151            }, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
152        }
153    
154        @Nullable
155        private static org.jetbrains.org.objectweb.asm.AnnotationVisitor convertAnnotationVisitor(
156                @NotNull AnnotationVisitor visitor, @NotNull String desc, @NotNull InnerClassesInfo innerClasses
157        ) {
158            AnnotationArgumentVisitor v = visitor.visitAnnotation(resolveNameByDesc(desc, innerClasses));
159            return v == null ? null : convertAnnotationVisitor(v, innerClasses);
160        }
161    
162        @NotNull
163        private static org.jetbrains.org.objectweb.asm.AnnotationVisitor convertAnnotationVisitor(
164                @NotNull final AnnotationArgumentVisitor v, @NotNull final InnerClassesInfo innerClasses
165        ) {
166            return new org.jetbrains.org.objectweb.asm.AnnotationVisitor(ASM5) {
167                @Override
168                public void visit(String name, @NotNull Object value) {
169                    v.visit(name == null ? null : Name.identifier(name), value);
170                }
171    
172                @Override
173                public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitArray(String name) {
174                    final AnnotationArrayArgumentVisitor arv = v.visitArray(Name.guess(name));
175                    return arv == null ? null : new org.jetbrains.org.objectweb.asm.AnnotationVisitor(ASM5) {
176                        @Override
177                        public void visit(String name, @NotNull Object value) {
178                            arv.visit(value);
179                        }
180    
181                        @Override
182                        public void visitEnum(String name, @NotNull String desc, @NotNull String value) {
183                            arv.visitEnum(resolveNameByDesc(desc, innerClasses), Name.identifier(value));
184                        }
185    
186                        @Override
187                        public void visitEnd() {
188                            arv.visitEnd();
189                        }
190                    };
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            if (name.equals(JvmAnnotationNames.KotlinSyntheticClass.KIND_INTERNAL_NAME)) {
266                // TODO: this is a hack which can be dropped once JVM back-end begins to write InnerClasses attribute for all referenced classes
267                return JvmAnnotationNames.KotlinSyntheticClass.KIND_CLASS_ID;
268            }
269    
270            List<String> classes = new ArrayList<String>(1);
271            
272            while (true) {
273                OuterAndInnerName outer = innerClasses.get(name);
274                if (outer == null) break;
275                classes.add(outer.innerSimpleName);
276                name = outer.outerInternalName;
277            }
278    
279            FqName outermostClassFqName = new FqName(name.replace('/', '.'));
280            classes.add(outermostClassFqName.shortName().asString());
281    
282            Collections.reverse(classes);
283    
284            FqName packageFqName = outermostClassFqName.parent();
285            FqNameUnsafe relativeClassName = FqNameUnsafe.fromSegments(classes);
286            return new ClassId(packageFqName, relativeClassName);
287        }
288    
289        @Override
290        public abstract int hashCode();
291    
292        @Override
293        public abstract boolean equals(Object obj);
294    
295        @Override
296        public abstract String toString();
297    }