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 }