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