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.consumer;
  27 
  28 import java.io.IOException;
  29 import java.io.PrintWriter;
  30 import java.io.StringWriter;
  31 import java.time.Duration;
  32 import java.time.Instant;
  33 import java.util.List;
  34 import java.util.Objects;
  35 
  36 import jdk.jfr.Timespan;
  37 import jdk.jfr.Timestamp;
  38 import jdk.jfr.ValueDescriptor;
  39 import jdk.jfr.internal.PrivateAccess;
  40 import jdk.jfr.internal.Type;
  41 import jdk.jfr.internal.cmd.PrettyWriter;
  42 
  43 /**
  44  * A complex data type that consists of one or more fields.
  45  * <p>
  46  * This class provides methods to select and query nested objects by passing a
  47  * dot {@code "."} delimited {@code String} object (for instance,
  48  * {@code "aaa.bbb"}). A method evaluates a nested object from left to right,
  49  * and if a part is {@code null}, it throws {@code NullPointerException}.
  50  *
  51  * @since 9
  52  */
  53 public class RecordedObject {
  54 
  55     private final static class UnsignedValue {
  56         private final Object o;
  57 
  58         UnsignedValue(Object o) {
  59             this.o = o;
  60         }
  61 
  62         Object value() {
  63             return o;
  64         }
  65     }
  66 
  67     private final Object[] objects;
  68     private final List<ValueDescriptor> descriptors;
  69     private final TimeConverter timeConverter;
  70 
  71     // package private, not to be subclassed outside this package
  72     RecordedObject(List<ValueDescriptor> descriptors, Object[] objects, TimeConverter timeConverter) {
  73         this.descriptors = descriptors;
  74         this.objects = objects;
  75         this.timeConverter = timeConverter;
  76     }
  77 
  78     // package private
  79     final <T> T getTyped(String name, Class<T> clazz, T defaultValue) {
  80         // Unnecessary to check field presence twice, but this
  81         // will do for now.
  82         if (!hasField(name)) {
  83             return defaultValue;
  84         }
  85         T object = getValue(name);
  86         if (object == null || object.getClass().isAssignableFrom(clazz)) {
  87             return object;
  88         } else {
  89             return defaultValue;
  90         }
  91     }
  92 
  93     /**
  94      * Returns {@code true} if a field with the given name exists, {@code false}
  95      * otherwise.
  96      *
  97      * @param name name of the field to get, not {@code null}
  98      *
  99      * @return {@code true} if the field exists, {@code false} otherwise.
 100      *
 101      * @see #getFields()
 102      */
 103     public boolean hasField(String name) {
 104         Objects.requireNonNull(name);
 105         for (ValueDescriptor v : descriptors) {
 106             if (v.getName().equals(name)) {
 107                 return true;
 108             }
 109         }
 110         int dotIndex = name.indexOf(".");
 111         if (dotIndex > 0) {
 112             String structName = name.substring(0, dotIndex);
 113             for (ValueDescriptor v : descriptors) {
 114                 if (!v.getFields().isEmpty() && v.getName().equals(structName)) {
 115                     RecordedObject child = getValue(structName);
 116                     if (child != null) {
 117                         return child.hasField(name.substring(dotIndex + 1));
 118                     }
 119                 }
 120             }
 121         }
 122         return false;
 123     }
 124 
 125     /**
 126      * Returns the value of the field with the given name.
 127      * <p>
 128      * The return type may be a primitive type or a subclass of
 129      * {@link RecordedObject}.
 130      * <p>
 131      * It's possible to index into a nested object by using {@code "."} (for
 132      * instance {@code "thread.group.parent.name}").
 133      * <p>
 134      * A field might change or be removed in a future JDK release. A best practice
 135      * for callers of this method is to validate the field before attempting access.
 136      * <p>
 137      * Example
 138      *
 139      * <pre>
 140      * <code>
 141      * if (event.hasField("intValue")) {
 142      *   int intValue = event.getValue("intValue");
 143      *   System.out.println("Int value: " + intValue);
 144      * }
 145      *
 146      * if (event.hasField("objectClass")) {
 147      *   RecordedClass clazz = event.getValue("objectClass");
 148      *   System.out.println("Class name: " + clazz.getName());
 149      * }
 150      *
 151      * if (event.hasField("sampledThread")) {
 152      *   RecordedThread sampledThread = event.getValue("sampledThread");
 153      *   System.out.println("Sampled thread: " + sampledThread.getName());
 154      * }
 155      * </code>
 156      * </pre>
 157      *
 158      * @param <T> the return type
 159      * @param  name of the field to get, not {@code null}
 160      * @throws IllegalArgumentException if no field called {@code name} exists
 161      *
 162      * @return the value, can be {@code null}
 163      *
 164      * @see #hasField(String)
 165      *
 166      */
 167     final public <T> T getValue(String name) {
 168         @SuppressWarnings("unchecked")
 169         T t = (T) getValue(name, false);
 170         return t;
 171     }
 172 
 173     private Object getValue(String name, boolean allowUnsigned) {
 174         Objects.requireNonNull(name);
 175         int index = 0;
 176         for (ValueDescriptor v : descriptors) {
 177             if (name.equals(v.getName())) {
 178                 Object object = objects[index];
 179                 if (object == null) {
 180                     // error or missing
 181                     return null;
 182                 }
 183                 if (v.getFields().isEmpty()) {
 184                     if (allowUnsigned && PrivateAccess.getInstance().isUnsigned(v)) {
 185                         // Types that are meaningless to widen
 186                         if (object instanceof Character || object instanceof Long) {
 187                             return object;
 188                         }
 189                         return new UnsignedValue(object);
 190                     }
 191                     return object; // primitives and primitive arrays
 192                 } else {
 193                     if (object instanceof RecordedObject) {
 194                         // known types from factory
 195                         return object;
 196                     }
 197                     // must be array type
 198                     Object[] array = (Object[]) object;
 199                     if (v.isArray()) {
 200                         // struct array
 201                         return structifyArray(v, array, 0);
 202                     }
 203                     // struct
 204                     return new RecordedObject(v.getFields(), (Object[]) object, timeConverter);
 205                 }
 206             }
 207             index++;
 208         }
 209 
 210         int dotIndex = name.indexOf(".");
 211         if (dotIndex > 0) {
 212             String structName = name.substring(0, dotIndex);
 213             for (ValueDescriptor v : descriptors) {
 214                 if (!v.getFields().isEmpty() && v.getName().equals(structName)) {
 215                     RecordedObject child = getValue(structName);
 216                     String subName = name.substring(dotIndex + 1);
 217                     if (child != null) {
 218                         return child.getValue(subName, allowUnsigned);
 219                     } else {
 220                         // Call getValueDescriptor to trigger IllegalArgumentException if the name is
 221                         // incorrect. Type can't be validate due to type erasure
 222                         getValueDescriptor(v.getFields(), subName, null);
 223                         throw new NullPointerException("Field value for \"" + structName + "\" was null. Can't access nested field \"" + subName + "\"");
 224                     }
 225                 }
 226             }
 227         }
 228         throw new IllegalArgumentException("Could not find field with name " + name);
 229     }
 230 
 231     // Returns the leaf value descriptor matches both name or value, or throws an
 232     // IllegalArgumentException
 233     private ValueDescriptor getValueDescriptor(List<ValueDescriptor> descriptors, String name, String leafType) {
 234         int dotIndex = name.indexOf(".");
 235         if (dotIndex > 0) {
 236             String first = name.substring(0, dotIndex);
 237             String second = name.substring(dotIndex + 1);
 238             for (ValueDescriptor v : descriptors) {
 239                 if (v.getName().equals(first)) {
 240                     List<ValueDescriptor> fields = v.getFields();
 241                     if (!fields.isEmpty()) {
 242                         return getValueDescriptor(v.getFields(), second, leafType);
 243                     }
 244                 }
 245             }
 246             throw new IllegalArgumentException("Attempt to get unknown field \"" + first + "\"");
 247         }
 248         for (ValueDescriptor v : descriptors) {
 249             if (v.getName().equals(name)) {
 250                 if (leafType != null && !v.getTypeName().equals(leafType)) {
 251                     throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal data type conversion " + leafType);
 252                 }
 253                 return v;
 254             }
 255         }
 256         throw new IllegalArgumentException("\"Attempt to get unknown field \"" + name + "\"");
 257     }
 258 
 259     // Gets a value, but checks that type and name is correct first
 260     // This is to prevent a call to getString on a thread field, that is
 261     // null to succeed.
 262     private <T> T getTypedValue(String name, String typeName) {
 263         Objects.requireNonNull(name);
 264         // Validate name and type first
 265         getValueDescriptor(descriptors, name, typeName);
 266         return getValue(name);
 267     }
 268 
 269     private Object[] structifyArray(ValueDescriptor v, Object[] array, int dimension) {
 270         if (array == null) {
 271             return null;
 272         }
 273         Object[] structArray = new Object[array.length];
 274         for (int i = 0; i < structArray.length; i++) {
 275             Object arrayElement = array[i];
 276             if (dimension == 0) {
 277                 // Hack, no general way to handle structarrays
 278                 // without invoking ObjectFarctory for every instance.
 279                 if ((Type.ORACLE_TYPE_PREFIX + "StackFrame").equals(v.getTypeName())) {
 280                     structArray[i] = new RecordedFrame(v.getFields(), (Object[]) arrayElement, timeConverter);
 281                 } else {
 282                     structArray[i] = new RecordedObject(v.getFields(), (Object[]) arrayElement, timeConverter);
 283                 }
 284             } else {
 285                 structArray[i] = structifyArray(v, (Object[]) arrayElement, dimension - 1);
 286             }
 287         }
 288         return structArray;
 289     }
 290 
 291     /**
 292      * Returns an immutable list of the fields for this object.
 293      *
 294      * @return the fields, not {@code null}
 295      */
 296     public List<ValueDescriptor> getFields() {
 297         return descriptors;
 298     }
 299 
 300     /**
 301      * Returns the value of a field of type {@code boolean}.
 302      * <p>
 303      * It's possible to index into a nested object using {@code "."} (for example,
 304      * {@code "aaa.bbb"}).
 305      * <p>
 306      * A field might change or be removed in a future JDK release. A best practice
 307      * for callers of this method is to validate the field before attempting access.
 308      *
 309      * @param name name of the field to get, not {@code null}
 310      *
 311      * @return the value of the field, {@code true} or {@code false}
 312      *
 313      * @throws IllegalArgumentException if the field doesn't exist, or the field is
 314      *         not of type {@code boolean}
 315      *
 316      * @see #hasField(String)
 317      * @see #getValue(String)
 318      */
 319     public final boolean getBoolean(String name) {
 320         Object o = getValue(name);
 321         if (o instanceof Boolean) {
 322             return ((Boolean) o).booleanValue();
 323         }
 324         throw newIllegalArgumentException(name, "boolean");
 325     }
 326 
 327     /**
 328      * Returns the value of a field of type {@code byte}.
 329      * <p>
 330      * It's possible to index into a nested object using {@code "."} (for example,
 331      * {@code "foo.bar"}).
 332      * <p>
 333      * A field might change or be removed in a future JDK release. A best practice
 334      * for callers of this method is to validate the field before attempting access.
 335      *
 336      * @param name of the field to get, not {@code null}
 337      *
 338      * @return the value of the field
 339      *
 340      * @throws IllegalArgumentException if the field doesn't exist, or the field is
 341      *         not of type {@code byte}
 342      *
 343      * @see #hasField(String)
 344      * @see #getValue(String)
 345      */
 346     public final byte getByte(String name) {
 347         Object o = getValue(name);
 348         if (o instanceof Byte) {
 349             return ((Byte) o).byteValue();
 350         }
 351         throw newIllegalArgumentException(name, "byte");
 352     }
 353 
 354     /**
 355      * Returns the value of a field of type {@code char}.
 356      * <p>
 357      * It's possible to index into a nested object using {@code "."} (for example,
 358      * {@code "aaa.bbb"}).
 359      * <p>
 360      * A field might change or be removed in a future JDK release. A best practice
 361      * for callers of this method is to validate the field before attempting access.
 362      *
 363      * @param name of the field to get, not {@code null}
 364      *
 365      * @return the value of the field as a {@code char}
 366      *
 367      * @throws IllegalArgumentException if the field doesn't exist, or the field is
 368      *         not of type {@code char}
 369      *
 370      * @see #hasField(String)
 371      * @see #getValue(String)
 372      */
 373     public final char getChar(String name) {
 374         Object o = getValue(name);
 375         if (o instanceof Character) {
 376             return ((Character) o).charValue();
 377         }
 378 
 379         throw newIllegalArgumentException(name, "char");
 380     }
 381 
 382     /**
 383      * Returns the value of a field of type {@code short} or of another primitive
 384      * type convertible to type {@code short} by a widening conversion.
 385      * <p>
 386      * This method can be used on the following types: {@code short} and {@code byte}.
 387      * <p>
 388      * If the field has the {@code @Unsigned} annotation and is of a narrower type
 389      * than {@code short}, then the value is returned as an unsigned.
 390      * <p>
 391      * It's possible to index into a nested object using {@code "."} (for example,
 392      * {@code "aaa.bbb"}).
 393      * <p>
 394      * A field might change or be removed in a future JDK release. A best practice
 395      * for callers of this method is to validate the field before attempting access.
 396      *
 397      * @param name of the field to get, not {@code null}
 398      *
 399      * @return the value of the field converted to type {@code short}
 400      *
 401      * @throws IllegalArgumentException if the field doesn't exist, or the field
 402      *         value can't be converted to the type {@code short} by a widening
 403      *         conversion
 404      *
 405      * @see #hasField(String)
 406      * @set #getValue(String)
 407      */
 408     public final short getShort(String name) {
 409         Object o = getValue(name, true);
 410         if (o instanceof Short) {
 411             return ((Short) o).shortValue();
 412         }
 413         if (o instanceof Byte) {
 414             return ((Byte) o).byteValue();
 415         }
 416         if (o instanceof UnsignedValue) {
 417             Object u = ((UnsignedValue) o).value();
 418             if (u instanceof Short) {
 419                 return ((Short) u).shortValue();
 420             }
 421             if (u instanceof Byte) {
 422                 return (short) Byte.toUnsignedInt(((Byte) u));
 423             }
 424         }
 425         throw newIllegalArgumentException(name, "short");
 426     }
 427 
 428     /**
 429      * Returns the value of a field of type {@code int} or of another primitive type
 430      * that is convertible to type {@code int} by a widening conversion.
 431      * <p>
 432      * This method can be used on fields of the following types: {@code int},
 433      * {@code short}, {@code char}, and {@code byte}.
 434      * <p>
 435      * If the field has the {@code @Unsigned} annotation and is of a narrower type
 436      * than {@code int}, then the value will be returned as an unsigned.
 437      * <p>
 438      * It's possible to index into a nested object using {@code "."} (for example,
 439      * {@code "aaa.bbb"}).
 440      * <p>
 441      * A field might change or be removed in a future JDK release. A best practice
 442      * for callers of this method is to validate the field before attempting access.
 443      *
 444      * @param name of the field to get, not {@code null}
 445      *
 446      * @return the value of the field converted to type {@code int}
 447      *
 448      * @throws IllegalArgumentException if the field doesn't exist, or the field
 449      *         value can't be converted to the type {@code int} by a widening
 450      *         conversion
 451      *
 452      * @see #hasField(String)
 453      * @set #getValue(String)
 454      */
 455     public final int getInt(String name) {
 456         Object o = getValue(name, true);
 457         if (o instanceof Integer) {
 458             return ((Integer) o).intValue();
 459         }
 460         if (o instanceof Short) {
 461             return ((Short) o).intValue();
 462         }
 463         if (o instanceof Character) {
 464             return ((Character) o).charValue();
 465         }
 466         if (o instanceof Byte) {
 467             return ((Byte) o).intValue();
 468         }
 469         if (o instanceof UnsignedValue) {
 470             Object u = ((UnsignedValue) o).value();
 471             if (u instanceof Integer) {
 472                 return ((Integer) u).intValue();
 473             }
 474             if (u instanceof Short) {
 475                 return Short.toUnsignedInt(((Short) u));
 476             }
 477             if (u instanceof Byte) {
 478                 return Byte.toUnsignedInt(((Byte) u));
 479             }
 480         }
 481         throw newIllegalArgumentException(name, "int");
 482     }
 483 
 484     /**
 485      * Returns the value of a field of type {@code float} or of another primitive
 486      * type convertible to type {@code float} bby a widening conversion.
 487      * <p>
 488      * This method can be used on fields of the following types: {@code float},
 489      * {@code long}, {@code int}, {@code short}, {@code char}, and {@code byte}.
 490      * <p>
 491      * It's possible to index into a nested object using {@code "."} (for example,
 492      * {@code "aaa.bbb"}).
 493      * <p>
 494      * A field might change or be removed in a future JDK release. A best practice
 495      * for callers of this method is to validate the field before attempting access.
 496      *
 497      * @param name of the field to get, not {@code null}
 498      *
 499      * @return the value of the field converted to type {@code float}
 500      *
 501      * @throws IllegalArgumentException if the field doesn't exist, or the field
 502      *         value can't be converted to the type {@code float} by a widening
 503      *         conversion
 504      *
 505      * @see #hasField(String)
 506      * @set #getValue(String)
 507      */
 508     public final float getFloat(String name) {
 509         Object o = getValue(name);
 510         if (o instanceof Float) {
 511             return ((Float) o).floatValue();
 512         }
 513         if (o instanceof Long) {
 514             return ((Long) o).floatValue();
 515         }
 516         if (o instanceof Integer) {
 517             return ((Integer) o).floatValue();
 518         }
 519         if (o instanceof Short) {
 520             return ((Short) o).floatValue();
 521         }
 522         if (o instanceof Byte) {
 523             return ((Byte) o).byteValue();
 524         }
 525         if (o instanceof Character) {
 526             return ((Character) o).charValue();
 527         }
 528         throw newIllegalArgumentException(name, "float");
 529     }
 530 
 531     /**
 532      * Returns the value of a field of type {@code long} or of another primitive
 533      * type that is convertible to type {@code long} by a widening conversion.
 534      * <p>
 535      * This method can be used on fields of the following types: {@code long},
 536      * {@code int}, {@code short}, {@code char}, and {@code byte}.
 537      * <p>
 538      * If the field has the {@code @Unsigned} annotation and is of a narrower type
 539      * than {@code long}, then the value will be returned as an unsigned.
 540      * <p>
 541      * It's possible to index into a nested object using {@code "."} (for example,
 542      * {@code "aaa.bbb"}).
 543      * <p>
 544      * A field might change or be removed in a future JDK release. A best practice
 545      * for callers of this method is to validate the field before attempting access.
 546      *
 547      * @param name of the field to get, not {@code null}
 548      *
 549      * @return the value of the field converted to type {@code long}
 550      *
 551      * @throws IllegalArgumentException if the field doesn't exist, or the field
 552      *         value can't be converted to the type {@code long} via a widening
 553      *         conversion
 554      *
 555      * @see #hasField(String)
 556      * @set #getValue(String)
 557      */
 558     public final long getLong(String name) {
 559         Object o = getValue(name, true);
 560         if (o instanceof Long) {
 561             return ((Long) o).longValue();
 562         }
 563         if (o instanceof Integer) {
 564             return ((Integer) o).longValue();
 565         }
 566         if (o instanceof Short) {
 567             return ((Short) o).longValue();
 568         }
 569         if (o instanceof Character) {
 570             return ((Character) o).charValue();
 571         }
 572         if (o instanceof Byte) {
 573             return ((Byte) o).longValue();
 574         }
 575         if (o instanceof UnsignedValue) {
 576             Object u = ((UnsignedValue) o).value();
 577             if (u instanceof Integer) {
 578                 return Integer.toUnsignedLong(((Integer) u));
 579             }
 580             if (u instanceof Short) {
 581                 return Short.toUnsignedLong(((Short) u));
 582             }
 583             if (u instanceof Byte) {
 584                 return Byte.toUnsignedLong(((Byte) u));
 585             }
 586         }
 587         throw newIllegalArgumentException(name, "long");
 588     }
 589 
 590     /**
 591      * Returns the value of a field of type {@code double} or of another primitive
 592      * type that is convertible to type {@code double} by a widening conversion.
 593      * <p>
 594      * This method can be used on fields of the following types: {@code double}, {@code float},
 595      * {@code long}, {@code int}, {@code short}, {@code char}, and {@code byte}.
 596      * <p>
 597      * It's possible to index into a nested object using {@code "."} (for example,
 598      * {@code "aaa.bbb"}).
 599      * <p>
 600      * A field might change or be removed in a future JDK release. A best practice
 601      * for callers of this method is to validate the field before attempting access.
 602      *
 603      * @param name of the field to get, not {@code null}
 604      *
 605      * @return the value of the field converted to type {@code double}
 606      *
 607      * @throws IllegalArgumentException if the field doesn't exist, or the field
 608      *         value can't be converted to the type {@code double} by a widening
 609      *         conversion
 610      *
 611      * @see #hasField(String)
 612      * @set #getValue(String)
 613      */
 614     public final double getDouble(String name) {
 615         Object o = getValue(name);
 616         if (o instanceof Double) {
 617             return ((Double) o).doubleValue();
 618         }
 619         if (o instanceof Float) {
 620             return ((Float) o).doubleValue();
 621         }
 622         if (o instanceof Long) {
 623             return ((Long) o).doubleValue();
 624         }
 625         if (o instanceof Integer) {
 626             return ((Integer) o).doubleValue();
 627         }
 628         if (o instanceof Short) {
 629             return ((Short) o).doubleValue();
 630         }
 631         if (o instanceof Byte) {
 632             return ((Byte) o).byteValue();
 633         }
 634         if (o instanceof Character) {
 635             return ((Character) o).charValue();
 636         }
 637         throw newIllegalArgumentException(name, "double");
 638     }
 639 
 640     /**
 641      * Returns the value of a field of type {@code String}.
 642      * <p>
 643      * It's possible to index into a nested object using {@code "."} (for example,
 644      * {@code "foo.bar"}).
 645      * <p>
 646      * A field might change or be removed in a future JDK release. A best practice
 647      * for callers of this method is to validate the field before attempting access.
 648      *
 649      * @param name of the field to get, not {@code null}
 650      *
 651      * @return the value of the field as a {@code String}, can be {@code null}
 652      *
 653      * @throws IllegalArgumentException if the field doesn't exist, or the field
 654      *         isn't of type {@code String}
 655      *
 656      * @see #hasField(String)
 657      * @set #getValue(String)
 658      */
 659     public final String getString(String name) {
 660         return getTypedValue(name, "java.lang.String");
 661     }
 662 
 663     /**
 664      * Returns the value of a timespan field.
 665      * <p>
 666      * This method can be used on fields annotated with {@code @Timespan}, and of
 667      * the following types: {@code long}, {@code int}, {@code short}, {@code char},
 668      * and {@code byte}.
 669      * <p>
 670      * It's possible to index into a nested object using {@code "."} (for example,
 671      * {@code "aaa.bbb"}).
 672      * <p>
 673      * A field might change or be removed in a future JDK release. A best practice
 674      * for callers of this method is to validate the field before attempting access.
 675      *
 676      * @param name of the field to get, not {@code null}
 677      *
 678      * @return a time span represented as a {@code Duration}, not {@code null}
 679      *
 680      * @throws IllegalArgumentException if the field doesn't exist, or the field
 681      *         value can't be converted to a {@code Duration} object
 682      *
 683      * @see #hasField(String)
 684      * @set #getValue(String)
 685      */
 686     public final Duration getDuration(String name) {
 687         Object o = getValue(name);
 688         if (o instanceof Long) {
 689             return getDuration(((Long) o).longValue(), name);
 690         }
 691         if (o instanceof Integer) {
 692             return getDuration(((Integer) o).longValue(), name);
 693         }
 694         if (o instanceof Short) {
 695             return getDuration(((Short) o).longValue(), name);
 696         }
 697         if (o instanceof Character) {
 698             return getDuration(((Character) o).charValue(), name);
 699         }
 700         if (o instanceof Byte) {
 701             return getDuration(((Byte) o).longValue(), name);
 702         }
 703         if (o instanceof UnsignedValue) {
 704             Object u = ((UnsignedValue) o).value();
 705             if (u instanceof Integer) {
 706                 return getDuration(Integer.toUnsignedLong((Integer) u), name);
 707             }
 708             if (u instanceof Short) {
 709                 return getDuration(Short.toUnsignedLong((Short) u), name);
 710             }
 711             if (u instanceof Byte) {
 712                 return getDuration(Short.toUnsignedLong((Byte) u), name);
 713             }
 714         }
 715         throw newIllegalArgumentException(name, "java,time.Duration");
 716     }
 717 
 718     private Duration getDuration(long timespan, String name) throws InternalError {
 719         ValueDescriptor v = getValueDescriptor(descriptors, name, null);
 720         Timespan ts = v.getAnnotation(Timespan.class);
 721         if (ts != null) {
 722             switch (ts.value()) {
 723             case Timespan.MICROSECONDS:
 724                 return Duration.ofNanos(1000 * timespan);
 725             case Timespan.SECONDS:
 726                 return Duration.ofSeconds(timespan);
 727             case Timespan.MILLISECONDS:
 728                 return Duration.ofMillis(timespan);
 729             case Timespan.NANOSECONDS:
 730                 return Duration.ofNanos(timespan);
 731             case Timespan.TICKS:
 732                 return Duration.ofNanos(timeConverter.convertTimespan(timespan));
 733             }
 734             throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal timespan unit " + ts.value());
 735         }
 736         throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with missing @Timespan");
 737     }
 738 
 739     /**
 740      * Returns the value of a timestamp field.
 741      * <p>
 742      * This method can be used on fields annotated with {@code @Timestamp}, and of
 743      * the following types: {@code long}, {@code int}, {@code short}, {@code char}
 744      * and {@code byte}.
 745      * <p>
 746      * It's possible to index into a nested object using {@code "."} (for example,
 747      * {@code "aaa.bbb"}).
 748      * <p>
 749      * A field might change or be removed in a future JDK release. A best practice
 750      * for callers of this method is to validate the field before attempting access.
 751      *
 752      * @param name of the field to get, not {@code null}
 753      *
 754      * @return a timstamp represented as an {@code Instant}, not {@code null}
 755      *
 756      * @throws IllegalArgumentException if the field doesn't exist, or the field
 757      *         value can't be converted to an {@code Instant} object
 758      *
 759      * @see #hasField(String)
 760      * @set #getValue(String)
 761      */
 762     public final Instant getInstant(String name) {
 763         Object o = getValue(name, true);
 764         if (o instanceof Long) {
 765             return getInstant(((Long) o).longValue(), name);
 766         }
 767         if (o instanceof Integer) {
 768             return getInstant(((Integer) o).longValue(), name);
 769         }
 770         if (o instanceof Short) {
 771             return getInstant(((Short) o).longValue(), name);
 772         }
 773         if (o instanceof Character) {
 774             return getInstant(((Character) o).charValue(), name);
 775         }
 776         if (o instanceof Byte) {
 777             return getInstant(((Byte) o).longValue(), name);
 778         }
 779         if (o instanceof UnsignedValue) {
 780             Object u = ((UnsignedValue) o).value();
 781             if (u instanceof Integer) {
 782                 return getInstant(Integer.toUnsignedLong((Integer) u), name);
 783             }
 784             if (u instanceof Short) {
 785                 return getInstant(Short.toUnsignedLong((Short) u), name);
 786             }
 787             if (u instanceof Byte) {
 788                 return getInstant(Short.toUnsignedLong((Byte) u), name);
 789             }
 790         }
 791         throw newIllegalArgumentException(name, "java,time.Instant");
 792     }
 793 
 794     private Instant getInstant(long timestamp, String name) {
 795         ValueDescriptor v = getValueDescriptor(descriptors, name, null);
 796         Timestamp ts = v.getAnnotation(Timestamp.class);
 797         if (ts != null) {
 798             switch (ts.value()) {
 799             case Timestamp.MILLISECONDS_SINCE_EPOCH:
 800                 return Instant.ofEpochMilli(timestamp);
 801             case Timestamp.TICKS:
 802                 return Instant.ofEpochSecond(0, timeConverter.convertTimestamp(timestamp));
 803             }
 804             throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal timestamp unit " + ts.value());
 805         }
 806         throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with missing @Timestamp");
 807     }
 808 
 809     /**
 810      * Returns the value of a field of type {@code Class}.
 811      * <p>
 812      * It's possible to index into a nested object using {@code "."} (for example,
 813      * {@code "aaa.bbb"}).
 814      * <p>
 815      * A field might change or be removed in a future JDK release. A best practice
 816      * for callers of this method is to validate the field before attempting access.
 817      *
 818      * @param name of the field to get, not {@code null}
 819      *
 820      * @return the value of the field as a {@code RecordedClass}, can be
 821      *         {@code null}
 822      *
 823      * @throws IllegalArgumentException if the field doesn't exist, or the field
 824      *         isn't of type {@code Class}
 825      *
 826      * @see #hasField(String)
 827      * @set #getValue(String)
 828      */
 829     public final RecordedClass getClass(String name) {
 830         return getTypedValue(name, "java.lang.Class");
 831     }
 832 
 833     /**
 834      * Returns the value of a field of type {@code Thread}.
 835      * <p>
 836      * It's possible to index into a nested object using {@code "."} (for example,
 837      * {@code "foo.bar"}).
 838      * <p>
 839      * A field might change or be removed in a future JDK release. A best practice
 840      * for callers of this method is to validate the field before attempting access.
 841      *
 842      * @param name of the field to get, not {@code null}
 843      *
 844      * @return the value of the field as a {@code RecordedThread} object, can be
 845      *         {@code null}
 846      *
 847      * @throws IllegalArgumentException if the field doesn't exist, or the field
 848      *         isn't of type {@code Thread}
 849      *
 850      * @see #hasField(String)
 851      * @set #getValue(String)
 852      */
 853     public final RecordedThread getThread(String name) {
 854         return getTypedValue(name, "java.lang.Thread");
 855     }
 856 
 857     /**
 858      * Returns a textual representation of this object.
 859      *
 860      * @return textual description of this object
 861      */
 862     @Override
 863     final public String toString() {
 864         StringWriter s = new StringWriter();
 865         PrettyWriter p = new PrettyWriter(new PrintWriter(s));
 866         try {
 867             if (this instanceof RecordedEvent) {
 868                 p.print((RecordedEvent) this);
 869             } else {
 870                 p.print(this, "");
 871             }
 872 
 873         } catch (IOException e) {
 874             // Ignore, should not happen with StringWriter
 875         }
 876         p.flush();
 877         return s.toString();
 878     }
 879 
 880     private static IllegalArgumentException newIllegalArgumentException(String name, String typeName) {
 881         return new IllegalArgumentException("Attempt to get field \"" + name + "\" with illegal data type conversion " + typeName);
 882     }
 883 }