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 }