/* * Copyright 2010-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.kotlin.load.kotlin.header; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.descriptors.SourceElement; import org.jetbrains.kotlin.load.java.JvmBytecodeBinaryVersion; import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion; import org.jetbrains.kotlin.name.ClassId; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.name.Name; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.*; import static org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass.*; import static org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind.*; public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor { private static final boolean IGNORE_OLD_METADATA = "true".equals(System.getProperty("kotlin.ignore.old.metadata")); private static final Map<ClassId, KotlinClassHeader.Kind> HEADER_KINDS = new HashMap<ClassId, KotlinClassHeader.Kind>(); static { // TODO: delete this at some point HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinClass")), CLASS); HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinFileFacade")), FILE_FACADE); HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinMultifileClass")), MULTIFILE_CLASS); HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinMultifileClassPart")), MULTIFILE_CLASS_PART); HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinSyntheticClass")), SYNTHETIC_CLASS); } private JvmMetadataVersion metadataVersion = null; private JvmBytecodeBinaryVersion bytecodeVersion = null; private String extraString = null; private int extraInt = 0; private String[] data = null; private String[] strings = null; private String[] incompatibleData = null; private KotlinClassHeader.Kind headerKind = null; @Nullable public KotlinClassHeader createHeader() { if (headerKind == null) { return null; } if (!metadataVersion.isCompatible()) { incompatibleData = data; } if (metadataVersion == null || !metadataVersion.isCompatible()) { data = null; } else if (shouldHaveData() && data == null) { // This means that the annotation is found and its ABI version is compatible, but there's no "data" string array in it. // We tell the outside world that there's really no annotation at all return null; } return new KotlinClassHeader( headerKind, metadataVersion != null ? metadataVersion : JvmMetadataVersion.INVALID_VERSION, bytecodeVersion != null ? bytecodeVersion : JvmBytecodeBinaryVersion.INVALID_VERSION, data, incompatibleData, strings, extraString, extraInt ); } private boolean shouldHaveData() { return headerKind == CLASS || headerKind == FILE_FACADE || headerKind == MULTIFILE_CLASS_PART; } @Nullable @Override public AnnotationArgumentVisitor visitAnnotation(@NotNull ClassId classId, @NotNull SourceElement source) { FqName fqName = classId.asSingleFqName(); if (fqName.equals(METADATA_FQ_NAME)) { return new KotlinMetadataArgumentVisitor(); } if (IGNORE_OLD_METADATA) return null; if (headerKind != null) { // Ignore all Kotlin annotations except the first found return null; } KotlinClassHeader.Kind newKind = HEADER_KINDS.get(classId); if (newKind != null) { headerKind = newKind; return new OldDeprecatedAnnotationArgumentVisitor(); } return null; } @Override public void visitEnd() { } private class KotlinMetadataArgumentVisitor implements AnnotationArgumentVisitor { @Override public void visit(@Nullable Name name, @Nullable Object value) { if (name == null) return; String string = name.asString(); if (KIND_FIELD_NAME.equals(string)) { if (value instanceof Integer) { headerKind = KotlinClassHeader.Kind.getById((Integer) value); } } else if (METADATA_VERSION_FIELD_NAME.equals(string)) { if (value instanceof int[]) { metadataVersion = new JvmMetadataVersion((int[]) value); } } else if (BYTECODE_VERSION_FIELD_NAME.equals(string)) { if (value instanceof int[]) { bytecodeVersion = new JvmBytecodeBinaryVersion((int[]) value); } } else if (METADATA_EXTRA_STRING_FIELD_NAME.equals(string)) { if (value instanceof String) { extraString = (String) value; } } else if (METADATA_EXTRA_INT_FIELD_NAME.equals(string)) { if (value instanceof Integer) { extraInt = (Integer) value; } } } @Override @Nullable public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) { String string = name.asString(); if (METADATA_DATA_FIELD_NAME.equals(string)) { return dataArrayVisitor(); } else if (METADATA_STRINGS_FIELD_NAME.equals(string)) { return stringsArrayVisitor(); } else { return null; } } @NotNull private AnnotationArrayArgumentVisitor dataArrayVisitor() { return new CollectStringArrayAnnotationVisitor() { @Override protected void visitEnd(@NotNull String[] result) { data = result; } }; } @NotNull private AnnotationArrayArgumentVisitor stringsArrayVisitor() { return new CollectStringArrayAnnotationVisitor() { @Override protected void visitEnd(@NotNull String[] result) { strings = result; } }; } @Override public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) { } @Nullable @Override public AnnotationArgumentVisitor visitAnnotation(@NotNull Name name, @NotNull ClassId classId) { return null; } @Override public void visitEnd() { } } private class OldDeprecatedAnnotationArgumentVisitor implements AnnotationArgumentVisitor { @Override public void visit(@Nullable Name name, @Nullable Object value) { if (name == null) return; String string = name.asString(); if ("version".equals(string)) { if (value instanceof int[]) { metadataVersion = new JvmMetadataVersion((int[]) value); // If there's no bytecode binary version in the class file, we assume it to be equal to the metadata version if (bytecodeVersion == null) { bytecodeVersion = new JvmBytecodeBinaryVersion((int[]) value); } } } else if ("multifileClassName".equals(string)) { extraString = value instanceof String ? (String) value : null; } } @Override @Nullable public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) { String string = name.asString(); if ("data".equals(string) || "filePartClassNames".equals(string)) { return dataArrayVisitor(); } else if ("strings".equals(string)) { return stringsArrayVisitor(); } else { return null; } } @NotNull private AnnotationArrayArgumentVisitor dataArrayVisitor() { return new CollectStringArrayAnnotationVisitor() { @Override protected void visitEnd(@NotNull String[] data) { ReadKotlinClassHeaderAnnotationVisitor.this.data = data; } }; } @NotNull private AnnotationArrayArgumentVisitor stringsArrayVisitor() { return new CollectStringArrayAnnotationVisitor() { @Override protected void visitEnd(@NotNull String[] data) { strings = data; } }; } @Override public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) { } @Nullable @Override public AnnotationArgumentVisitor visitAnnotation(@NotNull Name name, @NotNull ClassId classId) { return null; } @Override public void visitEnd() { } } private abstract static class CollectStringArrayAnnotationVisitor implements AnnotationArrayArgumentVisitor { private final List<String> strings; public CollectStringArrayAnnotationVisitor() { this.strings = new ArrayList<String>(); } @Override public void visit(@Nullable Object value) { if (value instanceof String) { strings.add((String) value); } } @Override public void visitEnum(@NotNull ClassId enumClassId, @NotNull Name enumEntryName) { } @Override public void visitEnd() { //noinspection SSBasedInspection visitEnd(strings.toArray(new String[strings.size()])); } protected abstract void visitEnd(@NotNull String[] data); } }