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.FqName;
025    import org.jetbrains.kotlin.name.Name;
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<ClassId, KotlinClassHeader.Kind> HEADER_KINDS = new HashMap<ClassId, KotlinClassHeader.Kind>();
039    
040        static {
041            HEADER_KINDS.put(ClassId.topLevel(KOTLIN_CLASS), CLASS);
042            HEADER_KINDS.put(ClassId.topLevel(KOTLIN_PACKAGE), PACKAGE_FACADE);
043            HEADER_KINDS.put(ClassId.topLevel(KOTLIN_FILE_FACADE), FILE_FACADE);
044            HEADER_KINDS.put(ClassId.topLevel(KOTLIN_MULTIFILE_CLASS), MULTIFILE_CLASS);
045            HEADER_KINDS.put(ClassId.topLevel(KOTLIN_MULTIFILE_CLASS_PART), MULTIFILE_CLASS_PART);
046            HEADER_KINDS.put(ClassId.topLevel(KOTLIN_SYNTHETIC_CLASS), SYNTHETIC_CLASS);
047        }
048    
049        private BinaryVersion version = AbiVersionUtil.INVALID_VERSION;
050        private String multifileClassName = null;
051        private String[] filePartClassNames = null;
052        private String[] annotationData = null;
053        private String[] strings = null;
054        private KotlinClassHeader.Kind headerKind = null;
055        private String syntheticClassKind = null;
056        private boolean isInterfaceDefaultImpls = false;
057        private boolean isLocalClass = false;
058    
059        @Nullable
060        public KotlinClassHeader createHeader() {
061            if (headerKind == null) {
062                return null;
063            }
064    
065            if (!AbiVersionUtil.isAbiVersionCompatible(version)) {
066                annotationData = null;
067            }
068            else if (shouldHaveData() && annotationData == null) {
069                // This means that the annotation is found and its ABI version is compatible, but there's no "data" string array in it.
070                // We tell the outside world that there's really no annotation at all
071                return null;
072            }
073    
074            return new KotlinClassHeader(
075                    headerKind, version, annotationData, strings, syntheticClassKind, filePartClassNames, multifileClassName,
076                    isInterfaceDefaultImpls, isLocalClass
077            );
078        }
079    
080        private boolean shouldHaveData() {
081            return headerKind == CLASS ||
082                   headerKind == PACKAGE_FACADE ||
083                   headerKind == FILE_FACADE ||
084                   headerKind == MULTIFILE_CLASS_PART;
085        }
086    
087        @Nullable
088        @Override
089        public AnnotationArgumentVisitor visitAnnotation(@NotNull ClassId classId, @NotNull SourceElement source) {
090            FqName fqName = classId.asSingleFqName();
091            if (KOTLIN_INTERFACE_DEFAULT_IMPLS.equals(fqName)) {
092                isInterfaceDefaultImpls = true;
093                return null;
094            }
095            else if (KOTLIN_LOCAL_CLASS.equals(fqName)) {
096                isLocalClass = true;
097                return null;
098            }
099    
100            if (headerKind != null) {
101                // Ignore all Kotlin annotations except the first found
102                return null;
103            }
104    
105            KotlinClassHeader.Kind newKind = HEADER_KINDS.get(classId);
106            if (newKind != null) {
107                headerKind = newKind;
108    
109                switch (newKind) {
110                    case CLASS:
111                    case PACKAGE_FACADE:
112                    case FILE_FACADE:
113                    case MULTIFILE_CLASS:
114                    case MULTIFILE_CLASS_PART:
115                        return new HeaderAnnotationArgumentVisitor();
116                    case SYNTHETIC_CLASS:
117                        return new SyntheticClassHeaderReader();
118                    default:
119                        return null;
120                }
121            }
122    
123            return null;
124        }
125    
126        @Override
127        public void visitEnd() {
128        }
129    
130        private class HeaderAnnotationArgumentVisitor implements AnnotationArgumentVisitor {
131            @Override
132            public void visit(@Nullable Name name, @Nullable Object value) {
133                if (name == null) return;
134    
135                String string = name.asString();
136                if (VERSION_FIELD_NAME.equals(string)) {
137                    version = value instanceof int[] ? BinaryVersion.create((int[]) value) : AbiVersionUtil.INVALID_VERSION;
138                }
139                else if (MULTIFILE_CLASS_NAME_FIELD_NAME.equals(string)) {
140                    multifileClassName = value instanceof String ? (String) value : null;
141                }
142                else if (OLD_ABI_VERSION_FIELD_NAME.equals(string)) {
143                    if (version == AbiVersionUtil.INVALID_VERSION && value instanceof Integer && (Integer) value > 0) {
144                        version = BinaryVersion.create(0, (Integer) value, 0);
145                    }
146                }
147            }
148    
149            @Override
150            @Nullable
151            public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) {
152                String string = name.asString();
153                if (DATA_FIELD_NAME.equals(string)) {
154                    return dataArrayVisitor();
155                }
156                else if (STRINGS_FIELD_NAME.equals(string)) {
157                    return stringsArrayVisitor();
158                }
159                else if (FILE_PART_CLASS_NAMES_FIELD_NAME.equals(string)) {
160                    return filePartClassNamesVisitor();
161                }
162                else {
163                    return null;
164                }
165            }
166    
167            @NotNull
168            private AnnotationArrayArgumentVisitor filePartClassNamesVisitor() {
169                return new CollectStringArrayAnnotationVisitor() {
170                    @Override
171                    protected void visitEnd(@NotNull String[] data) {
172                        filePartClassNames = data;
173                    }
174                };
175            }
176    
177            @NotNull
178            private AnnotationArrayArgumentVisitor dataArrayVisitor() {
179                return new CollectStringArrayAnnotationVisitor() {
180                    @Override
181                    protected void visitEnd(@NotNull String[] data) {
182                        annotationData = data;
183                    }
184                };
185            }
186    
187            @NotNull
188            private AnnotationArrayArgumentVisitor stringsArrayVisitor() {
189                return new CollectStringArrayAnnotationVisitor() {
190                    @Override
191                    protected void visitEnd(@NotNull String[] data) {
192                        strings = data;
193                    }
194                };
195            }
196    
197            @Override
198            public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
199            }
200    
201            @Nullable
202            @Override
203            public AnnotationArgumentVisitor visitAnnotation(@NotNull Name name, @NotNull ClassId classId) {
204                return null;
205            }
206    
207            @Override
208            public void visitEnd() {
209            }
210    
211            private abstract class CollectStringArrayAnnotationVisitor implements AnnotationArrayArgumentVisitor {
212                private final List<String> strings;
213    
214                public CollectStringArrayAnnotationVisitor() {
215                    this.strings = new ArrayList<String>();
216                }
217    
218                @Override
219                public void visit(@Nullable Object value) {
220                    if (value instanceof String) {
221                        strings.add((String) value);
222                    }
223                }
224    
225                @Override
226                public void visitEnum(@NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
227                }
228    
229                @Override
230                public void visitEnd() {
231                    //noinspection SSBasedInspection
232                    visitEnd(strings.toArray(new String[strings.size()]));
233                }
234    
235                protected abstract void visitEnd(@NotNull String[] data);
236            }
237        }
238    
239        private class SyntheticClassHeaderReader extends HeaderAnnotationArgumentVisitor {
240            @Override
241            public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
242                if ("Kind".equals(enumClassId.getShortClassName().asString()) &&
243                    enumClassId.isNestedClass() &&
244                    enumClassId.getOuterClassId().equals(ClassId.topLevel(KOTLIN_SYNTHETIC_CLASS)) &&
245                    "kind".equals(name.asString())) {
246                    syntheticClassKind = enumEntryName.asString();
247                }
248            }
249        }
250    }