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 }