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.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 }