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 }