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