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 static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_CONSTANT_POOL;
  29 import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_DEFAULT_VALUE;
  30 import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_DIMENSION;
  31 import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_GMT_OFFSET;
  32 import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_ID;
  33 import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_LOCALE;
  34 import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_NAME;
  35 import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_SIMPLE_TYPE;
  36 import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_SUPER_TYPE;
  37 import static jdk.jfr.internal.MetadataDescriptor.ATTRIBUTE_TYPE_ID;
  38 import static jdk.jfr.internal.MetadataDescriptor.ELEMENT_ANNOTATION;
  39 import static jdk.jfr.internal.MetadataDescriptor.ELEMENT_FIELD;
  40 import static jdk.jfr.internal.MetadataDescriptor.ELEMENT_SETTING;
  41 import static jdk.jfr.internal.MetadataDescriptor.ELEMENT_TYPE;
  42 
  43 import java.io.DataOutput;
  44 import java.io.IOException;
  45 import java.util.HashMap;
  46 import java.util.HashSet;
  47 import java.util.LinkedHashMap;
  48 import java.util.List;
  49 import java.util.Set;
  50 
  51 import jdk.jfr.AnnotationElement;
  52 import jdk.jfr.SettingDescriptor;
  53 import jdk.jfr.ValueDescriptor;
  54 import jdk.jfr.internal.MetadataDescriptor.Attribute;
  55 import jdk.jfr.internal.MetadataDescriptor.Element;
  56 import jdk.jfr.internal.consumer.RecordingInput;
  57 
  58 /**
  59  * Class responsible for converting a list of types into a format that can be
  60  * parsed by a client.
  61  *
  62  */
  63 final class MetadataWriter {
  64 
  65     private final Element metadata = new Element("metadata");
  66     private final Element root = new Element("root");
  67 
  68     public MetadataWriter(MetadataDescriptor descriptor) {
  69         descriptor.getTypes().forEach(type -> makeTypeElement(metadata, type));
  70 
  71         root.add(metadata);
  72         Element region = new Element("region");
  73         region.addAttribute(ATTRIBUTE_LOCALE, descriptor.locale);
  74         region.addAttribute(ATTRIBUTE_GMT_OFFSET, descriptor.gmtOffset);
  75         root.add(region);
  76     }
  77 
  78     public void writeBinary(DataOutput output) throws IOException {
  79         Set<String> stringPool = new HashSet<>(1000);
  80         // Possible improvement, sort string by how often they occur.
  81         // and assign low number to the most frequently used.
  82         buildStringPool(root, stringPool);
  83         HashMap<String, Integer> lookup = new LinkedHashMap<>(stringPool.size());
  84         int index = 0;
  85         int poolSize = stringPool.size();
  86         writeInt(output, poolSize);
  87         for (String s : stringPool) {
  88             lookup.put(s, index);
  89             writeString(output, s);
  90             index++;
  91         }
  92         write(output, root, lookup);
  93     }
  94 
  95     private void writeString(DataOutput out, String s) throws IOException {
  96         if (s == null ) {
  97             out.writeByte(RecordingInput.STRING_ENCODING_NULL);
  98             return;
  99         }
 100         out.writeByte(RecordingInput.STRING_ENCODING_CHAR_ARRAY); // encoding UTF-16
 101         int length = s.length();
 102         writeInt(out, length);
 103             for (int i = 0; i < length; i++) {
 104                 writeInt(out, s.charAt(i));
 105             }
 106     }
 107 
 108     private void writeInt(DataOutput out, int v) throws IOException {
 109 
 110         long s = v & 0xffffffffL;
 111         if (s < 1 << 7) {
 112             out.write((byte) (s));
 113             return;
 114         }
 115         out.write((byte) (s | 0x80)); // first byte written
 116         s >>= 7;
 117         if (s < 1 << 7) {
 118             out.write((byte) (s));
 119             return;
 120         }
 121         out.write((byte) (s | 0x80)); // second byte written
 122         s >>= 7;
 123         if (s < 1 << 7) {
 124             out.write((byte) (s));
 125             return;
 126         }
 127         out.write((byte) (s | 0x80)); // third byte written
 128         s >>= 7;
 129         if (s < 1 << 7) {
 130             out.write((byte) (s));
 131             return;
 132         }
 133         s >>= 7;
 134         out.write((byte) (s));// fourth byte written
 135     }
 136 
 137     private void buildStringPool(Element element, Set<String> pool) {
 138         pool.add(element.name);
 139         for (Attribute a : element.attributes) {
 140             pool.add(a.name);
 141             pool.add(a.value);
 142         }
 143         for (Element child : element.elements) {
 144             buildStringPool(child, pool);
 145         }
 146     }
 147 
 148     private void write(DataOutput output,Element element, HashMap<String, Integer> lookup) throws IOException {
 149         writeInt(output, lookup.get(element.name));
 150         writeInt(output, element.attributes.size());
 151         for (Attribute a : element.attributes) {
 152             writeInt(output, lookup.get(a.name));
 153             writeInt(output, lookup.get(a.value));
 154         }
 155         writeInt(output, element.elements.size());
 156         for (Element child : element.elements) {
 157             write(output, child, lookup);
 158         }
 159     }
 160 
 161     private void makeTypeElement(Element root, Type type) {
 162         Element element = root.newChild(ELEMENT_TYPE);
 163         element.addAttribute(ATTRIBUTE_NAME, type.getName());
 164         String superType = type.getSuperType();
 165         if (superType != null) {
 166             element.addAttribute(ATTRIBUTE_SUPER_TYPE, superType);
 167         }
 168         if (type.isSimpleType()) {
 169             element.addAttribute(ATTRIBUTE_SIMPLE_TYPE, true);
 170         }
 171         element.addAttribute(ATTRIBUTE_ID, type.getId());
 172         if (type instanceof PlatformEventType) {
 173             for (SettingDescriptor v : ((PlatformEventType)type).getSettings()) {
 174                 makeSettingElement(element, v);
 175             }
 176         }
 177         for (ValueDescriptor v : type.getFields()) {
 178             makeFieldElement(element, v);
 179         }
 180         for (AnnotationElement a : type.getAnnotationElements()) {
 181             makeAnnotation(element, a);
 182         }
 183     }
 184 
 185     private void makeSettingElement(Element typeElement, SettingDescriptor s) {
 186         Element element = typeElement.newChild(ELEMENT_SETTING);
 187         element.addAttribute(ATTRIBUTE_NAME, s.getName());
 188         element.addAttribute(ATTRIBUTE_TYPE_ID, s.getTypeId());
 189         element.addAttribute(ATTRIBUTE_DEFAULT_VALUE, s.getDefaultValue());
 190         for (AnnotationElement a : s.getAnnotationElements()) {
 191             makeAnnotation(element, a);
 192         }
 193     }
 194 
 195     private void makeFieldElement(Element typeElement, ValueDescriptor v) {
 196         Element element = typeElement.newChild(ELEMENT_FIELD);
 197         element.addAttribute(ATTRIBUTE_NAME, v.getName());
 198         element.addAttribute(ATTRIBUTE_TYPE_ID, v.getTypeId());
 199         if (v.isArray()) {
 200             element.addAttribute(ATTRIBUTE_DIMENSION, 1);
 201         }
 202         if (PrivateAccess.getInstance().isConstantPool(v)) {
 203             element.addAttribute(ATTRIBUTE_CONSTANT_POOL, true);
 204         }
 205         for (AnnotationElement a : v.getAnnotationElements()) {
 206             makeAnnotation(element, a);
 207         }
 208     }
 209 
 210     private void makeAnnotation(Element entity, AnnotationElement annotation) {
 211         Element element = entity.newChild(ELEMENT_ANNOTATION);
 212         element.addAttribute(ATTRIBUTE_TYPE_ID, annotation.getTypeId());
 213         List<Object> values = annotation.getValues();
 214         int index = 0;
 215         for (ValueDescriptor v : annotation.getValueDescriptors()) {
 216             Object value = values.get(index++);
 217             if (v.isArray()) {
 218                 element.addArrayAttribute(element, v.getName(), value);
 219             } else {
 220                 element.addAttribute(v.getName(), value);
 221             }
 222         }
 223     }
 224 
 225 }