|
1 /* |
|
2 * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package jdk.jfr.internal; |
|
27 |
|
28 import java.io.BufferedInputStream; |
|
29 import java.io.IOException; |
|
30 import java.io.InputStream; |
|
31 import java.lang.annotation.Annotation; |
|
32 import java.util.ArrayList; |
|
33 import java.util.Collections; |
|
34 import java.util.HashMap; |
|
35 import java.util.LinkedHashMap; |
|
36 import java.util.List; |
|
37 import java.util.Map; |
|
38 import java.util.Objects; |
|
39 |
|
40 import jdk.internal.org.xml.sax.Attributes; |
|
41 import jdk.internal.org.xml.sax.EntityResolver; |
|
42 import jdk.internal.org.xml.sax.SAXException; |
|
43 import jdk.internal.org.xml.sax.helpers.DefaultHandler; |
|
44 import jdk.internal.util.xml.SAXParser; |
|
45 import jdk.internal.util.xml.impl.SAXParserImpl; |
|
46 import jdk.jfr.AnnotationElement; |
|
47 import jdk.jfr.Category; |
|
48 import jdk.jfr.Description; |
|
49 import jdk.jfr.Enabled; |
|
50 import jdk.jfr.Experimental; |
|
51 import jdk.jfr.Label; |
|
52 import jdk.jfr.Period; |
|
53 import jdk.jfr.Relational; |
|
54 import jdk.jfr.StackTrace; |
|
55 import jdk.jfr.Threshold; |
|
56 import jdk.jfr.TransitionFrom; |
|
57 import jdk.jfr.TransitionTo; |
|
58 import jdk.jfr.Unsigned; |
|
59 |
|
60 final class MetadataHandler extends DefaultHandler implements EntityResolver { |
|
61 |
|
62 static class TypeElement { |
|
63 List<FieldElement> fields = new ArrayList<>(); |
|
64 String name; |
|
65 String label; |
|
66 String description; |
|
67 String category; |
|
68 String superType; |
|
69 String period; |
|
70 boolean thread; |
|
71 boolean startTime; |
|
72 boolean stackTrace; |
|
73 boolean cutoff; |
|
74 boolean isEvent; |
|
75 boolean experimental; |
|
76 boolean valueType; |
|
77 } |
|
78 |
|
79 static class FieldElement { |
|
80 TypeElement referenceType; |
|
81 String name; |
|
82 String label; |
|
83 String description; |
|
84 String contentType; |
|
85 String typeName; |
|
86 String transition; |
|
87 String relation; |
|
88 boolean struct; |
|
89 boolean array; |
|
90 boolean experimental; |
|
91 boolean unsigned; |
|
92 } |
|
93 |
|
94 static class XmlType { |
|
95 String name; |
|
96 String javaType; |
|
97 String contentType; |
|
98 boolean unsigned; |
|
99 } |
|
100 |
|
101 final Map<String, TypeElement> types = new LinkedHashMap<>(200); |
|
102 final Map<String, XmlType> xmlTypes = new HashMap<>(20); |
|
103 final Map<String, AnnotationElement> xmlContentTypes = new HashMap<>(20); |
|
104 final List<String> relations = new ArrayList<>(); |
|
105 long eventTypeId = 255; |
|
106 long structTypeId = 33; |
|
107 FieldElement currentField; |
|
108 TypeElement currentType; |
|
109 |
|
110 @Override |
|
111 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { |
|
112 switch (qName) { |
|
113 case "XmlType": |
|
114 XmlType xmlType = new XmlType(); |
|
115 xmlType.name = attributes.getValue("name"); |
|
116 xmlType.javaType = attributes.getValue("javaType"); |
|
117 xmlType.contentType = attributes.getValue("contentType"); |
|
118 xmlType.unsigned = Boolean.valueOf(attributes.getValue("unsigned")); |
|
119 xmlTypes.put(xmlType.name, xmlType); |
|
120 break; |
|
121 case "Type": |
|
122 case "Event": |
|
123 currentType = new TypeElement(); |
|
124 currentType.name = attributes.getValue("name"); |
|
125 currentType.label = attributes.getValue("label"); |
|
126 currentType.description = attributes.getValue("description"); |
|
127 currentType.category = attributes.getValue("category"); |
|
128 currentType.thread = getBoolean(attributes, "thread", false); |
|
129 currentType.stackTrace = getBoolean(attributes, "stackTrace", false); |
|
130 currentType.startTime = getBoolean(attributes, "startTime", true); |
|
131 currentType.period = attributes.getValue("period"); |
|
132 currentType.cutoff = getBoolean(attributes, "cutoff", false); |
|
133 currentType.experimental = getBoolean(attributes, "experimental", false); |
|
134 currentType.isEvent = qName.equals("Event"); |
|
135 break; |
|
136 case "Field": |
|
137 currentField = new FieldElement(); |
|
138 currentField.struct = getBoolean(attributes, "struct", false); |
|
139 currentField.array = getBoolean(attributes, "array", false); |
|
140 currentField.name = attributes.getValue("name"); |
|
141 currentField.label = attributes.getValue("label"); |
|
142 currentField.typeName = attributes.getValue("type"); |
|
143 currentField.description = attributes.getValue("description"); |
|
144 currentField.experimental = getBoolean(attributes, "experimental", false); |
|
145 currentField.contentType = attributes.getValue("contentType"); |
|
146 currentField.relation = attributes.getValue("relation"); |
|
147 currentField.transition = attributes.getValue("transition"); |
|
148 break; |
|
149 case "XmlContentType": |
|
150 String name = attributes.getValue("name"); |
|
151 String type = attributes.getValue("annotationType"); |
|
152 String value = attributes.getValue("annotationValue"); |
|
153 Class<? extends Annotation> annotationType = createAnnotationClass(type); |
|
154 AnnotationElement ae = value == null ? new AnnotationElement(annotationType) : new AnnotationElement(annotationType, value); |
|
155 xmlContentTypes.put(name, ae); |
|
156 break; |
|
157 case "Relation": |
|
158 String n = attributes.getValue("name"); |
|
159 relations.add(n); |
|
160 break; |
|
161 } |
|
162 } |
|
163 |
|
164 @SuppressWarnings("unchecked") |
|
165 private Class<? extends Annotation> createAnnotationClass(String type) { |
|
166 try { |
|
167 if (!type.startsWith("jdk.jfr.")) { |
|
168 throw new IllegalStateException("Incorrect type " + type + ". Annotation class must be located in jdk.jfr package."); |
|
169 } |
|
170 Class<?> c = Class.forName(type, true, null); |
|
171 return (Class<? extends Annotation>) c; |
|
172 } catch (ClassNotFoundException cne) { |
|
173 throw new IllegalStateException(cne); |
|
174 } |
|
175 } |
|
176 |
|
177 private boolean getBoolean(Attributes attributes, String name, boolean defaultValue) { |
|
178 String value = attributes.getValue(name); |
|
179 return value == null ? defaultValue : Boolean.valueOf(value); |
|
180 } |
|
181 |
|
182 @Override |
|
183 public void endElement(String uri, String localName, String qName) { |
|
184 switch (qName) { |
|
185 case "Type": |
|
186 case "Event": |
|
187 types.put(currentType.name, currentType); |
|
188 currentType = null; |
|
189 break; |
|
190 case "Field": |
|
191 currentType.fields.add(currentField); |
|
192 currentField = null; |
|
193 break; |
|
194 } |
|
195 } |
|
196 |
|
197 public static List<Type> createTypes() throws IOException { |
|
198 SAXParser parser = new SAXParserImpl(); |
|
199 MetadataHandler t = new MetadataHandler(); |
|
200 try (InputStream is = new BufferedInputStream(SecuritySupport.getResourceAsStream("/jdk/jfr/internal/types/metadata.xml"))) { |
|
201 Logger.log(LogTag.JFR_SYSTEM, LogLevel.DEBUG, () -> "Parsing metadata.xml"); |
|
202 try { |
|
203 parser.parse(is, t); |
|
204 return t.buildTypes(); |
|
205 } catch (Exception e) { |
|
206 e.printStackTrace(); |
|
207 throw new IOException(e); |
|
208 } |
|
209 } |
|
210 } |
|
211 |
|
212 private List<Type> buildTypes() { |
|
213 removeXMLConvenience(); |
|
214 Map<String, Type> typeMap = buildTypeMap(); |
|
215 Map<String, AnnotationElement> relationMap = buildRelationMap(typeMap); |
|
216 addFields(typeMap, relationMap); |
|
217 return trimTypes(typeMap); |
|
218 } |
|
219 |
|
220 private Map<String, AnnotationElement> buildRelationMap(Map<String, Type> typeMap) { |
|
221 Map<String, AnnotationElement> relationMap = new HashMap<>(); |
|
222 for (String relation : relations) { |
|
223 Type relationType = new Type(Type.TYPES_PREFIX + relation, Type.SUPER_TYPE_ANNOTATION, eventTypeId++); |
|
224 relationType.setAnnotations(Collections.singletonList(new AnnotationElement(Relational.class))); |
|
225 AnnotationElement ae = PrivateAccess.getInstance().newAnnotation(relationType, Collections.emptyList(), true); |
|
226 relationMap.put(relation, ae); |
|
227 typeMap.put(relationType.getName(), relationType); |
|
228 } |
|
229 return relationMap; |
|
230 } |
|
231 |
|
232 private List<Type> trimTypes(Map<String, Type> lookup) { |
|
233 List<Type> trimmedTypes = new ArrayList<>(lookup.size()); |
|
234 for (Type t : lookup.values()) { |
|
235 t.trimFields(); |
|
236 trimmedTypes.add(t); |
|
237 } |
|
238 return trimmedTypes; |
|
239 } |
|
240 |
|
241 private void addFields(Map<String, Type> lookup, Map<String, AnnotationElement> relationMap) { |
|
242 for (TypeElement te : types.values()) { |
|
243 Type type = lookup.get(te.name); |
|
244 if (te.isEvent) { |
|
245 boolean periodic = te.period!= null; |
|
246 TypeLibrary.addImplicitFields(type, periodic, te.startTime && !periodic, te.thread, te.stackTrace && !periodic, te.cutoff); |
|
247 } |
|
248 for (FieldElement f : te.fields) { |
|
249 Type fieldType = Type.getKnownType(f.typeName); |
|
250 if (fieldType == null) { |
|
251 fieldType = Objects.requireNonNull(lookup.get(f.referenceType.name)); |
|
252 } |
|
253 List<AnnotationElement> aes = new ArrayList<>(); |
|
254 if (f.unsigned) { |
|
255 aes.add(new AnnotationElement(Unsigned.class)); |
|
256 } |
|
257 if (f.contentType != null) { |
|
258 aes.add(Objects.requireNonNull(xmlContentTypes.get(f.contentType))); |
|
259 } |
|
260 if (f.relation != null) { |
|
261 aes.add(Objects.requireNonNull(relationMap.get(f.relation))); |
|
262 } |
|
263 if (f.label != null) { |
|
264 aes.add(new AnnotationElement(Label.class, f.label)); |
|
265 } |
|
266 if (f.experimental) { |
|
267 aes.add(new AnnotationElement(Experimental.class)); |
|
268 } |
|
269 if (f.description != null) { |
|
270 aes.add(new AnnotationElement(Description.class, f.description)); |
|
271 } |
|
272 if ("from".equals(f.transition)) { |
|
273 aes.add(new AnnotationElement(TransitionFrom.class)); |
|
274 } |
|
275 if ("to".equals(f.transition)) { |
|
276 aes.add(new AnnotationElement(TransitionTo.class)); |
|
277 } |
|
278 boolean constantPool = !f.struct && f.referenceType != null; |
|
279 type.add(PrivateAccess.getInstance().newValueDescriptor(f.name, fieldType, aes, f.array ? 1 : 0, constantPool, null)); |
|
280 } |
|
281 } |
|
282 } |
|
283 |
|
284 private Map<String, Type> buildTypeMap() { |
|
285 Map<String, Type> typeMap = new HashMap<>(); |
|
286 for (Type type : Type.getKnownTypes()) { |
|
287 typeMap.put(type.getName(), type); |
|
288 } |
|
289 |
|
290 for (TypeElement t : types.values()) { |
|
291 List<AnnotationElement> aes = new ArrayList<>(); |
|
292 if (t.category != null) { |
|
293 aes.add(new AnnotationElement(Category.class, buildCategoryArray(t.category))); |
|
294 } |
|
295 if (t.label != null) { |
|
296 aes.add(new AnnotationElement(Label.class, t.label)); |
|
297 } |
|
298 if (t.description != null) { |
|
299 aes.add(new AnnotationElement(Description.class, t.description)); |
|
300 } |
|
301 if (t.isEvent) { |
|
302 if (t.period != null) { |
|
303 aes.add(new AnnotationElement(Period.class, t.period)); |
|
304 } else { |
|
305 if (t.startTime) { |
|
306 aes.add(new AnnotationElement(Threshold.class, "0 ns")); |
|
307 } |
|
308 if (t.stackTrace) { |
|
309 aes.add(new AnnotationElement(StackTrace.class, true)); |
|
310 } |
|
311 } |
|
312 if (t.cutoff) { |
|
313 aes.add(new AnnotationElement(Cutoff.class, Cutoff.INIFITY)); |
|
314 } |
|
315 } |
|
316 if (t.experimental) { |
|
317 aes.add(new AnnotationElement(Experimental.class)); |
|
318 } |
|
319 Type type; |
|
320 if (t.isEvent) { |
|
321 aes.add(new AnnotationElement(Enabled.class, false)); |
|
322 type = new PlatformEventType(t.name, eventTypeId++, false, true); |
|
323 } else { |
|
324 // Struct types had their own XML-element in the past. To have id assigned in the |
|
325 // same order as generated .hpp file do some tweaks here. |
|
326 boolean valueType = t.name.endsWith("StackFrame") || t.valueType; |
|
327 type = new Type(t.name, null, valueType ? eventTypeId++ : nextTypeId(t.name), false); |
|
328 } |
|
329 type.setAnnotations(aes); |
|
330 typeMap.put(t.name, type); |
|
331 } |
|
332 return typeMap; |
|
333 } |
|
334 |
|
335 private long nextTypeId(String name) { |
|
336 if (Type.THREAD.getName().equals(name)) { |
|
337 return Type.THREAD.getId(); |
|
338 } |
|
339 if (Type.STRING.getName().equals(name)) { |
|
340 return Type.STRING.getId(); |
|
341 } |
|
342 if (Type.CLASS.getName().equals(name)) { |
|
343 return Type.CLASS.getId(); |
|
344 } |
|
345 for (Type type : Type.getKnownTypes()) { |
|
346 if (type.getName().equals(name)) { |
|
347 return type.getId(); |
|
348 } |
|
349 } |
|
350 return structTypeId++; |
|
351 } |
|
352 |
|
353 private String[] buildCategoryArray(String category) { |
|
354 List<String> categories = new ArrayList<>(); |
|
355 StringBuilder sb = new StringBuilder(); |
|
356 for (char c : category.toCharArray()) { |
|
357 if (c == ',') { |
|
358 categories.add(sb.toString().trim()); |
|
359 sb.setLength(0); |
|
360 } else { |
|
361 sb.append(c); |
|
362 } |
|
363 } |
|
364 categories.add(sb.toString().trim()); |
|
365 return categories.toArray(new String[0]); |
|
366 } |
|
367 |
|
368 private void removeXMLConvenience() { |
|
369 for (TypeElement t : types.values()) { |
|
370 XmlType xmlType = xmlTypes.get(t.name); |
|
371 if (xmlType != null && xmlType.javaType != null) { |
|
372 t.name = xmlType.javaType; // known type, i.e primitive |
|
373 } else { |
|
374 if (t.isEvent) { |
|
375 t.name = Type.EVENT_NAME_PREFIX + t.name; |
|
376 } else { |
|
377 t.name = Type.TYPES_PREFIX + t.name; |
|
378 } |
|
379 } |
|
380 } |
|
381 |
|
382 for (TypeElement t : types.values()) { |
|
383 for (FieldElement f : t.fields) { |
|
384 f.referenceType = types.get(f.typeName); |
|
385 XmlType xmlType = xmlTypes.get(f.typeName); |
|
386 if (xmlType != null) { |
|
387 if (xmlType.javaType != null) { |
|
388 f.typeName = xmlType.javaType; |
|
389 } |
|
390 if (xmlType.contentType != null) { |
|
391 f.contentType = xmlType.contentType; |
|
392 } |
|
393 if (xmlType.unsigned) { |
|
394 f.unsigned = true; |
|
395 } |
|
396 } |
|
397 if (f.struct && f.referenceType != null) { |
|
398 f.referenceType.valueType = true; |
|
399 } |
|
400 } |
|
401 } |
|
402 } |
|
403 } |