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.header;
018    
019    import org.jetbrains.annotations.NotNull;
020    import org.jetbrains.annotations.Nullable;
021    import org.jetbrains.kotlin.load.java.AbiVersionUtil;
022    import org.jetbrains.kotlin.name.ClassId;
023    import org.jetbrains.kotlin.name.Name;
024    import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
025    
026    import java.util.ArrayList;
027    import java.util.HashMap;
028    import java.util.List;
029    import java.util.Map;
030    
031    import static org.jetbrains.kotlin.load.java.AbiVersionUtil.isAbiVersionCompatible;
032    import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.*;
033    import static org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass.*;
034    import static org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind.*;
035    
036    public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor {
037        private static final Map<JvmClassName, KotlinClassHeader.Kind> HEADER_KINDS = new HashMap<JvmClassName, KotlinClassHeader.Kind>();
038        private static final Map<JvmClassName, KotlinClassHeader.Kind> OLD_DEPRECATED_ANNOTATIONS_KINDS = new HashMap<JvmClassName, KotlinClassHeader.Kind>();
039    
040        private int version = AbiVersionUtil.INVALID_VERSION;
041        static {
042            HEADER_KINDS.put(KotlinClass.CLASS_NAME, CLASS);
043            HEADER_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_PACKAGE), PACKAGE_FACADE);
044            HEADER_KINDS.put(KotlinSyntheticClass.CLASS_NAME, SYNTHETIC_CLASS);
045    
046            initOldAnnotations();
047        }
048    
049        @SuppressWarnings("deprecation")
050        private static void initOldAnnotations() {
051            OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_JET_CLASS_ANNOTATION), CLASS);
052            OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_JET_PACKAGE_CLASS_ANNOTATION),
053                                                 KotlinClassHeader.Kind.PACKAGE_FACADE);
054            OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_KOTLIN_CLASS), CLASS);
055            OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_KOTLIN_PACKAGE), PACKAGE_FACADE);
056            OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_KOTLIN_PACKAGE_FRAGMENT), SYNTHETIC_CLASS);
057            OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_KOTLIN_TRAIT_IMPL), SYNTHETIC_CLASS);
058        }
059    
060        private String[] annotationData = null;
061        private KotlinClassHeader.Kind headerKind = null;
062        private KotlinClass.Kind classKind = null;
063        private KotlinSyntheticClass.Kind syntheticClassKind = null;
064    
065        @Nullable
066        public KotlinClassHeader createHeader() {
067            if (headerKind == null) {
068                return null;
069            }
070    
071            if (!AbiVersionUtil.isAbiVersionCompatible(version)) {
072                return new KotlinClassHeader(headerKind, version, null, classKind, syntheticClassKind);
073            }
074    
075            if ((headerKind == CLASS || headerKind == PACKAGE_FACADE) && annotationData == null) {
076                // This means that the annotation is found and its ABI version is compatible, but there's no "data" string array in it.
077                // We tell the outside world that there's really no annotation at all
078                return null;
079            }
080    
081            return new KotlinClassHeader(headerKind, version, annotationData, classKind, syntheticClassKind);
082        }
083    
084        @Nullable
085        @Override
086        public AnnotationArgumentVisitor visitAnnotation(@NotNull ClassId classId) {
087            if (headerKind != null) {
088                // Ignore all Kotlin annotations except the first found
089                return null;
090            }
091    
092            JvmClassName annotation = JvmClassName.byClassId(classId);
093    
094            KotlinClassHeader.Kind newKind = HEADER_KINDS.get(annotation);
095            if (newKind != null) {
096                headerKind = newKind;
097    
098                switch (newKind) {
099                    case CLASS:
100                        return new ClassHeaderReader();
101                    case PACKAGE_FACADE:
102                        return new PackageHeaderReader();
103                    case SYNTHETIC_CLASS:
104                        return new SyntheticClassHeaderReader();
105                    default:
106                        throw new IllegalStateException("Unknown kind: " + newKind);
107                }
108            }
109    
110            KotlinClassHeader.Kind oldAnnotationKind = OLD_DEPRECATED_ANNOTATIONS_KINDS.get(annotation);
111            if (oldAnnotationKind != null) {
112                headerKind = oldAnnotationKind;
113            }
114    
115            return null;
116        }
117    
118        @Override
119        public void visitEnd() {
120        }
121    
122        private abstract class HeaderAnnotationArgumentVisitor implements AnnotationArgumentVisitor {
123            protected final JvmClassName annotationClassName;
124    
125            public HeaderAnnotationArgumentVisitor(@NotNull JvmClassName annotationClassName) {
126                this.annotationClassName = annotationClassName;
127            }
128    
129            @Override
130            public void visit(@Nullable Name name, @Nullable Object value) {
131                if (name != null && name.asString().equals(ABI_VERSION_FIELD_NAME)) {
132                    version = value == null ? AbiVersionUtil.INVALID_VERSION : (Integer) value;
133                }
134                else {
135                    unexpectedArgument(name);
136                }
137            }
138    
139            @Override
140            @Nullable
141            public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) {
142                if (name.asString().equals(DATA_FIELD_NAME)) {
143                    return stringArrayVisitor();
144                }
145                else if (isAbiVersionCompatible(version)) {
146                    throw new IllegalStateException("Unexpected array argument " + name + " for annotation " + annotationClassName);
147                }
148    
149                return null;
150            }
151    
152            @Nullable
153            @Override
154            public AnnotationArgumentVisitor visitAnnotation(@NotNull Name name, @NotNull ClassId classId) {
155                return null;
156            }
157    
158            @NotNull
159            private AnnotationArrayArgumentVisitor stringArrayVisitor() {
160                final List<String> strings = new ArrayList<String>(1);
161                return new AnnotationArrayArgumentVisitor() {
162                    @Override
163                    public void visit(@Nullable Object value) {
164                        if (!(value instanceof String)) {
165                            throw new IllegalStateException("Unexpected argument value: " + value);
166                        }
167    
168                        strings.add((String) value);
169                    }
170    
171                    @Override
172                    public void visitEnum(@NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
173                        unexpectedArgument(null);
174                    }
175    
176                    @Override
177                    public void visitEnd() {
178                        //noinspection SSBasedInspection
179                        annotationData = strings.toArray(new String[strings.size()]);
180                    }
181                };
182            }
183    
184            @Nullable
185            protected AnnotationArrayArgumentVisitor unexpectedArgument(@Nullable Name name) {
186                if (isAbiVersionCompatible(version)) {
187                    throw new IllegalStateException("Unexpected argument " + name + " for annotation " + annotationClassName);
188                }
189                return null;
190            }
191    
192            protected void unexpectedEnumArgument(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
193                if (isAbiVersionCompatible(version)) {
194                    throw new IllegalStateException("Unexpected enum entry for class annotation " + annotationClassName + ": " +
195                                                    name + "=" + enumClassId + "." + enumEntryName);
196                }
197            }
198    
199            @Override
200            public void visitEnd() {
201            }
202        }
203    
204        private class ClassHeaderReader extends HeaderAnnotationArgumentVisitor {
205            public ClassHeaderReader() {
206                super(KotlinClass.CLASS_NAME);
207            }
208    
209            @Override
210            public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
211                if (KotlinClass.KIND_CLASS_ID.equals(enumClassId) && KIND_FIELD_NAME.equals(name.asString())) {
212                    classKind = valueOfOrNull(KotlinClass.Kind.class, enumEntryName.asString());
213                    if (classKind != null) return;
214                }
215                unexpectedEnumArgument(name, enumClassId, enumEntryName);
216            }
217        }
218    
219        private class PackageHeaderReader extends HeaderAnnotationArgumentVisitor {
220            public PackageHeaderReader() {
221                super(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_PACKAGE));
222            }
223    
224            @Override
225            public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
226                unexpectedEnumArgument(name, enumClassId, enumEntryName);
227            }
228        }
229    
230        private class SyntheticClassHeaderReader extends HeaderAnnotationArgumentVisitor {
231            public SyntheticClassHeaderReader() {
232                super(KotlinSyntheticClass.CLASS_NAME);
233            }
234    
235            @Override
236            public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
237                if (KotlinSyntheticClass.KIND_CLASS_ID.equals(enumClassId) && KIND_FIELD_NAME.equals(name.asString())) {
238                    syntheticClassKind = valueOfOrNull(KotlinSyntheticClass.Kind.class, enumEntryName.asString());
239                    if (syntheticClassKind != null) return;
240                }
241                unexpectedEnumArgument(name, enumClassId, enumEntryName);
242            }
243        }
244    
245        // This function is needed here because Enum.valueOf() throws exception if there's no such value,
246        // but we don't want to fail if we're loading the header with an _incompatible_ ABI version
247        @Nullable
248        private static <E extends Enum<E>> E valueOfOrNull(@NotNull Class<E> enumClass, @NotNull String entry) {
249            try {
250                return Enum.valueOf(enumClass, entry);
251            }
252            catch (IllegalArgumentException e) {
253                return null;
254            }
255        }
256    }