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 }