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.load.java.AbiVersionUtil;
022 import org.jetbrains.kotlin.name.ClassId;
023 import org.jetbrains.kotlin.name.Name;
024 import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
025
026 import java.util.ArrayList;
027 import java.util.HashMap;
028 import java.util.List;
029 import java.util.Map;
030
031 import static org.jetbrains.kotlin.load.java.AbiVersionUtil.isAbiVersionCompatible;
032 import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.*;
033 import static org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass.*;
034 import static org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind.*;
035
036 public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor {
037 private static final Map<JvmClassName, KotlinClassHeader.Kind> HEADER_KINDS = new HashMap<JvmClassName, KotlinClassHeader.Kind>();
038 private static final Map<JvmClassName, KotlinClassHeader.Kind> OLD_DEPRECATED_ANNOTATIONS_KINDS = new HashMap<JvmClassName, KotlinClassHeader.Kind>();
039
040 private int version = AbiVersionUtil.INVALID_VERSION;
041 static {
042 HEADER_KINDS.put(KotlinClass.CLASS_NAME, CLASS);
043 HEADER_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_PACKAGE), PACKAGE_FACADE);
044 HEADER_KINDS.put(KotlinSyntheticClass.CLASS_NAME, SYNTHETIC_CLASS);
045
046 initOldAnnotations();
047 }
048
049 @SuppressWarnings("deprecation")
050 private static void initOldAnnotations() {
051 OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_JET_CLASS_ANNOTATION), CLASS);
052 OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_JET_PACKAGE_CLASS_ANNOTATION),
053 KotlinClassHeader.Kind.PACKAGE_FACADE);
054 OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_KOTLIN_CLASS), CLASS);
055 OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_KOTLIN_PACKAGE), PACKAGE_FACADE);
056 OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_KOTLIN_PACKAGE_FRAGMENT), SYNTHETIC_CLASS);
057 OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_KOTLIN_TRAIT_IMPL), SYNTHETIC_CLASS);
058 }
059
060 private String[] annotationData = null;
061 private KotlinClassHeader.Kind headerKind = null;
062 private KotlinClass.Kind classKind = null;
063 private KotlinSyntheticClass.Kind syntheticClassKind = null;
064
065 @Nullable
066 public KotlinClassHeader createHeader() {
067 if (headerKind == null) {
068 return null;
069 }
070
071 if (!AbiVersionUtil.isAbiVersionCompatible(version)) {
072 return new KotlinClassHeader(headerKind, version, null, classKind, syntheticClassKind);
073 }
074
075 if ((headerKind == CLASS || headerKind == PACKAGE_FACADE) && annotationData == null) {
076 // This means that the annotation is found and its ABI version is compatible, but there's no "data" string array in it.
077 // We tell the outside world that there's really no annotation at all
078 return null;
079 }
080
081 return new KotlinClassHeader(headerKind, version, annotationData, classKind, syntheticClassKind);
082 }
083
084 @Nullable
085 @Override
086 public AnnotationArgumentVisitor visitAnnotation(@NotNull ClassId classId) {
087 if (headerKind != null) {
088 // Ignore all Kotlin annotations except the first found
089 return null;
090 }
091
092 JvmClassName annotation = JvmClassName.byClassId(classId);
093
094 KotlinClassHeader.Kind newKind = HEADER_KINDS.get(annotation);
095 if (newKind != null) {
096 headerKind = newKind;
097
098 switch (newKind) {
099 case CLASS:
100 return new ClassHeaderReader();
101 case PACKAGE_FACADE:
102 return new PackageHeaderReader();
103 case SYNTHETIC_CLASS:
104 return new SyntheticClassHeaderReader();
105 default:
106 throw new IllegalStateException("Unknown kind: " + newKind);
107 }
108 }
109
110 KotlinClassHeader.Kind oldAnnotationKind = OLD_DEPRECATED_ANNOTATIONS_KINDS.get(annotation);
111 if (oldAnnotationKind != null) {
112 headerKind = oldAnnotationKind;
113 }
114
115 return null;
116 }
117
118 @Override
119 public void visitEnd() {
120 }
121
122 private abstract class HeaderAnnotationArgumentVisitor implements AnnotationArgumentVisitor {
123 protected final JvmClassName annotationClassName;
124
125 public HeaderAnnotationArgumentVisitor(@NotNull JvmClassName annotationClassName) {
126 this.annotationClassName = annotationClassName;
127 }
128
129 @Override
130 public void visit(@Nullable Name name, @Nullable Object value) {
131 if (name != null && name.asString().equals(ABI_VERSION_FIELD_NAME)) {
132 version = value == null ? AbiVersionUtil.INVALID_VERSION : (Integer) value;
133 }
134 else {
135 unexpectedArgument(name);
136 }
137 }
138
139 @Override
140 @Nullable
141 public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) {
142 if (name.asString().equals(DATA_FIELD_NAME)) {
143 return stringArrayVisitor();
144 }
145 else if (isAbiVersionCompatible(version)) {
146 throw new IllegalStateException("Unexpected array argument " + name + " for annotation " + annotationClassName);
147 }
148
149 return null;
150 }
151
152 @Nullable
153 @Override
154 public AnnotationArgumentVisitor visitAnnotation(@NotNull Name name, @NotNull ClassId classId) {
155 return null;
156 }
157
158 @NotNull
159 private AnnotationArrayArgumentVisitor stringArrayVisitor() {
160 final List<String> strings = new ArrayList<String>(1);
161 return new AnnotationArrayArgumentVisitor() {
162 @Override
163 public void visit(@Nullable Object value) {
164 if (!(value instanceof String)) {
165 throw new IllegalStateException("Unexpected argument value: " + value);
166 }
167
168 strings.add((String) value);
169 }
170
171 @Override
172 public void visitEnum(@NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
173 unexpectedArgument(null);
174 }
175
176 @Override
177 public void visitEnd() {
178 //noinspection SSBasedInspection
179 annotationData = strings.toArray(new String[strings.size()]);
180 }
181 };
182 }
183
184 @Nullable
185 protected AnnotationArrayArgumentVisitor unexpectedArgument(@Nullable Name name) {
186 if (isAbiVersionCompatible(version)) {
187 throw new IllegalStateException("Unexpected argument " + name + " for annotation " + annotationClassName);
188 }
189 return null;
190 }
191
192 protected void unexpectedEnumArgument(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
193 if (isAbiVersionCompatible(version)) {
194 throw new IllegalStateException("Unexpected enum entry for class annotation " + annotationClassName + ": " +
195 name + "=" + enumClassId + "." + enumEntryName);
196 }
197 }
198
199 @Override
200 public void visitEnd() {
201 }
202 }
203
204 private class ClassHeaderReader extends HeaderAnnotationArgumentVisitor {
205 public ClassHeaderReader() {
206 super(KotlinClass.CLASS_NAME);
207 }
208
209 @Override
210 public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
211 if (KotlinClass.KIND_CLASS_ID.equals(enumClassId) && KIND_FIELD_NAME.equals(name.asString())) {
212 classKind = valueOfOrNull(KotlinClass.Kind.class, enumEntryName.asString());
213 if (classKind != null) return;
214 }
215 unexpectedEnumArgument(name, enumClassId, enumEntryName);
216 }
217 }
218
219 private class PackageHeaderReader extends HeaderAnnotationArgumentVisitor {
220 public PackageHeaderReader() {
221 super(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_PACKAGE));
222 }
223
224 @Override
225 public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
226 unexpectedEnumArgument(name, enumClassId, enumEntryName);
227 }
228 }
229
230 private class SyntheticClassHeaderReader extends HeaderAnnotationArgumentVisitor {
231 public SyntheticClassHeaderReader() {
232 super(KotlinSyntheticClass.CLASS_NAME);
233 }
234
235 @Override
236 public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
237 if (KotlinSyntheticClass.KIND_CLASS_ID.equals(enumClassId) && KIND_FIELD_NAME.equals(name.asString())) {
238 syntheticClassKind = valueOfOrNull(KotlinSyntheticClass.Kind.class, enumEntryName.asString());
239 if (syntheticClassKind != null) return;
240 }
241 unexpectedEnumArgument(name, enumClassId, enumEntryName);
242 }
243 }
244
245 // This function is needed here because Enum.valueOf() throws exception if there's no such value,
246 // but we don't want to fail if we're loading the header with an _incompatible_ ABI version
247 @Nullable
248 private static <E extends Enum<E>> E valueOfOrNull(@NotNull Class<E> enumClass, @NotNull String entry) {
249 try {
250 return Enum.valueOf(enumClass, entry);
251 }
252 catch (IllegalArgumentException e) {
253 return null;
254 }
255 }
256 }