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.JvmBytecodeBinaryVersion;
023 import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion;
024 import org.jetbrains.kotlin.name.ClassId;
025 import org.jetbrains.kotlin.name.FqName;
026 import org.jetbrains.kotlin.name.Name;
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 boolean IGNORE_OLD_METADATA = "true".equals(System.getProperty("kotlin.ignore.old.metadata"));
039
040 private static final Map<ClassId, KotlinClassHeader.Kind> HEADER_KINDS = new HashMap<ClassId, KotlinClassHeader.Kind>();
041
042 static {
043 // TODO: delete this at some point
044 HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinClass")), CLASS);
045 HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinFileFacade")), FILE_FACADE);
046 HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinMultifileClass")), MULTIFILE_CLASS);
047 HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinMultifileClassPart")), MULTIFILE_CLASS_PART);
048 HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinSyntheticClass")), SYNTHETIC_CLASS);
049 }
050
051 private JvmMetadataVersion metadataVersion = null;
052 private JvmBytecodeBinaryVersion bytecodeVersion = null;
053 private String extraString = null;
054 private int extraInt = 0;
055 private String[] data = null;
056 private String[] strings = null;
057 private KotlinClassHeader.Kind headerKind = null;
058
059 @Nullable
060 public KotlinClassHeader createHeader() {
061 if (headerKind == null) {
062 return null;
063 }
064
065 if (metadataVersion == null || !metadataVersion.isCompatible()) {
066 data = null;
067 }
068 else if (shouldHaveData() && data == null) {
069 // This means that the annotation is found and its ABI version is compatible, but there's no "data" string array in it.
070 // We tell the outside world that there's really no annotation at all
071 return null;
072 }
073
074 return new KotlinClassHeader(
075 headerKind,
076 metadataVersion != null ? metadataVersion : JvmMetadataVersion.INVALID_VERSION,
077 bytecodeVersion != null ? bytecodeVersion : JvmBytecodeBinaryVersion.INVALID_VERSION,
078 data,
079 strings,
080 extraString,
081 extraInt
082 );
083 }
084
085 private boolean shouldHaveData() {
086 return headerKind == CLASS ||
087 headerKind == FILE_FACADE ||
088 headerKind == MULTIFILE_CLASS_PART;
089 }
090
091 @Nullable
092 @Override
093 public AnnotationArgumentVisitor visitAnnotation(@NotNull ClassId classId, @NotNull SourceElement source) {
094 FqName fqName = classId.asSingleFqName();
095 if (fqName.equals(METADATA_FQ_NAME)) {
096 return new KotlinMetadataArgumentVisitor();
097 }
098
099 if (IGNORE_OLD_METADATA) return null;
100
101 if (headerKind != null) {
102 // Ignore all Kotlin annotations except the first found
103 return null;
104 }
105
106 KotlinClassHeader.Kind newKind = HEADER_KINDS.get(classId);
107 if (newKind != null) {
108 headerKind = newKind;
109 return new OldDeprecatedAnnotationArgumentVisitor();
110 }
111
112 return null;
113 }
114
115 @Override
116 public void visitEnd() {
117 }
118
119 private class KotlinMetadataArgumentVisitor implements AnnotationArgumentVisitor {
120 @Override
121 public void visit(@Nullable Name name, @Nullable Object value) {
122 if (name == null) return;
123
124 String string = name.asString();
125 if (KIND_FIELD_NAME.equals(string)) {
126 if (value instanceof Integer) {
127 headerKind = KotlinClassHeader.Kind.getById((Integer) value);
128 }
129 }
130 else if (METADATA_VERSION_FIELD_NAME.equals(string)) {
131 if (value instanceof int[]) {
132 metadataVersion = new JvmMetadataVersion((int[]) value);
133 }
134 }
135 else if (BYTECODE_VERSION_FIELD_NAME.equals(string)) {
136 if (value instanceof int[]) {
137 bytecodeVersion = new JvmBytecodeBinaryVersion((int[]) value);
138 }
139 }
140 else if (METADATA_EXTRA_STRING_FIELD_NAME.equals(string)) {
141 if (value instanceof String) {
142 extraString = (String) value;
143 }
144 }
145 else if (METADATA_EXTRA_INT_FIELD_NAME.equals(string)) {
146 if (value instanceof Integer) {
147 extraInt = (Integer) value;
148 }
149 }
150 }
151
152 @Override
153 @Nullable
154 public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) {
155 String string = name.asString();
156 if (METADATA_DATA_FIELD_NAME.equals(string)) {
157 return dataArrayVisitor();
158 }
159 else if (METADATA_STRINGS_FIELD_NAME.equals(string)) {
160 return stringsArrayVisitor();
161 }
162 else {
163 return null;
164 }
165 }
166
167 @NotNull
168 private AnnotationArrayArgumentVisitor dataArrayVisitor() {
169 return new CollectStringArrayAnnotationVisitor() {
170 @Override
171 protected void visitEnd(@NotNull String[] result) {
172 data = result;
173 }
174 };
175 }
176
177 @NotNull
178 private AnnotationArrayArgumentVisitor stringsArrayVisitor() {
179 return new CollectStringArrayAnnotationVisitor() {
180 @Override
181 protected void visitEnd(@NotNull String[] result) {
182 strings = result;
183 }
184 };
185 }
186
187 @Override
188 public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
189 }
190
191 @Nullable
192 @Override
193 public AnnotationArgumentVisitor visitAnnotation(@NotNull Name name, @NotNull ClassId classId) {
194 return null;
195 }
196
197 @Override
198 public void visitEnd() {
199 }
200 }
201
202 private class OldDeprecatedAnnotationArgumentVisitor implements AnnotationArgumentVisitor {
203 @Override
204 public void visit(@Nullable Name name, @Nullable Object value) {
205 if (name == null) return;
206
207 String string = name.asString();
208 if ("version".equals(string)) {
209 if (value instanceof int[]) {
210 metadataVersion = new JvmMetadataVersion((int[]) value);
211
212 // If there's no bytecode binary version in the class file, we assume it to be equal to the metadata version
213 if (bytecodeVersion == null) {
214 bytecodeVersion = new JvmBytecodeBinaryVersion((int[]) value);
215 }
216 }
217 }
218 else if ("multifileClassName".equals(string)) {
219 extraString = value instanceof String ? (String) value : null;
220 }
221 }
222
223 @Override
224 @Nullable
225 public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) {
226 String string = name.asString();
227 if ("data".equals(string) || "filePartClassNames".equals(string)) {
228 return dataArrayVisitor();
229 }
230 else if ("strings".equals(string)) {
231 return stringsArrayVisitor();
232 }
233 else {
234 return null;
235 }
236 }
237
238 @NotNull
239 private AnnotationArrayArgumentVisitor dataArrayVisitor() {
240 return new CollectStringArrayAnnotationVisitor() {
241 @Override
242 protected void visitEnd(@NotNull String[] data) {
243 ReadKotlinClassHeaderAnnotationVisitor.this.data = data;
244 }
245 };
246 }
247
248 @NotNull
249 private AnnotationArrayArgumentVisitor stringsArrayVisitor() {
250 return new CollectStringArrayAnnotationVisitor() {
251 @Override
252 protected void visitEnd(@NotNull String[] data) {
253 strings = data;
254 }
255 };
256 }
257
258 @Override
259 public void visitEnum(@NotNull Name name, @NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
260 }
261
262 @Nullable
263 @Override
264 public AnnotationArgumentVisitor visitAnnotation(@NotNull Name name, @NotNull ClassId classId) {
265 return null;
266 }
267
268 @Override
269 public void visitEnd() {
270 }
271 }
272
273 private abstract static class CollectStringArrayAnnotationVisitor implements AnnotationArrayArgumentVisitor {
274 private final List<String> strings;
275
276 public CollectStringArrayAnnotationVisitor() {
277 this.strings = new ArrayList<String>();
278 }
279
280 @Override
281 public void visit(@Nullable Object value) {
282 if (value instanceof String) {
283 strings.add((String) value);
284 }
285 }
286
287 @Override
288 public void visitEnum(@NotNull ClassId enumClassId, @NotNull Name enumEntryName) {
289 }
290
291 @Override
292 public void visitEnd() {
293 //noinspection SSBasedInspection
294 visitEnd(strings.toArray(new String[strings.size()]));
295 }
296
297 protected abstract void visitEnd(@NotNull String[] data);
298 }
299 }