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 }