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.FqName;
025 import org.jetbrains.kotlin.name.Name;
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<ClassId, KotlinClassHeader.Kind> HEADER_KINDS = new HashMap<ClassId, KotlinClassHeader.Kind>();
039
040 static {
041 HEADER_KINDS.put(ClassId.topLevel(KOTLIN_CLASS), CLASS);
042 HEADER_KINDS.put(ClassId.topLevel(KOTLIN_FILE_FACADE), FILE_FACADE);
043 HEADER_KINDS.put(ClassId.topLevel(KOTLIN_MULTIFILE_CLASS), MULTIFILE_CLASS);
044 HEADER_KINDS.put(ClassId.topLevel(KOTLIN_MULTIFILE_CLASS_PART), MULTIFILE_CLASS_PART);
045 HEADER_KINDS.put(ClassId.topLevel(KOTLIN_SYNTHETIC_CLASS), SYNTHETIC_CLASS);
046 }
047
048 private BinaryVersion version = AbiVersionUtil.INVALID_VERSION;
049 private String multifileClassName = null;
050 private String[] filePartClassNames = null;
051 private String[] annotationData = null;
052 private String[] strings = null;
053 private KotlinClassHeader.Kind headerKind = null;
054 private String syntheticClassKind = null;
055 private boolean isInterfaceDefaultImpls = false;
056 private boolean isLocalClass = false;
057
058 @Nullable
059 public KotlinClassHeader createHeader() {
060 if (headerKind == null) {
061 return null;
062 }
063
064 if (!AbiVersionUtil.isAbiVersionCompatible(version)) {
065 annotationData = null;
066 }
067 else if (shouldHaveData() && annotationData == null) {
068 // This means that the annotation is found and its ABI version is compatible, but there's no "data" string array in it.
069 // We tell the outside world that there's really no annotation at all
070 return null;
071 }
072
073 return new KotlinClassHeader(
074 headerKind, version, annotationData, strings, syntheticClassKind, filePartClassNames, multifileClassName,
075 isInterfaceDefaultImpls, isLocalClass
076 );
077 }
078
079 private boolean shouldHaveData() {
080 return headerKind == CLASS ||
081 headerKind == FILE_FACADE ||
082 headerKind == MULTIFILE_CLASS_PART;
083 }
084
085 @Nullable
086 @Override
087 public AnnotationArgumentVisitor visitAnnotation(@NotNull ClassId classId, @NotNull SourceElement source) {
088 FqName fqName = classId.asSingleFqName();
089 if (KOTLIN_INTERFACE_DEFAULT_IMPLS.equals(fqName)) {
090 isInterfaceDefaultImpls = true;
091 return null;
092 }
093 else if (KOTLIN_LOCAL_CLASS.equals(fqName)) {
094 isLocalClass = true;
095 return null;
096 }
097
098 if (headerKind != null) {
099 // Ignore all Kotlin annotations except the first found
100 return null;
101 }
102
103 KotlinClassHeader.Kind newKind = HEADER_KINDS.get(classId);
104 if (newKind != null) {
105 headerKind = newKind;
106
107 switch (newKind) {
108 case CLASS:
109 case FILE_FACADE:
110 case MULTIFILE_CLASS:
111 case MULTIFILE_CLASS_PART:
112 return new HeaderAnnotationArgumentVisitor();
113 case SYNTHETIC_CLASS:
114 return new SyntheticClassHeaderReader();
115 default:
116 return null;
117 }
118 }
119
120 return null;
121 }
122
123 @Override
124 public void visitEnd() {
125 }
126
127 private class HeaderAnnotationArgumentVisitor implements AnnotationArgumentVisitor {
128 @Override
129 public void visit(@Nullable Name name, @Nullable Object value) {
130 if (name == null) return;
131
132 String string = name.asString();
133 if (VERSION_FIELD_NAME.equals(string)) {
134 version = value instanceof int[] ? BinaryVersion.create((int[]) value) : AbiVersionUtil.INVALID_VERSION;
135 }
136 else if (MULTIFILE_CLASS_NAME_FIELD_NAME.equals(string)) {
137 multifileClassName = value instanceof String ? (String) value : null;
138 }
139 else if (OLD_ABI_VERSION_FIELD_NAME.equals(string)) {
140 if (version == AbiVersionUtil.INVALID_VERSION && value instanceof Integer && (Integer) value > 0) {
141 version = BinaryVersion.create(0, (Integer) value, 0);
142 }
143 }
144 }
145
146 @Override
147 @Nullable
148 public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) {
149 String string = name.asString();
150 if (DATA_FIELD_NAME.equals(string)) {
151 return dataArrayVisitor();
152 }
153 else if (STRINGS_FIELD_NAME.equals(string)) {
154 return stringsArrayVisitor();
155 }
156 else if (FILE_PART_CLASS_NAMES_FIELD_NAME.equals(string)) {
157 return filePartClassNamesVisitor();
158 }
159 else {
160 return null;
161 }
162 }
163
164 @NotNull
165 private AnnotationArrayArgumentVisitor filePartClassNamesVisitor() {
166 return new CollectStringArrayAnnotationVisitor() {
167 @Override
168 protected void visitEnd(@NotNull String[] data) {
169 filePartClassNames = data;
170 }
171 };
172 }
173
174 @NotNull
175 private AnnotationArrayArgumentVisitor dataArrayVisitor() {
176 return new CollectStringArrayAnnotationVisitor() {
177 @Override
178 protected void visitEnd(@NotNull String[] data) {
179 annotationData = data;
180 }
181 };
182 }
183
184 @NotNull
185 private AnnotationArrayArgumentVisitor stringsArrayVisitor() {
186 return new CollectStringArrayAnnotationVisitor() {
187 @Override
188 protected void visitEnd(@NotNull String[] data) {
189 strings = data;
190 }
191 };
192 }
193
194 @Override
195 public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
196 }
197
198 @Nullable
199 @Override
200 public AnnotationArgumentVisitor visitAnnotation(@NotNull Name name, @NotNull ClassId classId) {
201 return null;
202 }
203
204 @Override
205 public void visitEnd() {
206 }
207
208 private abstract class CollectStringArrayAnnotationVisitor implements AnnotationArrayArgumentVisitor {
209 private final List<String> strings;
210
211 public CollectStringArrayAnnotationVisitor() {
212 this.strings = new ArrayList<String>();
213 }
214
215 @Override
216 public void visit(@Nullable Object value) {
217 if (value instanceof String) {
218 strings.add((String) value);
219 }
220 }
221
222 @Override
223 public void visitEnum(@NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
224 }
225
226 @Override
227 public void visitEnd() {
228 //noinspection SSBasedInspection
229 visitEnd(strings.toArray(new String[strings.size()]));
230 }
231
232 protected abstract void visitEnd(@NotNull String[] data);
233 }
234 }
235
236 private class SyntheticClassHeaderReader extends HeaderAnnotationArgumentVisitor {
237 @Override
238 public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
239 if ("Kind".equals(enumClassId.getShortClassName().asString()) &&
240 enumClassId.isNestedClass() &&
241 enumClassId.getOuterClassId().equals(ClassId.topLevel(KOTLIN_SYNTHETIC_CLASS)) &&
242 "kind".equals(name.asString())) {
243 syntheticClassKind = enumEntryName.asString();
244 }
245 }
246 }
247 }