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.AbiVersionUtil;
023 import org.jetbrains.kotlin.name.ClassId;
024 import org.jetbrains.kotlin.name.Name;
025 import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
026 import org.jetbrains.kotlin.serialization.deserialization.BinaryVersion;
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 Map<JvmClassName, KotlinClassHeader.Kind> HEADER_KINDS = new HashMap<JvmClassName, KotlinClassHeader.Kind>();
039 private static final Map<JvmClassName, KotlinClassHeader.Kind> OLD_DEPRECATED_ANNOTATIONS_KINDS = new HashMap<JvmClassName, KotlinClassHeader.Kind>();
040
041 static {
042 HEADER_KINDS.put(KotlinClass.CLASS_NAME, CLASS);
043 HEADER_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_PACKAGE), PACKAGE_FACADE);
044 HEADER_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_FILE_FACADE), FILE_FACADE);
045 HEADER_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_MULTIFILE_CLASS), MULTIFILE_CLASS);
046 HEADER_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_MULTIFILE_CLASS_PART), MULTIFILE_CLASS_PART);
047 HEADER_KINDS.put(KotlinSyntheticClass.CLASS_NAME, SYNTHETIC_CLASS);
048
049 initOldAnnotations();
050 }
051
052 @SuppressWarnings("deprecation")
053 private static void initOldAnnotations() {
054 OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_JET_CLASS_ANNOTATION), CLASS);
055 OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_JET_PACKAGE_CLASS_ANNOTATION),
056 KotlinClassHeader.Kind.PACKAGE_FACADE);
057 OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_KOTLIN_CLASS), CLASS);
058 OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_KOTLIN_PACKAGE), PACKAGE_FACADE);
059 OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_KOTLIN_PACKAGE_FRAGMENT), SYNTHETIC_CLASS);
060 OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_KOTLIN_TRAIT_IMPL), SYNTHETIC_CLASS);
061 }
062
063 private BinaryVersion version = AbiVersionUtil.INVALID_VERSION;
064 private String multifileClassName = null;
065 private String[] filePartClassNames = null;
066 private String[] annotationData = null;
067 private KotlinClassHeader.Kind headerKind = null;
068 private KotlinClass.Kind classKind = null;
069 private KotlinSyntheticClass.Kind syntheticClassKind = null;
070
071 @Nullable
072 public KotlinClassHeader createHeader() {
073 if (headerKind == null) {
074 return null;
075 }
076
077 if (headerKind == CLASS && classKind == null) {
078 // Default class kind is Kind.CLASS
079 classKind = KotlinClass.Kind.CLASS;
080 }
081
082 if (!AbiVersionUtil.isAbiVersionCompatible(version)) {
083 return new KotlinClassHeader(headerKind, version, null, classKind, syntheticClassKind, null, null);
084 }
085
086 if (shouldHaveData() && annotationData == null) {
087 // This means that the annotation is found and its ABI version is compatible, but there's no "data" string array in it.
088 // We tell the outside world that there's really no annotation at all
089 return null;
090 }
091
092 return new KotlinClassHeader(headerKind, version, annotationData, classKind, syntheticClassKind, filePartClassNames, multifileClassName);
093 }
094
095 private boolean shouldHaveData() {
096 return headerKind == CLASS ||
097 headerKind == PACKAGE_FACADE ||
098 headerKind == FILE_FACADE ||
099 headerKind == MULTIFILE_CLASS_PART;
100 }
101
102 @Nullable
103 @Override
104 public AnnotationArgumentVisitor visitAnnotation(@NotNull ClassId classId, @NotNull SourceElement source) {
105 if (headerKind != null) {
106 // Ignore all Kotlin annotations except the first found
107 return null;
108 }
109
110 JvmClassName annotation = JvmClassName.byClassId(classId);
111
112 KotlinClassHeader.Kind newKind = HEADER_KINDS.get(annotation);
113 if (newKind != null) {
114 headerKind = newKind;
115
116 switch (newKind) {
117 case CLASS:
118 return new ClassHeaderReader();
119 case PACKAGE_FACADE:
120 return new PackageHeaderReader();
121 case FILE_FACADE:
122 return new FileFacadeHeaderReader();
123 case MULTIFILE_CLASS:
124 return new MultifileClassHeaderReader();
125 case MULTIFILE_CLASS_PART:
126 return new MultifileClassPartHeaderReader();
127 case SYNTHETIC_CLASS:
128 return new SyntheticClassHeaderReader();
129 default:
130 return null;
131 }
132 }
133
134 KotlinClassHeader.Kind oldAnnotationKind = OLD_DEPRECATED_ANNOTATIONS_KINDS.get(annotation);
135 if (oldAnnotationKind != null) {
136 headerKind = oldAnnotationKind;
137 }
138
139 return null;
140 }
141
142 @Override
143 public void visitEnd() {
144 }
145
146 private abstract class HeaderAnnotationArgumentVisitor implements AnnotationArgumentVisitor {
147 protected final JvmClassName annotationClassName;
148
149 public HeaderAnnotationArgumentVisitor(@NotNull JvmClassName annotationClassName) {
150 this.annotationClassName = annotationClassName;
151 }
152
153 @Override
154 public void visit(@Nullable Name name, @Nullable Object value) {
155 if (name == null) return;
156
157 String string = name.asString();
158 if (VERSION_FIELD_NAME.equals(string)) {
159 version = value instanceof int[] ? BinaryVersion.create((int[]) value) : AbiVersionUtil.INVALID_VERSION;
160 }
161 else if (MULTIFILE_CLASS_NAME_FIELD_NAME.equals(string)) {
162 multifileClassName = value instanceof String ? (String) value : null;
163 }
164 else if (OLD_ABI_VERSION_FIELD_NAME.equals(string)) {
165 if (version == AbiVersionUtil.INVALID_VERSION && value instanceof Integer && (Integer) value > 0) {
166 version = BinaryVersion.create(0, (Integer) value, 0);
167 }
168 }
169 }
170
171 @Override
172 @Nullable
173 public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) {
174 if (name.asString().equals(DATA_FIELD_NAME)) {
175 return dataArrayVisitor();
176 }
177 else if (name.asString().equals(FILE_PART_CLASS_NAMES_FIELD_NAME)) {
178 return filePartClassNamesVisitor();
179 }
180 else {
181 return null;
182 }
183 }
184
185 @NotNull
186 private AnnotationArrayArgumentVisitor filePartClassNamesVisitor() {
187 return new CollectStringArrayAnnotationVisitor() {
188 @Override
189 protected void visitEnd(String[] data) {
190 filePartClassNames = data;
191 }
192 };
193 }
194
195 @Override
196 public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
197 }
198
199 @Nullable
200 @Override
201 public AnnotationArgumentVisitor visitAnnotation(@NotNull Name name, @NotNull ClassId classId) {
202 return null;
203 }
204
205 @NotNull
206 private AnnotationArrayArgumentVisitor dataArrayVisitor() {
207 return new CollectStringArrayAnnotationVisitor() {
208 @Override
209 protected void visitEnd(String[] data) {
210 annotationData = data;
211 }
212 };
213 }
214
215 @Override
216 public void visitEnd() {
217 }
218
219 private abstract class CollectStringArrayAnnotationVisitor implements AnnotationArrayArgumentVisitor {
220 private final List<String> strings;
221
222 public CollectStringArrayAnnotationVisitor() {
223 this.strings = new ArrayList<String>();
224 }
225
226 @Override
227 public void visit(@Nullable Object value) {
228 if (value instanceof String) {
229 strings.add((String) value);
230 }
231 }
232
233 @Override
234 public void visitEnum(@NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
235 }
236
237 @Override
238 public void visitEnd() {
239 //noinspection SSBasedInspection
240 visitEnd(strings.toArray(new String[strings.size()]));
241 }
242
243 protected abstract void visitEnd(String[] data);
244 }
245 }
246
247 private class ClassHeaderReader extends HeaderAnnotationArgumentVisitor {
248 public ClassHeaderReader() {
249 super(KotlinClass.CLASS_NAME);
250 }
251
252 @Override
253 public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
254 if (KotlinClass.KIND_CLASS_ID.equals(enumClassId) && KIND_FIELD_NAME.equals(name.asString())) {
255 classKind = valueOfOrNull(KotlinClass.Kind.class, enumEntryName.asString());
256 }
257 }
258 }
259
260 private class PackageHeaderReader extends HeaderAnnotationArgumentVisitor {
261 public PackageHeaderReader() {
262 super(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_PACKAGE));
263 }
264 }
265
266 private class FileFacadeHeaderReader extends HeaderAnnotationArgumentVisitor {
267 public FileFacadeHeaderReader() {
268 super(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_FILE_FACADE));
269 }
270 }
271
272 private class MultifileClassHeaderReader extends HeaderAnnotationArgumentVisitor {
273 public MultifileClassHeaderReader() {
274 super(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_MULTIFILE_CLASS));
275 }
276 }
277
278 private class MultifileClassPartHeaderReader extends HeaderAnnotationArgumentVisitor {
279 public MultifileClassPartHeaderReader() {
280 super(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_MULTIFILE_CLASS_PART));
281 }
282 }
283
284 private class SyntheticClassHeaderReader extends HeaderAnnotationArgumentVisitor {
285 public SyntheticClassHeaderReader() {
286 super(KotlinSyntheticClass.CLASS_NAME);
287 }
288
289 @Override
290 public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
291 if (KotlinSyntheticClass.KIND_CLASS_ID.equals(enumClassId) && KIND_FIELD_NAME.equals(name.asString())) {
292 syntheticClassKind = valueOfOrNull(KotlinSyntheticClass.Kind.class, enumEntryName.asString());
293 }
294 }
295 }
296
297 // This function is needed here because Enum.valueOf() throws exception if there's no such value,
298 // but we don't want to fail if we're loading the header with an _incompatible_ ABI version
299 @Nullable
300 private static <E extends Enum<E>> E valueOfOrNull(@NotNull Class<E> enumClass, @NotNull String entry) {
301 try {
302 return Enum.valueOf(enumClass, entry);
303 }
304 catch (IllegalArgumentException e) {
305 return null;
306 }
307 }
308 }