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