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