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