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