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