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    }