001 package org.jetbrains.jet.lang.resolve.java.resolver;
002
003 import com.intellij.openapi.vfs.VirtualFile;
004 import org.jetbrains.annotations.NotNull;
005 import org.jetbrains.annotations.Nullable;
006 import org.jetbrains.asm4.AnnotationVisitor;
007 import org.jetbrains.asm4.ClassReader;
008 import org.jetbrains.asm4.ClassVisitor;
009 import org.jetbrains.asm4.Opcodes;
010 import org.jetbrains.jet.descriptors.serialization.ClassData;
011 import org.jetbrains.jet.descriptors.serialization.JavaProtoBufUtil;
012 import org.jetbrains.jet.descriptors.serialization.PackageData;
013 import org.jetbrains.jet.lang.resolve.java.AbiVersionUtil;
014 import org.jetbrains.jet.lang.resolve.java.JvmAnnotationNames;
015 import org.jetbrains.jet.lang.resolve.java.JvmClassName;
016 import org.jetbrains.jet.lang.resolve.name.FqName;
017
018 import java.io.IOException;
019 import java.io.InputStream;
020 import java.util.ArrayList;
021 import java.util.List;
022
023 import static org.jetbrains.asm4.ClassReader.*;
024 import static org.jetbrains.jet.lang.resolve.java.AbiVersionUtil.isAbiVersionCompatible;
025
026 public final class KotlinClassFileHeader {
027 @NotNull
028 public static KotlinClassFileHeader readKotlinHeaderFromClassFile(@NotNull VirtualFile virtualFile) {
029 try {
030 InputStream inputStream = virtualFile.getInputStream();
031 try {
032 ClassReader reader = new ClassReader(inputStream);
033 KotlinClassFileHeader classFileData = new KotlinClassFileHeader();
034 reader.accept(classFileData.new ReadDataFromAnnotationVisitor(), SKIP_CODE | SKIP_FRAMES | SKIP_DEBUG);
035 return classFileData;
036 }
037 finally {
038 inputStream.close();
039 }
040 }
041 catch (IOException e) {
042 throw new RuntimeException(e);
043 }
044 }
045
046 @SuppressWarnings("deprecation")
047 public enum HeaderType {
048 CLASS(JvmAnnotationNames.KOTLIN_CLASS),
049 PACKAGE(JvmAnnotationNames.KOTLIN_PACKAGE),
050 OLD_CLASS(JvmAnnotationNames.OLD_JET_CLASS_ANNOTATION),
051 OLD_PACKAGE(JvmAnnotationNames.OLD_JET_PACKAGE_CLASS_ANNOTATION),
052 NONE(null);
053
054 @Nullable
055 private final JvmClassName correspondingAnnotation;
056
057 HeaderType(@Nullable JvmClassName annotation) {
058 correspondingAnnotation = annotation;
059 }
060
061 boolean isValidAnnotation() {
062 return this == CLASS || this == PACKAGE;
063 }
064
065 @NotNull
066 public static HeaderType byDescriptor(@NotNull String desc) {
067 for (HeaderType headerType : HeaderType.values()) {
068 JvmClassName annotation = headerType.correspondingAnnotation;
069 if (annotation == null) {
070 continue;
071 }
072 if (desc.equals(annotation.getDescriptor())) {
073 return headerType;
074 }
075 }
076 return NONE;
077 }
078 }
079
080 private int version = AbiVersionUtil.INVALID_VERSION;
081
082 @Nullable
083 private String[] annotationData = null;
084 @NotNull
085 HeaderType type = HeaderType.NONE;
086 @Nullable
087 JvmClassName jvmClassName = null;
088
089 public int getVersion() {
090 return version;
091 }
092
093 @NotNull
094 public HeaderType getType() {
095 return type;
096 }
097
098 /*
099 Checks that this is a header for compiled Kotlin file with correct abi version which can be processed by compiler or the IDE.
100 */
101 public boolean isKotlinCompiledFile() {
102 return type.isValidAnnotation() && isAbiVersionCompatible(version);
103 }
104
105 /**
106 * @return FQ name for class header or package class FQ name for package header (e.g. <code>test.TestPackage</code>)
107 */
108 @NotNull
109 public FqName getFqName() {
110 assert jvmClassName != null;
111 return jvmClassName.getFqName();
112 }
113
114 public String[] getAnnotationData() {
115 assertDataRead();
116 return annotationData;
117 }
118
119 private void assertDataRead() {
120 if (annotationData == null && type != HeaderType.NONE) {
121 throw new IllegalStateException("Data for annotations " + type.correspondingAnnotation + " was not read.");
122 }
123 }
124
125 @NotNull
126 public ClassData readClassData() {
127 assert type == HeaderType.CLASS;
128 return JavaProtoBufUtil.readClassDataFrom(getAnnotationData());
129 }
130
131 @NotNull
132 public PackageData readPackageData() {
133 assert type == HeaderType.PACKAGE;
134 return JavaProtoBufUtil.readPackageDataFrom(getAnnotationData());
135 }
136
137 private class ReadDataFromAnnotationVisitor extends ClassVisitor {
138
139 public ReadDataFromAnnotationVisitor() {
140 super(Opcodes.ASM4);
141 }
142
143 @Override
144 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
145 jvmClassName = JvmClassName.byInternalName(name);
146 }
147
148 @Override
149 public AnnotationVisitor visitAnnotation(final String desc, boolean visible) {
150 HeaderType headerTypeByAnnotation = HeaderType.byDescriptor(desc);
151 if (headerTypeByAnnotation == HeaderType.NONE) {
152 return null;
153 }
154 if (headerTypeByAnnotation.isValidAnnotation() && type.isValidAnnotation()) {
155 throw new IllegalStateException("Both " + type.correspondingAnnotation + " and "
156 + headerTypeByAnnotation.correspondingAnnotation + " present!");
157 }
158 if (!type.isValidAnnotation()) {
159 type = headerTypeByAnnotation;
160 }
161 if (!headerTypeByAnnotation.isValidAnnotation()) {
162 return null;
163 }
164 return new AnnotationVisitor(Opcodes.ASM4) {
165 @Override
166 public void visit(String name, Object value) {
167 if (name.equals(JvmAnnotationNames.ABI_VERSION_FIELD_NAME)) {
168 version = (Integer) value;
169 }
170 else if (isAbiVersionCompatible(version)) {
171 throw new IllegalStateException("Unexpected argument " + name + " for annotation " + desc);
172 }
173 }
174
175 @Override
176 public AnnotationVisitor visitArray(String name) {
177 if (name.equals(JvmAnnotationNames.DATA_FIELD_NAME)) {
178 return stringArrayVisitor();
179 }
180 else if (isAbiVersionCompatible(version)) {
181 throw new IllegalStateException("Unexpected array argument " + name + " for annotation " + desc);
182 }
183
184 return super.visitArray(name);
185 }
186
187 @NotNull
188 private AnnotationVisitor stringArrayVisitor() {
189 final List<String> strings = new ArrayList<String>(1);
190 return new AnnotationVisitor(Opcodes.ASM4) {
191 @Override
192 public void visit(String name, Object value) {
193 if (!(value instanceof String)) {
194 throw new IllegalStateException("Unexpected argument value: " + value);
195 }
196
197 strings.add((String) value);
198 }
199
200 @Override
201 public void visitEnd() {
202 annotationData = strings.toArray(new String[strings.size()]);
203 }
204 };
205 }
206 };
207 }
208 }
209
210 private KotlinClassFileHeader() {
211 }
212 }