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 }