| /* |
| * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package jdk.jfr.consumer; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.time.Duration; |
| import java.time.Instant; |
| import java.time.OffsetDateTime; |
| import java.util.Comparator; |
| import java.util.List; |
| import java.util.Objects; |
| |
| import jdk.jfr.Timespan; |
| import jdk.jfr.Timestamp; |
| import jdk.jfr.ValueDescriptor; |
| import jdk.jfr.internal.consumer.JdkJfrConsumer; |
| import jdk.jfr.internal.consumer.ObjectFactory; |
| import jdk.jfr.internal.PrivateAccess; |
| import jdk.jfr.internal.Type; |
| import jdk.jfr.internal.consumer.ObjectContext; |
| import jdk.jfr.internal.tool.PrettyWriter; |
| |
| /** |
| * A complex data type that consists of one or more fields. |
| * <p> |
| * This class provides methods to select and query nested objects by passing a |
| * dot {@code "."} delimited {@code String} object (for instance, |
| * {@code "aaa.bbb"}). A method evaluates a nested object from left to right, |
| * and if a part is {@code null}, it throws {@code NullPointerException}. |
| * |
| * @since 9 |
| */ |
| public class RecordedObject { |
| |
| static{ |
| JdkJfrConsumer access = new JdkJfrConsumer() { |
| public List<Type> readTypes(RecordingFile file) throws IOException { |
| return file.readTypes(); |
| } |
| |
| public boolean isLastEventInChunk(RecordingFile file) { |
| return file.isLastEventInChunk(); |
| } |
| |
| @Override |
| public Object getOffsetDataTime(RecordedObject event, String name) { |
| return event.getOffsetDateTime(name); |
| } |
| |
| @Override |
| public RecordedClass newRecordedClass(ObjectContext objectContext, long id, Object[] values) { |
| return new RecordedClass(objectContext, id, values); |
| } |
| |
| @Override |
| public RecordedClassLoader newRecordedClassLoader(ObjectContext objectContext, long id, Object[] values) { |
| return new RecordedClassLoader(objectContext, id, values); |
| } |
| |
| @Override |
| public Comparator<? super RecordedEvent> eventComparator() { |
| return new Comparator<RecordedEvent>() { |
| @Override |
| public int compare(RecordedEvent e1, RecordedEvent e2) { |
| return Long.compare(e1.endTimeTicks, e2.endTimeTicks); |
| } |
| }; |
| } |
| |
| @Override |
| public RecordedStackTrace newRecordedStackTrace(ObjectContext objectContext, Object[] values) { |
| return new RecordedStackTrace(objectContext, values); |
| } |
| |
| @Override |
| public RecordedThreadGroup newRecordedThreadGroup(ObjectContext objectContext, Object[] values) { |
| return new RecordedThreadGroup(objectContext, values); |
| } |
| |
| @Override |
| public RecordedFrame newRecordedFrame(ObjectContext objectContext, Object[] values) { |
| return new RecordedFrame(objectContext, values); |
| } |
| |
| @Override |
| public RecordedThread newRecordedThread(ObjectContext objectContext, long id, Object[] values) { |
| return new RecordedThread(objectContext, id, values); |
| } |
| |
| @Override |
| public RecordedMethod newRecordedMethod(ObjectContext objectContext, Object[] values) { |
| return new RecordedMethod(objectContext, values); |
| } |
| |
| @Override |
| public RecordedEvent newRecordedEvent(ObjectContext objectContext, Object[] values, long startTimeTicks, long endTimeTicks) { |
| return new RecordedEvent(objectContext, values, startTimeTicks, endTimeTicks); |
| } |
| |
| @Override |
| public void setStartTicks(RecordedEvent event, long startTicks) { |
| event.startTimeTicks = startTicks; |
| } |
| |
| @Override |
| public void setEndTicks(RecordedEvent event, long endTicks) { |
| event.endTimeTicks = endTicks; |
| } |
| |
| @Override |
| public Object[] eventValues(RecordedEvent event) { |
| return event.objects; |
| } |
| }; |
| JdkJfrConsumer.setAccess(access); |
| } |
| |
| private final static class UnsignedValue { |
| private final Object o; |
| |
| UnsignedValue(Object o) { |
| this.o = o; |
| } |
| |
| Object value() { |
| return o; |
| } |
| } |
| |
| final Object[] objects; |
| final ObjectContext objectContext; |
| |
| // package private, not to be subclassed outside this package |
| RecordedObject(ObjectContext objectContext, Object[] objects) { |
| this.objectContext = objectContext; |
| this.objects = objects; |
| } |
| |
| // package private |
| final <T> T getTyped(String name, Class<T> clazz, T defaultValue) { |
| // Unnecessary to check field presence twice, but this |
| // will do for now. |
| if (!hasField(name)) { |
| return defaultValue; |
| } |
| T object = getValue(name); |
| if (object == null || object.getClass().isAssignableFrom(clazz)) { |
| return object; |
| } else { |
| return defaultValue; |
| } |
| } |
| |
| /** |
| * Returns {@code true} if a field with the given name exists, {@code false} |
| * otherwise. |
| * |
| * @param name name of the field to get, not {@code null} |
| * |
| * @return {@code true} if the field exists, {@code false} otherwise. |
| * |
| * @see #getFields() |
| */ |
| public boolean hasField(String name) { |
| Objects.requireNonNull(name); |
| for (ValueDescriptor v : objectContext.fields) { |
| if (v.getName().equals(name)) { |
| return true; |
| } |
| } |
| int dotIndex = name.indexOf("."); |
| if (dotIndex > 0) { |
| String structName = name.substring(0, dotIndex); |
| for (ValueDescriptor v : objectContext.fields) { |
| if (!v.getFields().isEmpty() && v.getName().equals(structName)) { |
| RecordedObject child = getValue(structName); |
| if (child != null) { |
| return child.hasField(name.substring(dotIndex + 1)); |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the value of the field with the given name. |
| * <p> |
| * The return type may be a primitive type or a subclass of |
| * {@link RecordedObject}. |
| * <p> |
| * It's possible to index into a nested object by using {@code "."} (for |
| * instance {@code "thread.group.parent.name}"). |
| * <p> |
| * A field might change or be removed in a future JDK release. A best practice |
| * for callers of this method is to validate the field before attempting access. |
| * <p> |
| * Example |
| * |
| * <pre> |
| * <code> |
| * if (event.hasField("intValue")) { |
| * int intValue = event.getValue("intValue"); |
| * System.out.println("Int value: " + intValue); |
| * } |
| * |
| * if (event.hasField("objectClass")) { |
| * RecordedClass clazz = event.getValue("objectClass"); |
| * System.out.println("Class name: " + clazz.getName()); |
| * } |
| * |
| * if (event.hasField("sampledThread")) { |
| * RecordedThread sampledThread = event.getValue("sampledThread"); |
| * System.out.println("Sampled thread: " + sampledThread.getName()); |
| * } |
| * </code> |
| * </pre> |
| * |
| * @param <T> the return type |
| * @param name of the field to get, not {@code null} |
| * @throws IllegalArgumentException if no field called {@code name} exists |
| * |
| * @return the value, can be {@code null} |
| * |
| * @see #hasField(String) |
| * |
| */ |
| final public <T> T getValue(String name) { |
| @SuppressWarnings("unchecked") |
| T t = (T) getValue(name, false); |
| return t; |
| } |
| |
| protected Object objectAt(int index) { |
| return objects[index]; |
| } |
| |
| private Object getValue(String name, boolean allowUnsigned) { |
| Objects.requireNonNull(name); |
| int index = 0; |
| for (ValueDescriptor v : objectContext.fields) { |
| if (name.equals(v.getName())) { |
| Object object = objectAt(index); |
| if (object == null) { |
| // error or missing |
| return null; |
| } |
| if (v.getFields().isEmpty()) { |
| if (allowUnsigned && PrivateAccess.getInstance().isUnsigned(v)) { |
| // Types that are meaningless to widen |
| if (object instanceof Character || object instanceof Long) { |
| return object; |
| } |
| return new UnsignedValue(object); |
| } |
| return object; // primitives and primitive arrays |
| } else { |
| if (object instanceof RecordedObject) { |
| // known types from factory |
| return object; |
| } |
| // must be array type |
| Object[] array = (Object[]) object; |
| if (v.isArray()) { |
| // struct array |
| return structifyArray(v, array, 0); |
| } |
| // struct |
| return new RecordedObject(objectContext.getInstance(v), (Object[]) object); |
| } |
| } |
| index++; |
| } |
| |
| int dotIndex = name.indexOf("."); |
| if (dotIndex > 0) { |
| String structName = name.substring(0, dotIndex); |
| for (ValueDescriptor v : objectContext.fields) { |
| if (!v.getFields().isEmpty() && v.getName().equals(structName)) { |
| RecordedObject child = getValue(structName); |
| String subName = name.substring(dotIndex + 1); |
| if (child != null) { |
| return child.getValue(subName, allowUnsigned); |
| } else { |
| // Call getValueDescriptor to trigger IllegalArgumentException if the name is |
| // incorrect. Type can't be validate due to type erasure |
| getValueDescriptor(v.getFields(), subName, null); |
| throw new NullPointerException("Field value for \"" + structName + "\" was null. Can't access nested field \"" + subName + "\""); |
| } |
| } |
| } |
| } |
| throw new IllegalArgumentException("Could not find field with name " + name); |
| } |
| |
| // Returns the leaf value descriptor matches both name or value, or throws an |
| // IllegalArgumentException |
| private ValueDescriptor getValueDescriptor(List<ValueDescriptor> descriptors, String name, String leafType) { |
| int dotIndex = name.indexOf("."); |
| if (dotIndex > 0) { |
| String first = name.substring(0, dotIndex); |
| String second = name.substring(dotIndex + 1); |
| for (ValueDescriptor v : descriptors) { |
| if (v.getName().equals(first)) { |
| List<ValueDescriptor> fields = v.getFields(); |
| if (!fields.isEmpty()) { |
| return getValueDescriptor(v.getFields(), second, leafType); |
| } |
| } |
| } |
| throw new IllegalArgumentException("Attempt to get unknown field \"" + first + "\""); |
| } |
| for (ValueDescriptor v : descriptors) { |
| if (v.getName().equals(name)) { |
| if (leafType != null && !v.getTypeName().equals(leafType)) { |
| throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal data type conversion " + leafType); |
| } |
| return v; |
| } |
| } |
| throw new IllegalArgumentException("\"Attempt to get unknown field \"" + name + "\""); |
| } |
| |
| // Gets a value, but checks that type and name is correct first |
| // This is to prevent a call to getString on a thread field, that is |
| // null to succeed. |
| private <T> T getTypedValue(String name, String typeName) { |
| Objects.requireNonNull(name); |
| // Validate name and type first |
| getValueDescriptor(objectContext.fields, name, typeName); |
| return getValue(name); |
| } |
| |
| private Object[] structifyArray(ValueDescriptor v, Object[] array, int dimension) { |
| if (array == null) { |
| return null; |
| } |
| Object[] structArray = new Object[array.length]; |
| ObjectContext objContext = objectContext.getInstance(v); |
| for (int i = 0; i < structArray.length; i++) { |
| Object arrayElement = array[i]; |
| if (dimension == 0) { |
| // No general way to handle structarrays |
| // without invoking ObjectFactory for every instance (which may require id) |
| if (isStackFrameType(v.getTypeName())) { |
| structArray[i] = new RecordedFrame(objContext, (Object[]) arrayElement); |
| } else { |
| structArray[i] = new RecordedObject(objContext, (Object[]) arrayElement); |
| } |
| } else { |
| structArray[i] = structifyArray(v, (Object[]) arrayElement, dimension - 1); |
| } |
| } |
| return structArray; |
| } |
| |
| private boolean isStackFrameType(String typeName) { |
| if (ObjectFactory.STACK_FRAME_VERSION_1.equals(typeName)) { |
| return true; |
| } |
| if (ObjectFactory.STACK_FRAME_VERSION_2.equals(typeName)) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns an immutable list of the fields for this object. |
| * |
| * @return the fields, not {@code null} |
| */ |
| public List<ValueDescriptor> getFields() { |
| return objectContext.fields; |
| } |
| |
| /** |
| * Returns the value of a field of type {@code boolean}. |
| * <p> |
| * It's possible to index into a nested object using {@code "."} (for example, |
| * {@code "aaa.bbb"}). |
| * <p> |
| * A field might change or be removed in a future JDK release. A best practice |
| * for callers of this method is to validate the field before attempting access. |
| * |
| * @param name name of the field to get, not {@code null} |
| * |
| * @return the value of the field, {@code true} or {@code false} |
| * |
| * @throws IllegalArgumentException if the field doesn't exist, or the field is |
| * not of type {@code boolean} |
| * |
| * @see #hasField(String) |
| * @see #getValue(String) |
| */ |
| public final boolean getBoolean(String name) { |
| Object o = getValue(name); |
| if (o instanceof Boolean) { |
| return ((Boolean) o).booleanValue(); |
| } |
| throw newIllegalArgumentException(name, "boolean"); |
| } |
| |
| /** |
| * Returns the value of a field of type {@code byte}. |
| * <p> |
| * It's possible to index into a nested object using {@code "."} (for example, |
| * {@code "foo.bar"}). |
| * <p> |
| * A field might change or be removed in a future JDK release. A best practice |
| * for callers of this method is to validate the field before attempting access. |
| * |
| * @param name of the field to get, not {@code null} |
| * |
| * @return the value of the field |
| * |
| * @throws IllegalArgumentException if the field doesn't exist, or the field is |
| * not of type {@code byte} |
| * |
| * @see #hasField(String) |
| * @see #getValue(String) |
| */ |
| public final byte getByte(String name) { |
| Object o = getValue(name); |
| if (o instanceof Byte) { |
| return ((Byte) o).byteValue(); |
| } |
| throw newIllegalArgumentException(name, "byte"); |
| } |
| |
| /** |
| * Returns the value of a field of type {@code char}. |
| * <p> |
| * It's possible to index into a nested object using {@code "."} (for example, |
| * {@code "aaa.bbb"}). |
| * <p> |
| * A field might change or be removed in a future JDK release. A best practice |
| * for callers of this method is to validate the field before attempting access. |
| * |
| * @param name of the field to get, not {@code null} |
| * |
| * @return the value of the field as a {@code char} |
| * |
| * @throws IllegalArgumentException if the field doesn't exist, or the field is |
| * not of type {@code char} |
| * |
| * @see #hasField(String) |
| * @see #getValue(String) |
| */ |
| public final char getChar(String name) { |
| Object o = getValue(name); |
| if (o instanceof Character) { |
| return ((Character) o).charValue(); |
| } |
| |
| throw newIllegalArgumentException(name, "char"); |
| } |
| |
| /** |
| * Returns the value of a field of type {@code short} or of another primitive |
| * type convertible to type {@code short} by a widening conversion. |
| * <p> |
| * This method can be used on the following types: {@code short} and {@code byte}. |
| * <p> |
| * If the field has the {@code @Unsigned} annotation and is of a narrower type |
| * than {@code short}, then the value is returned as an unsigned. |
| * <p> |
| * It's possible to index into a nested object using {@code "."} (for example, |
| * {@code "aaa.bbb"}). |
| * <p> |
| * A field might change or be removed in a future JDK release. A best practice |
| * for callers of this method is to validate the field before attempting access. |
| * |
| * @param name of the field to get, not {@code null} |
| * |
| * @return the value of the field converted to type {@code short} |
| * |
| * @throws IllegalArgumentException if the field doesn't exist, or the field |
| * value can't be converted to the type {@code short} by a widening |
| * conversion |
| * |
| * @see #hasField(String) |
| * @set #getValue(String) |
| */ |
| public final short getShort(String name) { |
| Object o = getValue(name, true); |
| if (o instanceof Short) { |
| return ((Short) o).shortValue(); |
| } |
| if (o instanceof Byte) { |
| return ((Byte) o).byteValue(); |
| } |
| if (o instanceof UnsignedValue) { |
| Object u = ((UnsignedValue) o).value(); |
| if (u instanceof Short) { |
| return ((Short) u).shortValue(); |
| } |
| if (u instanceof Byte) { |
| return (short) Byte.toUnsignedInt(((Byte) u)); |
| } |
| } |
| throw newIllegalArgumentException(name, "short"); |
| } |
| |
| /** |
| * Returns the value of a field of type {@code int} or of another primitive type |
| * that is convertible to type {@code int} by a widening conversion. |
| * <p> |
| * This method can be used on fields of the following types: {@code int}, |
| * {@code short}, {@code char}, and {@code byte}. |
| * <p> |
| * If the field has the {@code @Unsigned} annotation and is of a narrower type |
| * than {@code int}, then the value will be returned as an unsigned. |
| * <p> |
| * It's possible to index into a nested object using {@code "."} (for example, |
| * {@code "aaa.bbb"}). |
| * <p> |
| * A field might change or be removed in a future JDK release. A best practice |
| * for callers of this method is to validate the field before attempting access. |
| * |
| * @param name of the field to get, not {@code null} |
| * |
| * @return the value of the field converted to type {@code int} |
| * |
| * @throws IllegalArgumentException if the field doesn't exist, or the field |
| * value can't be converted to the type {@code int} by a widening |
| * conversion |
| * |
| * @see #hasField(String) |
| * @set #getValue(String) |
| */ |
| public final int getInt(String name) { |
| Object o = getValue(name, true); |
| if (o instanceof Integer) { |
| return ((Integer) o).intValue(); |
| } |
| if (o instanceof Short) { |
| return ((Short) o).intValue(); |
| } |
| if (o instanceof Character) { |
| return ((Character) o).charValue(); |
| } |
| if (o instanceof Byte) { |
| return ((Byte) o).intValue(); |
| } |
| if (o instanceof UnsignedValue) { |
| Object u = ((UnsignedValue) o).value(); |
| if (u instanceof Integer) { |
| return ((Integer) u).intValue(); |
| } |
| if (u instanceof Short) { |
| return Short.toUnsignedInt(((Short) u)); |
| } |
| if (u instanceof Byte) { |
| return Byte.toUnsignedInt(((Byte) u)); |
| } |
| } |
| throw newIllegalArgumentException(name, "int"); |
| } |
| |
| /** |
| * Returns the value of a field of type {@code float} or of another primitive |
| * type convertible to type {@code float} by a widening conversion. |
| * <p> |
| * This method can be used on fields of the following types: {@code float}, |
| * {@code long}, {@code int}, {@code short}, {@code char}, and {@code byte}. |
| * <p> |
| * It's possible to index into a nested object using {@code "."} (for example, |
| * {@code "aaa.bbb"}). |
| * <p> |
| * A field might change or be removed in a future JDK release. A best practice |
| * for callers of this method is to validate the field before attempting access. |
| * |
| * @param name of the field to get, not {@code null} |
| * |
| * @return the value of the field converted to type {@code float} |
| * |
| * @throws IllegalArgumentException if the field doesn't exist, or the field |
| * value can't be converted to the type {@code float} by a widening |
| * conversion |
| * |
| * @see #hasField(String) |
| * @set #getValue(String) |
| */ |
| public final float getFloat(String name) { |
| Object o = getValue(name); |
| if (o instanceof Float) { |
| return ((Float) o).floatValue(); |
| } |
| if (o instanceof Long) { |
| return ((Long) o).floatValue(); |
| } |
| if (o instanceof Integer) { |
| return ((Integer) o).floatValue(); |
| } |
| if (o instanceof Short) { |
| return ((Short) o).floatValue(); |
| } |
| if (o instanceof Byte) { |
| return ((Byte) o).byteValue(); |
| } |
| if (o instanceof Character) { |
| return ((Character) o).charValue(); |
| } |
| throw newIllegalArgumentException(name, "float"); |
| } |
| |
| /** |
| * Returns the value of a field of type {@code long} or of another primitive |
| * type that is convertible to type {@code long} by a widening conversion. |
| * <p> |
| * This method can be used on fields of the following types: {@code long}, |
| * {@code int}, {@code short}, {@code char}, and {@code byte}. |
| * <p> |
| * If the field has the {@code @Unsigned} annotation and is of a narrower type |
| * than {@code long}, then the value will be returned as an unsigned. |
| * <p> |
| * It's possible to index into a nested object using {@code "."} (for example, |
| * {@code "aaa.bbb"}). |
| * <p> |
| * A field might change or be removed in a future JDK release. A best practice |
| * for callers of this method is to validate the field before attempting access. |
| * |
| * @param name of the field to get, not {@code null} |
| * |
| * @return the value of the field converted to type {@code long} |
| * |
| * @throws IllegalArgumentException if the field doesn't exist, or the field |
| * value can't be converted to the type {@code long} via a widening |
| * conversion |
| * |
| * @see #hasField(String) |
| * @set #getValue(String) |
| */ |
| public final long getLong(String name) { |
| Object o = getValue(name, true); |
| if (o instanceof Long) { |
| return ((Long) o).longValue(); |
| } |
| if (o instanceof Integer) { |
| return ((Integer) o).longValue(); |
| } |
| if (o instanceof Short) { |
| return ((Short) o).longValue(); |
| } |
| if (o instanceof Character) { |
| return ((Character) o).charValue(); |
| } |
| if (o instanceof Byte) { |
| return ((Byte) o).longValue(); |
| } |
| if (o instanceof UnsignedValue) { |
| Object u = ((UnsignedValue) o).value(); |
| if (u instanceof Integer) { |
| return Integer.toUnsignedLong(((Integer) u)); |
| } |
| if (u instanceof Short) { |
| return Short.toUnsignedLong(((Short) u)); |
| } |
| if (u instanceof Byte) { |
| return Byte.toUnsignedLong(((Byte) u)); |
| } |
| } |
| throw newIllegalArgumentException(name, "long"); |
| } |
| |
| /** |
| * Returns the value of a field of type {@code double} or of another primitive |
| * type that is convertible to type {@code double} by a widening conversion. |
| * <p> |
| * This method can be used on fields of the following types: {@code double}, {@code float}, |
| * {@code long}, {@code int}, {@code short}, {@code char}, and {@code byte}. |
| * <p> |
| * It's possible to index into a nested object using {@code "."} (for example, |
| * {@code "aaa.bbb"}). |
| * <p> |
| * A field might change or be removed in a future JDK release. A best practice |
| * for callers of this method is to validate the field before attempting access. |
| * |
| * @param name of the field to get, not {@code null} |
| * |
| * @return the value of the field converted to type {@code double} |
| * |
| * @throws IllegalArgumentException if the field doesn't exist, or the field |
| * value can't be converted to the type {@code double} by a widening |
| * conversion |
| * |
| * @see #hasField(String) |
| * @set #getValue(String) |
| */ |
| public final double getDouble(String name) { |
| Object o = getValue(name); |
| if (o instanceof Double) { |
| return ((Double) o).doubleValue(); |
| } |
| if (o instanceof Float) { |
| return ((Float) o).doubleValue(); |
| } |
| if (o instanceof Long) { |
| return ((Long) o).doubleValue(); |
| } |
| if (o instanceof Integer) { |
| return ((Integer) o).doubleValue(); |
| } |
| if (o instanceof Short) { |
| return ((Short) o).doubleValue(); |
| } |
| if (o instanceof Byte) { |
| return ((Byte) o).byteValue(); |
| } |
| if (o instanceof Character) { |
| return ((Character) o).charValue(); |
| } |
| throw newIllegalArgumentException(name, "double"); |
| } |
| |
| /** |
| * Returns the value of a field of type {@code String}. |
| * <p> |
| * It's possible to index into a nested object using {@code "."} (for example, |
| * {@code "foo.bar"}). |
| * <p> |
| * A field might change or be removed in a future JDK release. A best practice |
| * for callers of this method is to validate the field before attempting access. |
| * |
| * @param name of the field to get, not {@code null} |
| * |
| * @return the value of the field as a {@code String}, can be {@code null} |
| * |
| * @throws IllegalArgumentException if the field doesn't exist, or the field |
| * isn't of type {@code String} |
| * |
| * @see #hasField(String) |
| * @set #getValue(String) |
| */ |
| public final String getString(String name) { |
| return getTypedValue(name, "java.lang.String"); |
| } |
| |
| /** |
| * Returns the value of a timespan field. |
| * <p> |
| * This method can be used on fields annotated with {@code @Timespan}, and of |
| * the following types: {@code long}, {@code int}, {@code short}, {@code char}, |
| * and {@code byte}. |
| * <p> |
| * It's possible to index into a nested object using {@code "."} (for example, |
| * {@code "aaa.bbb"}). |
| * <p> |
| * A field might change or be removed in a future JDK release. A best practice |
| * for callers of this method is to validate the field before attempting access. |
| * |
| * @param name of the field to get, not {@code null} |
| * |
| * @return a time span represented as a {@code Duration}, not {@code null} |
| * |
| * @throws IllegalArgumentException if the field doesn't exist, or the field |
| * value can't be converted to a {@code Duration} object |
| * |
| * @see #hasField(String) |
| * @set #getValue(String) |
| */ |
| public final Duration getDuration(String name) { |
| Object o = getValue(name); |
| if (o instanceof Long) { |
| return getDuration(((Long) o).longValue(), name); |
| } |
| if (o instanceof Integer) { |
| return getDuration(((Integer) o).longValue(), name); |
| } |
| if (o instanceof Short) { |
| return getDuration(((Short) o).longValue(), name); |
| } |
| if (o instanceof Character) { |
| return getDuration(((Character) o).charValue(), name); |
| } |
| if (o instanceof Byte) { |
| return getDuration(((Byte) o).longValue(), name); |
| } |
| if (o instanceof UnsignedValue) { |
| Object u = ((UnsignedValue) o).value(); |
| if (u instanceof Integer) { |
| return getDuration(Integer.toUnsignedLong((Integer) u), name); |
| } |
| if (u instanceof Short) { |
| return getDuration(Short.toUnsignedLong((Short) u), name); |
| } |
| if (u instanceof Byte) { |
| return getDuration(Short.toUnsignedLong((Byte) u), name); |
| } |
| } |
| throw newIllegalArgumentException(name, "java,time.Duration"); |
| } |
| |
| private Duration getDuration(long timespan, String name) throws InternalError { |
| ValueDescriptor v = getValueDescriptor(objectContext.fields, name, null); |
| if (timespan == Long.MIN_VALUE) { |
| return Duration.ofSeconds(Long.MIN_VALUE, 0); |
| } |
| Timespan ts = v.getAnnotation(Timespan.class); |
| if (ts != null) { |
| switch (ts.value()) { |
| case Timespan.MICROSECONDS: |
| return Duration.ofNanos(1000 * timespan); |
| case Timespan.SECONDS: |
| return Duration.ofSeconds(timespan); |
| case Timespan.MILLISECONDS: |
| return Duration.ofMillis(timespan); |
| case Timespan.NANOSECONDS: |
| return Duration.ofNanos(timespan); |
| case Timespan.TICKS: |
| return Duration.ofNanos(objectContext.convertTimespan(timespan)); |
| } |
| throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal timespan unit " + ts.value()); |
| } |
| throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with missing @Timespan"); |
| } |
| |
| /** |
| * Returns the value of a timestamp field. |
| * <p> |
| * This method can be used on fields annotated with {@code @Timestamp}, and of |
| * the following types: {@code long}, {@code int}, {@code short}, {@code char} |
| * and {@code byte}. |
| * <p> |
| * It's possible to index into a nested object using {@code "."} (for example, |
| * {@code "aaa.bbb"}). |
| * <p> |
| * A field might change or be removed in a future JDK release. A best practice |
| * for callers of this method is to validate the field before attempting access. |
| * |
| * @param name of the field to get, not {@code null} |
| * |
| * @return a timstamp represented as an {@code Instant}, not {@code null} |
| * |
| * @throws IllegalArgumentException if the field doesn't exist, or the field |
| * value can't be converted to an {@code Instant} object |
| * |
| * @see #hasField(String) |
| * @set #getValue(String) |
| */ |
| public final Instant getInstant(String name) { |
| Object o = getValue(name, true); |
| if (o instanceof Long) { |
| return getInstant(((Long) o).longValue(), name); |
| } |
| if (o instanceof Integer) { |
| return getInstant(((Integer) o).longValue(), name); |
| } |
| if (o instanceof Short) { |
| return getInstant(((Short) o).longValue(), name); |
| } |
| if (o instanceof Character) { |
| return getInstant(((Character) o).charValue(), name); |
| } |
| if (o instanceof Byte) { |
| return getInstant(((Byte) o).longValue(), name); |
| } |
| if (o instanceof UnsignedValue) { |
| Object u = ((UnsignedValue) o).value(); |
| if (u instanceof Integer) { |
| return getInstant(Integer.toUnsignedLong((Integer) u), name); |
| } |
| if (u instanceof Short) { |
| return getInstant(Short.toUnsignedLong((Short) u), name); |
| } |
| if (u instanceof Byte) { |
| return getInstant(Short.toUnsignedLong((Byte) u), name); |
| } |
| } |
| throw newIllegalArgumentException(name, "java.time.Instant"); |
| } |
| |
| private Instant getInstant(long timestamp, String name) { |
| ValueDescriptor v = getValueDescriptor(objectContext.fields, name, null); |
| Timestamp ts = v.getAnnotation(Timestamp.class); |
| if (ts != null) { |
| if (timestamp == Long.MIN_VALUE) { |
| return Instant.MIN; |
| } |
| switch (ts.value()) { |
| case Timestamp.MILLISECONDS_SINCE_EPOCH: |
| return Instant.ofEpochMilli(timestamp); |
| case Timestamp.TICKS: |
| return Instant.ofEpochSecond(0, objectContext.convertTimestamp(timestamp)); |
| } |
| throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal timestamp unit " + ts.value()); |
| } |
| throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with missing @Timestamp"); |
| } |
| |
| /** |
| * Returns the value of a field of type {@code Class}. |
| * <p> |
| * It's possible to index into a nested object using {@code "."} (for example, |
| * {@code "aaa.bbb"}). |
| * <p> |
| * A field might change or be removed in a future JDK release. A best practice |
| * for callers of this method is to validate the field before attempting access. |
| * |
| * @param name of the field to get, not {@code null} |
| * |
| * @return the value of the field as a {@code RecordedClass}, can be |
| * {@code null} |
| * |
| * @throws IllegalArgumentException if the field doesn't exist, or the field |
| * isn't of type {@code Class} |
| * |
| * @see #hasField(String) |
| * @set #getValue(String) |
| */ |
| public final RecordedClass getClass(String name) { |
| return getTypedValue(name, "java.lang.Class"); |
| } |
| |
| /** |
| * Returns the value of a field of type {@code Thread}. |
| * <p> |
| * It's possible to index into a nested object using {@code "."} (for example, |
| * {@code "foo.bar"}). |
| * <p> |
| * A field might change or be removed in a future JDK release. A best practice |
| * for callers of this method is to validate the field before attempting access. |
| * |
| * @param name of the field to get, not {@code null} |
| * |
| * @return the value of the field as a {@code RecordedThread} object, can be |
| * {@code null} |
| * |
| * @throws IllegalArgumentException if the field doesn't exist, or the field |
| * isn't of type {@code Thread} |
| * |
| * @see #hasField(String) |
| * @set #getValue(String) |
| */ |
| public final RecordedThread getThread(String name) { |
| return getTypedValue(name, "java.lang.Thread"); |
| } |
| |
| /** |
| * Returns a textual representation of this object. |
| * |
| * @return textual description of this object |
| */ |
| @Override |
| final public String toString() { |
| StringWriter s = new StringWriter(); |
| PrettyWriter p = new PrettyWriter(new PrintWriter(s)); |
| p.setStackDepth(5); |
| if (this instanceof RecordedEvent) { |
| p.print((RecordedEvent) this); |
| } else { |
| p.print(this, ""); |
| } |
| p.flush(true); |
| return s.toString(); |
| } |
| |
| // package private for now. Used by EventWriter |
| private OffsetDateTime getOffsetDateTime(String name) { |
| Instant instant = getInstant(name); |
| if (instant.equals(Instant.MIN)) { |
| return OffsetDateTime.MIN; |
| } |
| return OffsetDateTime.ofInstant(getInstant(name), objectContext.getZoneOffset()); |
| } |
| |
| private static IllegalArgumentException newIllegalArgumentException(String name, String typeName) { |
| return new IllegalArgumentException("Attempt to get field \"" + name + "\" with illegal data type conversion " + typeName); |
| } |
| } |