001    /*
002     * Copyright 2010-2013 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.jet.lang.resolve.kotlin.header;
018    
019    import org.jetbrains.annotations.NotNull;
020    import org.jetbrains.annotations.Nullable;
021    import org.jetbrains.jet.lang.resolve.java.AbiVersionUtil;
022    import org.jetbrains.jet.lang.resolve.java.JvmClassName;
023    import org.jetbrains.jet.lang.resolve.name.FqName;
024    import org.jetbrains.jet.lang.resolve.name.Name;
025    
026    import java.util.*;
027    
028    import static org.jetbrains.jet.lang.resolve.java.AbiVersionUtil.isAbiVersionCompatible;
029    import static org.jetbrains.jet.lang.resolve.java.JvmAnnotationNames.*;
030    import static org.jetbrains.jet.lang.resolve.kotlin.KotlinJvmBinaryClass.*;
031    import static org.jetbrains.jet.lang.resolve.kotlin.header.KotlinClassHeader.Kind.*;
032    
033    public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor {
034        private static final Map<JvmClassName, KotlinClassHeader.Kind> HEADER_KINDS = new HashMap<JvmClassName, KotlinClassHeader.Kind>();
035    
036        static {
037            HEADER_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_CLASS), CLASS);
038            HEADER_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_PACKAGE), PACKAGE_FACADE);
039            HEADER_KINDS.put(KotlinSyntheticClass.CLASS_NAME, SYNTHETIC_CLASS);
040    
041            @SuppressWarnings("deprecation")
042            List<FqName> incompatible = Arrays.asList(OLD_JET_CLASS_ANNOTATION, OLD_JET_PACKAGE_CLASS_ANNOTATION, OLD_KOTLIN_CLASS,
043                                                      OLD_KOTLIN_PACKAGE, OLD_KOTLIN_PACKAGE_FRAGMENT, OLD_KOTLIN_TRAIT_IMPL);
044            for (FqName fqName : incompatible) {
045                HEADER_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(fqName), INCOMPATIBLE_ABI_VERSION);
046            }
047        }
048    
049        private int version = AbiVersionUtil.INVALID_VERSION;
050        private String[] annotationData = null;
051        private KotlinClassHeader.Kind headerKind = null;
052        private KotlinSyntheticClass.Kind syntheticClassKind = null;
053    
054        @Nullable
055        public KotlinClassHeader createHeader() {
056            if (headerKind == null) {
057                return null;
058            }
059    
060            if (!AbiVersionUtil.isAbiVersionCompatible(version)) {
061                return new KotlinClassHeader(INCOMPATIBLE_ABI_VERSION, version, null, null);
062            }
063    
064            if ((headerKind == CLASS || headerKind == PACKAGE_FACADE) && annotationData == null) {
065                // This means that the annotation is found and its ABI version is compatible, but there's no "data" string array in it.
066                // We tell the outside world that there's really no annotation at all
067                return null;
068            }
069    
070            return new KotlinClassHeader(headerKind, version, annotationData, syntheticClassKind);
071        }
072    
073        @Nullable
074        @Override
075        public AnnotationArgumentVisitor visitAnnotation(@NotNull JvmClassName annotation) {
076            KotlinClassHeader.Kind newKind = HEADER_KINDS.get(annotation);
077            if (newKind == null) return null;
078    
079            if (headerKind != null) {
080                // Ignore all Kotlin annotations except the first found
081                return null;
082            }
083    
084            headerKind = newKind;
085    
086            if (newKind == CLASS || newKind == PACKAGE_FACADE) {
087                return kotlinClassOrPackageVisitor(annotation);
088            }
089            else if (newKind == SYNTHETIC_CLASS) {
090                return syntheticClassAnnotationVisitor();
091            }
092    
093            return null;
094        }
095    
096        @Override
097        public void visitEnd() {
098        }
099    
100        @NotNull
101        private AnnotationArgumentVisitor kotlinClassOrPackageVisitor(@NotNull final JvmClassName annotationClassName) {
102            return new AnnotationArgumentVisitor() {
103                @Override
104                public void visit(@Nullable Name name, @Nullable Object value) {
105                    visitIntValueForSupportedAnnotation(name, value, annotationClassName);
106                }
107    
108                @Override
109                public void visitEnum(@NotNull Name name, @NotNull JvmClassName enumClassName, @NotNull Name enumEntryName) {
110                    unexpectedArgument(name, annotationClassName);
111                }
112    
113                @Override
114                @Nullable
115                public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) {
116                    if (name.asString().equals(DATA_FIELD_NAME)) {
117                        return stringArrayVisitor();
118                    }
119                    else if (isAbiVersionCompatible(version)) {
120                        throw new IllegalStateException("Unexpected array argument " + name + " for annotation " + annotationClassName);
121                    }
122    
123                    return null;
124                }
125    
126                @NotNull
127                private AnnotationArrayArgumentVisitor stringArrayVisitor() {
128                    final List<String> strings = new ArrayList<String>(1);
129                    return new AnnotationArrayArgumentVisitor() {
130                        @Override
131                        public void visit(@Nullable Object value) {
132                            if (!(value instanceof String)) {
133                                throw new IllegalStateException("Unexpected argument value: " + value);
134                            }
135    
136                            strings.add((String) value);
137                        }
138    
139                        @Override
140                        public void visitEnum(@NotNull JvmClassName enumClassName, @NotNull Name enumEntryName) {
141                            unexpectedArgument(null, annotationClassName);
142                        }
143    
144                        @Override
145                        public void visitEnd() {
146                            annotationData = strings.toArray(new String[strings.size()]);
147                        }
148                    };
149                }
150    
151                @Override
152                public void visitEnd() {
153                }
154            };
155        }
156    
157        @NotNull
158        private AnnotationArgumentVisitor syntheticClassAnnotationVisitor() {
159            return new AnnotationArgumentVisitor() {
160                @Override
161                public void visit(@Nullable Name name, @Nullable Object value) {
162                    visitIntValueForSupportedAnnotation(name, value, KotlinSyntheticClass.CLASS_NAME);
163                }
164    
165                @Override
166                public void visitEnum(@NotNull Name name, @NotNull JvmClassName enumClassName, @NotNull Name enumEntryName) {
167                    if (enumClassName.getInternalName().equals(KotlinSyntheticClass.KIND_INTERNAL_NAME) &&
168                        name.equals(KotlinSyntheticClass.KIND_FIELD_NAME)) {
169                        // Don't call KotlinSyntheticClass.Kind.valueOf() here, because it will throw an exception if there's no such value,
170                        // but we don't want to fail if we're loading the header with an _incompatible_ ABI version
171                        syntheticClassKind = KotlinSyntheticClass.Kind.valueOfOrNull(enumEntryName.asString());
172                        if (syntheticClassKind != null) return;
173                    }
174                    if (isAbiVersionCompatible(version)) {
175                        throw new IllegalStateException("Unexpected enum entry for synthetic class annotation: " +
176                                                        name + "=" + enumClassName + "." + enumEntryName);
177                    }
178                }
179    
180                @Nullable
181                @Override
182                public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) {
183                    return unexpectedArgument(name, KotlinSyntheticClass.CLASS_NAME);
184                }
185    
186                @Override
187                public void visitEnd() {
188                }
189            };
190        }
191    
192        private void visitIntValueForSupportedAnnotation(@Nullable Name name, @Nullable Object value, @NotNull JvmClassName className) {
193            if (name != null && name.asString().equals(ABI_VERSION_FIELD_NAME)) {
194                version = value == null ? AbiVersionUtil.INVALID_VERSION : (Integer) value;
195            }
196            else {
197                unexpectedArgument(name, className);
198            }
199        }
200    
201        @Nullable
202        private AnnotationArrayArgumentVisitor unexpectedArgument(@Nullable Name name, @NotNull JvmClassName annotationClassName) {
203            if (isAbiVersionCompatible(version)) {
204                throw new IllegalStateException("Unexpected argument " + name + " for annotation " + annotationClassName);
205            }
206            return null;
207        }
208    }