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.JvmBytecodeBinaryVersion;
023    import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion;
024    import org.jetbrains.kotlin.name.ClassId;
025    import org.jetbrains.kotlin.name.FqName;
026    import org.jetbrains.kotlin.name.Name;
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 boolean IGNORE_OLD_METADATA = "true".equals(System.getProperty("kotlin.ignore.old.metadata"));
039    
040        private static final Map<ClassId, KotlinClassHeader.Kind> HEADER_KINDS = new HashMap<ClassId, KotlinClassHeader.Kind>();
041    
042        static {
043            // TODO: delete this at some point
044            HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinClass")), CLASS);
045            HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinFileFacade")), FILE_FACADE);
046            HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinMultifileClass")), MULTIFILE_CLASS);
047            HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinMultifileClassPart")), MULTIFILE_CLASS_PART);
048            HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinSyntheticClass")), SYNTHETIC_CLASS);
049        }
050    
051        private JvmMetadataVersion metadataVersion = null;
052        private JvmBytecodeBinaryVersion bytecodeVersion = null;
053        private String multifileClassName = null;
054        private String[] data = null;
055        private String[] strings = null;
056        private KotlinClassHeader.Kind headerKind = null;
057    
058        @Nullable
059        public KotlinClassHeader createHeader() {
060            if (headerKind == null) {
061                return null;
062            }
063    
064            if (metadataVersion == null || !metadataVersion.isCompatible()) {
065                data = null;
066            }
067            else if (shouldHaveData() && data == 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,
075                    metadataVersion != null ? metadataVersion : JvmMetadataVersion.INVALID_VERSION,
076                    bytecodeVersion != null ? bytecodeVersion : JvmBytecodeBinaryVersion.INVALID_VERSION,
077                    data,
078                    strings,
079                    multifileClassName
080            );
081        }
082    
083        private boolean shouldHaveData() {
084            return headerKind == CLASS ||
085                   headerKind == FILE_FACADE ||
086                   headerKind == MULTIFILE_CLASS_PART;
087        }
088    
089        @Nullable
090        @Override
091        public AnnotationArgumentVisitor visitAnnotation(@NotNull ClassId classId, @NotNull SourceElement source) {
092            FqName fqName = classId.asSingleFqName();
093            if (fqName.equals(METADATA_FQ_NAME)) {
094                return new KotlinMetadataArgumentVisitor();
095            }
096    
097            if (IGNORE_OLD_METADATA) return null;
098    
099            if (headerKind != null) {
100                // Ignore all Kotlin annotations except the first found
101                return null;
102            }
103    
104            KotlinClassHeader.Kind newKind = HEADER_KINDS.get(classId);
105            if (newKind != null) {
106                headerKind = newKind;
107                return new OldDeprecatedAnnotationArgumentVisitor();
108            }
109    
110            return null;
111        }
112    
113        @Override
114        public void visitEnd() {
115        }
116    
117        private class KotlinMetadataArgumentVisitor implements AnnotationArgumentVisitor {
118            @Override
119            public void visit(@Nullable Name name, @Nullable Object value) {
120                if (name == null) return;
121    
122                String string = name.asString();
123                if (KIND_FIELD_NAME.equals(string)) {
124                    if (value instanceof Integer) {
125                        headerKind = KotlinClassHeader.Kind.getById((Integer) value);
126                    }
127                }
128                else if (METADATA_VERSION_FIELD_NAME.equals(string)) {
129                    if (value instanceof int[]) {
130                        metadataVersion = new JvmMetadataVersion((int[]) value);
131                    }
132                }
133                else if (BYTECODE_VERSION_FIELD_NAME.equals(string)) {
134                    if (value instanceof int[]) {
135                        bytecodeVersion = new JvmBytecodeBinaryVersion((int[]) value);
136                    }
137                }
138                else if (METADATA_MULTIFILE_CLASS_NAME_FIELD_NAME.equals(string)) {
139                    if (value instanceof String) {
140                        multifileClassName = (String) value;
141                    }
142                }
143            }
144    
145            @Override
146            @Nullable
147            public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) {
148                String string = name.asString();
149                if (METADATA_DATA_FIELD_NAME.equals(string)) {
150                    return dataArrayVisitor();
151                }
152                else if (METADATA_STRINGS_FIELD_NAME.equals(string)) {
153                    return stringsArrayVisitor();
154                }
155                else {
156                    return null;
157                }
158            }
159    
160            @NotNull
161            private AnnotationArrayArgumentVisitor dataArrayVisitor() {
162                return new CollectStringArrayAnnotationVisitor() {
163                    @Override
164                    protected void visitEnd(@NotNull String[] result) {
165                        data = result;
166                    }
167                };
168            }
169    
170            @NotNull
171            private AnnotationArrayArgumentVisitor stringsArrayVisitor() {
172                return new CollectStringArrayAnnotationVisitor() {
173                    @Override
174                    protected void visitEnd(@NotNull String[] result) {
175                        strings = result;
176                    }
177                };
178            }
179    
180            @Override
181            public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
182            }
183    
184            @Nullable
185            @Override
186            public AnnotationArgumentVisitor visitAnnotation(@NotNull Name name, @NotNull ClassId classId) {
187                return null;
188            }
189    
190            @Override
191            public void visitEnd() {
192            }
193        }
194    
195        private class OldDeprecatedAnnotationArgumentVisitor implements AnnotationArgumentVisitor {
196            @Override
197            public void visit(@Nullable Name name, @Nullable Object value) {
198                if (name == null) return;
199    
200                String string = name.asString();
201                if ("version".equals(string)) {
202                    if (value instanceof int[]) {
203                        metadataVersion = new JvmMetadataVersion((int[]) value);
204    
205                        // If there's no bytecode binary version in the class file, we assume it to be equal to the metadata version
206                        if (bytecodeVersion == null) {
207                            bytecodeVersion = new JvmBytecodeBinaryVersion((int[]) value);
208                        }
209                    }
210                }
211                else if ("multifileClassName".equals(string)) {
212                    multifileClassName = value instanceof String ? (String) value : null;
213                }
214            }
215    
216            @Override
217            @Nullable
218            public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) {
219                String string = name.asString();
220                if ("data".equals(string) || "filePartClassNames".equals(string)) {
221                    return dataArrayVisitor();
222                }
223                else if ("strings".equals(string)) {
224                    return stringsArrayVisitor();
225                }
226                else {
227                    return null;
228                }
229            }
230    
231            @NotNull
232            private AnnotationArrayArgumentVisitor dataArrayVisitor() {
233                return new CollectStringArrayAnnotationVisitor() {
234                    @Override
235                    protected void visitEnd(@NotNull String[] data) {
236                        ReadKotlinClassHeaderAnnotationVisitor.this.data = data;
237                    }
238                };
239            }
240    
241            @NotNull
242            private AnnotationArrayArgumentVisitor stringsArrayVisitor() {
243                return new CollectStringArrayAnnotationVisitor() {
244                    @Override
245                    protected void visitEnd(@NotNull String[] data) {
246                        strings = data;
247                    }
248                };
249            }
250    
251            @Override
252            public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
253            }
254    
255            @Nullable
256            @Override
257            public AnnotationArgumentVisitor visitAnnotation(@NotNull Name name, @NotNull ClassId classId) {
258                return null;
259            }
260    
261            @Override
262            public void visitEnd() {
263            }
264        }
265    
266        private abstract static class CollectStringArrayAnnotationVisitor implements AnnotationArrayArgumentVisitor {
267            private final List<String> strings;
268    
269            public CollectStringArrayAnnotationVisitor() {
270                this.strings = new ArrayList<String>();
271            }
272    
273            @Override
274            public void visit(@Nullable Object value) {
275                if (value instanceof String) {
276                    strings.add((String) value);
277                }
278            }
279    
280            @Override
281            public void visitEnum(@NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
282            }
283    
284            @Override
285            public void visitEnd() {
286                //noinspection SSBasedInspection
287                visitEnd(strings.toArray(new String[strings.size()]));
288            }
289    
290            protected abstract void visitEnd(@NotNull String[] data);
291        }
292    }