src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedObject.java
changeset 50113 caf115bb98ad
child 52850 f527b24990d7
equal deleted inserted replaced
50112:7a2a740815b7 50113:caf115bb98ad
       
     1 /*
       
     2  * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package jdk.jfr.consumer;
       
    27 
       
    28 import java.io.IOException;
       
    29 import java.io.PrintWriter;
       
    30 import java.io.StringWriter;
       
    31 import java.time.Duration;
       
    32 import java.time.Instant;
       
    33 import java.util.List;
       
    34 import java.util.Objects;
       
    35 
       
    36 import jdk.jfr.Timespan;
       
    37 import jdk.jfr.Timestamp;
       
    38 import jdk.jfr.ValueDescriptor;
       
    39 import jdk.jfr.internal.PrivateAccess;
       
    40 import jdk.jfr.internal.cmd.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 9
       
    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         Timespan ts = v.getAnnotation(Timespan.class);
       
   730         if (ts != null) {
       
   731             switch (ts.value()) {
       
   732             case Timespan.MICROSECONDS:
       
   733                 return Duration.ofNanos(1000 * timespan);
       
   734             case Timespan.SECONDS:
       
   735                 return Duration.ofSeconds(timespan);
       
   736             case Timespan.MILLISECONDS:
       
   737                 return Duration.ofMillis(timespan);
       
   738             case Timespan.NANOSECONDS:
       
   739                 return Duration.ofNanos(timespan);
       
   740             case Timespan.TICKS:
       
   741                 return Duration.ofNanos(timeConverter.convertTimespan(timespan));
       
   742             }
       
   743             throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal timespan unit " + ts.value());
       
   744         }
       
   745         throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with missing @Timespan");
       
   746     }
       
   747 
       
   748     /**
       
   749      * Returns the value of a timestamp field.
       
   750      * <p>
       
   751      * This method can be used on fields annotated with {@code @Timestamp}, and of
       
   752      * the following types: {@code long}, {@code int}, {@code short}, {@code char}
       
   753      * and {@code byte}.
       
   754      * <p>
       
   755      * It's possible to index into a nested object using {@code "."} (for example,
       
   756      * {@code "aaa.bbb"}).
       
   757      * <p>
       
   758      * A field might change or be removed in a future JDK release. A best practice
       
   759      * for callers of this method is to validate the field before attempting access.
       
   760      *
       
   761      * @param name of the field to get, not {@code null}
       
   762      *
       
   763      * @return a timstamp represented as an {@code Instant}, not {@code null}
       
   764      *
       
   765      * @throws IllegalArgumentException if the field doesn't exist, or the field
       
   766      *         value can't be converted to an {@code Instant} object
       
   767      *
       
   768      * @see #hasField(String)
       
   769      * @set #getValue(String)
       
   770      */
       
   771     public final Instant getInstant(String name) {
       
   772         Object o = getValue(name, true);
       
   773         if (o instanceof Long) {
       
   774             return getInstant(((Long) o).longValue(), name);
       
   775         }
       
   776         if (o instanceof Integer) {
       
   777             return getInstant(((Integer) o).longValue(), name);
       
   778         }
       
   779         if (o instanceof Short) {
       
   780             return getInstant(((Short) o).longValue(), name);
       
   781         }
       
   782         if (o instanceof Character) {
       
   783             return getInstant(((Character) o).charValue(), name);
       
   784         }
       
   785         if (o instanceof Byte) {
       
   786             return getInstant(((Byte) o).longValue(), name);
       
   787         }
       
   788         if (o instanceof UnsignedValue) {
       
   789             Object u = ((UnsignedValue) o).value();
       
   790             if (u instanceof Integer) {
       
   791                 return getInstant(Integer.toUnsignedLong((Integer) u), name);
       
   792             }
       
   793             if (u instanceof Short) {
       
   794                 return getInstant(Short.toUnsignedLong((Short) u), name);
       
   795             }
       
   796             if (u instanceof Byte) {
       
   797                 return getInstant(Short.toUnsignedLong((Byte) u), name);
       
   798             }
       
   799         }
       
   800         throw newIllegalArgumentException(name, "java,time.Instant");
       
   801     }
       
   802 
       
   803     private Instant getInstant(long timestamp, String name) {
       
   804         ValueDescriptor v = getValueDescriptor(descriptors, name, null);
       
   805         Timestamp ts = v.getAnnotation(Timestamp.class);
       
   806         if (ts != null) {
       
   807             switch (ts.value()) {
       
   808             case Timestamp.MILLISECONDS_SINCE_EPOCH:
       
   809                 return Instant.ofEpochMilli(timestamp);
       
   810             case Timestamp.TICKS:
       
   811                 return Instant.ofEpochSecond(0, timeConverter.convertTimestamp(timestamp));
       
   812             }
       
   813             throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal timestamp unit " + ts.value());
       
   814         }
       
   815         throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with missing @Timestamp");
       
   816     }
       
   817 
       
   818     /**
       
   819      * Returns the value of a field of type {@code Class}.
       
   820      * <p>
       
   821      * It's possible to index into a nested object using {@code "."} (for example,
       
   822      * {@code "aaa.bbb"}).
       
   823      * <p>
       
   824      * A field might change or be removed in a future JDK release. A best practice
       
   825      * for callers of this method is to validate the field before attempting access.
       
   826      *
       
   827      * @param name of the field to get, not {@code null}
       
   828      *
       
   829      * @return the value of the field as a {@code RecordedClass}, can be
       
   830      *         {@code null}
       
   831      *
       
   832      * @throws IllegalArgumentException if the field doesn't exist, or the field
       
   833      *         isn't of type {@code Class}
       
   834      *
       
   835      * @see #hasField(String)
       
   836      * @set #getValue(String)
       
   837      */
       
   838     public final RecordedClass getClass(String name) {
       
   839         return getTypedValue(name, "java.lang.Class");
       
   840     }
       
   841 
       
   842     /**
       
   843      * Returns the value of a field of type {@code Thread}.
       
   844      * <p>
       
   845      * It's possible to index into a nested object using {@code "."} (for example,
       
   846      * {@code "foo.bar"}).
       
   847      * <p>
       
   848      * A field might change or be removed in a future JDK release. A best practice
       
   849      * for callers of this method is to validate the field before attempting access.
       
   850      *
       
   851      * @param name of the field to get, not {@code null}
       
   852      *
       
   853      * @return the value of the field as a {@code RecordedThread} object, can be
       
   854      *         {@code null}
       
   855      *
       
   856      * @throws IllegalArgumentException if the field doesn't exist, or the field
       
   857      *         isn't of type {@code Thread}
       
   858      *
       
   859      * @see #hasField(String)
       
   860      * @set #getValue(String)
       
   861      */
       
   862     public final RecordedThread getThread(String name) {
       
   863         return getTypedValue(name, "java.lang.Thread");
       
   864     }
       
   865 
       
   866     /**
       
   867      * Returns a textual representation of this object.
       
   868      *
       
   869      * @return textual description of this object
       
   870      */
       
   871     @Override
       
   872     final public String toString() {
       
   873         StringWriter s = new StringWriter();
       
   874         PrettyWriter p = new PrettyWriter(new PrintWriter(s));
       
   875         try {
       
   876             if (this instanceof RecordedEvent) {
       
   877                 p.print((RecordedEvent) this);
       
   878             } else {
       
   879                 p.print(this, "");
       
   880             }
       
   881 
       
   882         } catch (IOException e) {
       
   883             // Ignore, should not happen with StringWriter
       
   884         }
       
   885         p.flush();
       
   886         return s.toString();
       
   887     }
       
   888 
       
   889     private static IllegalArgumentException newIllegalArgumentException(String name, String typeName) {
       
   890         return new IllegalArgumentException("Attempt to get field \"" + name + "\" with illegal data type conversion " + typeName);
       
   891     }
       
   892 }