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