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