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, List<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 annotation = attributes.getValue("annotation");
 152             xmlContentTypes.put(name, createAnnotationElements(annotation));
 153             break;
 154         case "Relation":
 155             String n = attributes.getValue("name");
 156             relations.add(n);
 157             break;
 158         }
 159     }
 160 
 161     private List<AnnotationElement> createAnnotationElements(String annotation) throws InternalError {
 162         String[] annotations = annotation.split(",");
 163         List<AnnotationElement> annotationElements = new ArrayList<>();
 164         for (String a : annotations) {
 165             a = a.trim();
 166             int leftParenthesis = a.indexOf("(");
 167             if (leftParenthesis == -1) {
 168                 annotationElements.add(new AnnotationElement(createAnnotationClass(a)));
 169             } else {
 170                 int rightParenthesis = a.lastIndexOf(")");
 171                 if (rightParenthesis == -1) {
 172                     throw new InternalError("Expected closing parenthesis for 'XMLContentType'");
 173                 }
 174                 String value = a.substring(leftParenthesis + 1, rightParenthesis);
 175                 String type = a.substring(0, leftParenthesis);
 176                 annotationElements.add(new AnnotationElement(createAnnotationClass(type), value));
 177             }
 178         }
 179         return annotationElements;
 180     }
 181 
 182     @SuppressWarnings("unchecked")
 183     private Class<? extends Annotation> createAnnotationClass(String type) {
 184         try {
 185             if (!type.startsWith("jdk.jfr.")) {
 186                 throw new IllegalStateException("Incorrect type " + type + ". Annotation class must be located in jdk.jfr package.");
 187             }
 188             Class<?> c = Class.forName(type, true, null);
 189             return (Class<? extends Annotation>) c;
 190         } catch (ClassNotFoundException cne) {
 191             throw new IllegalStateException(cne);
 192         }
 193     }
 194 
 195     private boolean getBoolean(Attributes attributes, String name, boolean defaultValue) {
 196         String value = attributes.getValue(name);
 197         return value == null ? defaultValue : Boolean.valueOf(value);
 198     }
 199 
 200     @Override
 201     public void endElement(String uri, String localName, String qName) {
 202         switch (qName) {
 203         case "Type":
 204         case "Event":
 205             types.put(currentType.name, currentType);
 206             currentType = null;
 207             break;
 208         case "Field":
 209             currentType.fields.add(currentField);
 210             currentField = null;
 211             break;
 212         }
 213     }
 214 
 215     public static List<Type> createTypes() throws IOException {
 216         SAXParser parser = new SAXParserImpl();
 217         MetadataHandler t = new MetadataHandler();
 218         try (InputStream is = new BufferedInputStream(SecuritySupport.getResourceAsStream("/jdk/jfr/internal/types/metadata.xml"))) {
 219             Logger.log(LogTag.JFR_SYSTEM, LogLevel.DEBUG, () -> "Parsing metadata.xml");
 220             try {
 221                 parser.parse(is, t);
 222                 return t.buildTypes();
 223             } catch (Exception e) {
 224                 e.printStackTrace();
 225                 throw new IOException(e);
 226             }
 227         }
 228     }
 229 
 230     private List<Type> buildTypes() {
 231         removeXMLConvenience();
 232         Map<String, Type> typeMap = buildTypeMap();
 233         Map<String, AnnotationElement> relationMap = buildRelationMap(typeMap);
 234         addFields(typeMap, relationMap);
 235         return trimTypes(typeMap);
 236     }
 237 
 238     private Map<String, AnnotationElement> buildRelationMap(Map<String, Type> typeMap) {
 239         Map<String, AnnotationElement> relationMap = new HashMap<>();
 240         for (String relation : relations) {
 241             Type relationType = new Type(Type.TYPES_PREFIX + relation, Type.SUPER_TYPE_ANNOTATION, eventTypeId++);
 242             relationType.setAnnotations(Collections.singletonList(new AnnotationElement(Relational.class)));
 243             AnnotationElement ae = PrivateAccess.getInstance().newAnnotation(relationType, Collections.emptyList(), true);
 244             relationMap.put(relation, ae);
 245             typeMap.put(relationType.getName(), relationType);
 246         }
 247         return relationMap;
 248     }
 249 
 250     private List<Type> trimTypes(Map<String, Type> lookup) {
 251         List<Type> trimmedTypes = new ArrayList<>(lookup.size());
 252         for (Type t : lookup.values()) {
 253             t.trimFields();
 254             trimmedTypes.add(t);
 255         }
 256         return trimmedTypes;
 257     }
 258 
 259     private void addFields(Map<String, Type> lookup, Map<String, AnnotationElement> relationMap) {
 260         for (TypeElement te : types.values()) {
 261             Type type = lookup.get(te.name);
 262             if (te.isEvent) {
 263                 boolean periodic = te.period!= null;
 264                 TypeLibrary.addImplicitFields(type, periodic, te.startTime && !periodic, te.thread, te.stackTrace && !periodic, te.cutoff);
 265             }
 266             for (FieldElement f : te.fields) {
 267                 Type fieldType = Type.getKnownType(f.typeName);
 268                 if (fieldType == null) {
 269                     fieldType = Objects.requireNonNull(lookup.get(f.referenceType.name));
 270                 }
 271                 List<AnnotationElement> aes = new ArrayList<>();
 272                 if (f.unsigned) {
 273                     aes.add(new AnnotationElement(Unsigned.class));
 274                 }
 275                 if (f.contentType != null) {
 276                     aes.addAll(Objects.requireNonNull(xmlContentTypes.get(f.contentType)));
 277                 }
 278                 if (f.relation != null) {
 279                     aes.add(Objects.requireNonNull(relationMap.get(f.relation)));
 280                 }
 281                 if (f.label != null) {
 282                     aes.add(new AnnotationElement(Label.class, f.label));
 283                 }
 284                 if (f.experimental) {
 285                     aes.add(new AnnotationElement(Experimental.class));
 286                 }
 287                 if (f.description != null) {
 288                     aes.add(new AnnotationElement(Description.class, f.description));
 289                 }
 290                 if ("from".equals(f.transition)) {
 291                     aes.add(new AnnotationElement(TransitionFrom.class));
 292                 }
 293                 if ("to".equals(f.transition)) {
 294                     aes.add(new AnnotationElement(TransitionTo.class));
 295                 }
 296                 boolean constantPool = !f.struct && f.referenceType != null;
 297                 type.add(PrivateAccess.getInstance().newValueDescriptor(f.name, fieldType, aes, f.array ? 1 : 0, constantPool, null));
 298             }
 299         }
 300     }
 301 
 302     private Map<String, Type> buildTypeMap() {
 303         Map<String, Type> typeMap = new HashMap<>();
 304         for (Type type : Type.getKnownTypes()) {
 305             typeMap.put(type.getName(), type);
 306         }
 307 
 308         for (TypeElement t : types.values()) {
 309             List<AnnotationElement> aes = new ArrayList<>();
 310             if (t.category != null) {
 311                 aes.add(new AnnotationElement(Category.class, buildCategoryArray(t.category)));
 312             }
 313             if (t.label != null) {
 314                 aes.add(new AnnotationElement(Label.class, t.label));
 315             }
 316             if (t.description != null) {
 317                 aes.add(new AnnotationElement(Description.class, t.description));
 318             }
 319             if (t.isEvent) {
 320                 if (t.period != null) {
 321                     aes.add(new AnnotationElement(Period.class, t.period));
 322                 } else {
 323                     if (t.startTime) {
 324                         aes.add(new AnnotationElement(Threshold.class, "0 ns"));
 325                     }
 326                     if (t.stackTrace) {
 327                         aes.add(new AnnotationElement(StackTrace.class, true));
 328                     }
 329                 }
 330                 if (t.cutoff) {
 331                     aes.add(new AnnotationElement(Cutoff.class, Cutoff.INIFITY));
 332                 }
 333             }
 334             if (t.experimental) {
 335                 aes.add(new AnnotationElement(Experimental.class));
 336             }
 337             Type type;
 338             if (t.isEvent) {
 339                 aes.add(new AnnotationElement(Enabled.class, false));
 340                 type = new PlatformEventType(t.name,  eventTypeId++, false, true);
 341             } else {
 342                 // Struct types had their own XML-element in the past. To have id assigned in the
 343                 // same order as generated .hpp file do some tweaks here.
 344                 boolean valueType = t.name.endsWith("StackFrame") || t.valueType;
 345                 type = new Type(t.name, null, valueType ?  eventTypeId++ : nextTypeId(t.name), false);
 346             }
 347             type.setAnnotations(aes);
 348             typeMap.put(t.name, type);
 349         }
 350         return typeMap;
 351     }
 352 
 353     private long nextTypeId(String name) {
 354         if (Type.THREAD.getName().equals(name)) {
 355             return Type.THREAD.getId();
 356         }
 357         if (Type.STRING.getName().equals(name)) {
 358             return Type.STRING.getId();
 359         }
 360         if (Type.CLASS.getName().equals(name)) {
 361             return Type.CLASS.getId();
 362         }
 363         for (Type type : Type.getKnownTypes()) {
 364             if (type.getName().equals(name)) {
 365                 return type.getId();
 366             }
 367         }
 368         return structTypeId++;
 369     }
 370 
 371     private String[] buildCategoryArray(String category) {
 372         List<String> categories = new ArrayList<>();
 373         StringBuilder sb = new StringBuilder();
 374         for (char c : category.toCharArray()) {
 375             if (c == ',') {
 376                 categories.add(sb.toString().trim());
 377                 sb.setLength(0);
 378             } else {
 379                 sb.append(c);
 380             }
 381         }
 382         categories.add(sb.toString().trim());
 383         return categories.toArray(new String[0]);
 384     }
 385 
 386     private void removeXMLConvenience() {
 387         for (TypeElement t : types.values()) {
 388             XmlType xmlType = xmlTypes.get(t.name);
 389             if (xmlType != null && xmlType.javaType != null) {
 390                 t.name = xmlType.javaType; // known type, i.e primitive
 391             } else {
 392                 if (t.isEvent) {
 393                     t.name = Type.EVENT_NAME_PREFIX + t.name;
 394                 } else {
 395                     t.name = Type.TYPES_PREFIX + t.name;
 396                 }
 397             }
 398         }
 399 
 400         for (TypeElement t : types.values()) {
 401             for (FieldElement f : t.fields) {
 402                 f.referenceType = types.get(f.typeName);
 403                 XmlType xmlType = xmlTypes.get(f.typeName);
 404                 if (xmlType != null) {
 405                     if (xmlType.javaType != null) {
 406                         f.typeName = xmlType.javaType;
 407                     }
 408                     if (xmlType.contentType != null) {
 409                         f.contentType = xmlType.contentType;
 410                     }
 411                     if (xmlType.unsigned) {
 412                         f.unsigned = true;
 413                     }
 414                 }
 415                 if (f.struct && f.referenceType != null) {
 416                     f.referenceType.valueType = true;
 417                 }
 418             }
 419         }
 420     }
 421 }