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