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 }