/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * ident "%Z%%M% %I% %E% SMI" */ package org.opensolaris.os.dtrace; import java.io.*; import java.util.Arrays; import java.beans.*; /** * A traced D primitive generated by a DTrace action such as {@code * trace()} or {@code tracemem()}, or else an element in a composite * value generated by DTrace. *

* Immutable. Supports persistence using {@link java.beans.XMLEncoder}. * * @author Tom Erickson */ public final class ScalarRecord implements ValueRecord, Serializable { static final long serialVersionUID = -6920826443240176724L; static final int RAW_BYTES_INDENT = 5; static { try { BeanInfo info = Introspector.getBeanInfo(ScalarRecord.class); PersistenceDelegate persistenceDelegate = new DefaultPersistenceDelegate( new String[] {"value", "numberOfBytes"}) { /* * Need to prevent DefaultPersistenceDelegate from using * overridden equals() method, resulting in a * StackOverFlowError. Revert to PersistenceDelegate * implementation. See * http://forum.java.sun.com/thread.jspa?threadID= * 477019&tstart=135 */ protected boolean mutatesTo(Object oldInstance, Object newInstance) { return (newInstance != null && oldInstance != null && oldInstance.getClass() == newInstance.getClass()); } }; BeanDescriptor d = info.getBeanDescriptor(); d.setValue("persistenceDelegate", persistenceDelegate); } catch (IntrospectionException e) { System.out.println(e); } } /** @serial */ private final Object value; /** @serial */ private int numberOfBytes; /** * Creates a scalar record with the given DTrace primitive and the * number of bytes used to store the primitive in the native DTrace * buffer. Since traced 8- and 16-bit integers are promoted (as * unsigned values) to 32-bit integers, it may be important for * output formatting to know the number of bytes used to represent * the primitive before promotion. * * @param v DTrace primitive data value * @param nativeByteCount number of bytes used to store the given * primitive in the native DTrace buffer * @throws NullPointerException if the given value is {@code null} * @throws IllegalArgumentException if the given number of bytes is * not consistent with the given primitive type or is not greater * than zero * @throws ClassCastException if the given value is not a DTrace * primitive type listed as a possible return value of {@link * #getValue()} */ public ScalarRecord(Object v, int nativeByteCount) { value = v; numberOfBytes = nativeByteCount; validate(); } private final void validate() { if (value == null) { throw new NullPointerException(); } // Short-circuit-evaluate common cases first if (value instanceof Integer) { switch (numberOfBytes) { case 1: case 2: case 4: break; default: throw new IllegalArgumentException( "number of bytes is " + numberOfBytes + ", expected 1, 2, or 4 for Integer primitive"); } } else if (value instanceof Long) { if (numberOfBytes != 8) { throw new IllegalArgumentException( "number of bytes is " + numberOfBytes + ", expected 8 for Long primitive"); } } else if ((value instanceof String) || (value instanceof byte[])) { switch (numberOfBytes) { case 1: case 2: case 4: case 8: throw new IllegalArgumentException( "number of bytes is " + numberOfBytes + ", expected a number other than " + "1, 2, 4, or 8 for String or byte-array " + "primitive"); } } else if (value instanceof Number) { if (numberOfBytes <= 0) { throw new IllegalArgumentException( "number of bytes is " + numberOfBytes + ", must be greater than zero"); } } else { throw new ClassCastException(value.getClass().getName() + " value is not a D primitive"); } } /** * Gets the traced D primitive value of this record. * * @return a non-null value whose type is one of the following: *

*/ public Object getValue() { return value; } /** * Gets the number of bytes used to store the primitive value of * this record in the native DTrace buffer. Since traced 8- and * 16-bit integers are promoted (as unsigned values) to 32-bit * integers, it may be important for output formatting to know the * number of bytes used to represent the primitive before promotion. * * @return the number of bytes used to store the primitive value * of this record in the native DTrace buffer, guaranteed to be * greater than zero and consisitent with the type of the primitive * value */ public int getNumberOfBytes() { return numberOfBytes; } /** * Compares the specified object with this record for equality. * Defines equality as having the same value. * * @return {@code true} if and only if the specified object is also * a {@code ScalarRecord} and the values returned by the {@link * #getValue()} methods of both instances are equal, {@code false} * otherwise. Values are compared using {@link * java.lang.Object#equals(Object o) Object.equals()}, unless they * are arrays of raw bytes, in which case they are compared using * {@link java.util.Arrays#equals(byte[] a, byte[] a2)}. */ @Override public boolean equals(Object o) { if (o instanceof ScalarRecord) { ScalarRecord r = (ScalarRecord)o; if (value instanceof byte[]) { if (r.value instanceof byte[]) { byte[] a1 = (byte[])value; byte[] a2 = (byte[])r.value; return Arrays.equals(a1, a2); } return false; } return value.equals(r.value); } return false; } /** * Overridden to ensure that equal instances have equal hashcodes. * * @return {@link java.lang.Object#hashCode()} of {@link * #getValue()}, or {@link java.util.Arrays#hashCode(byte[] a)} if * the value is a raw byte array */ @Override public int hashCode() { if (value instanceof byte[]) { return Arrays.hashCode((byte[])value); } return value.hashCode(); } private static final int BYTE_SIGN_BIT = 1 << 7; /** * Static utility for treating a byte as unsigned by converting it * to int without sign extending. */ static int unsignedByte(byte b) { if (b < 0) { b ^= (byte)BYTE_SIGN_BIT; return ((int)b) | BYTE_SIGN_BIT; } return (int)b; } static String hexString(int n, int width) { String s = Integer.toHexString(n); int len = s.length(); if (width < len) { s = s.substring(len - width); } else if (width > len) { s = (spaces(width - len) + s); } return s; } static String spaces(int n) { StringBuilder buf = new StringBuilder(); for (int i = 0; i < n; ++i) { buf.append(' '); } return buf.toString(); } /** * Represents a byte array as a table of unsigned byte values in hex, * 16 per row ending in their corresponding character * representations (or a period (.) for each unprintable * character). Uses default indentation. * * @see ScalarRecord#rawBytesString(byte[] bytes, int indent) */ static String rawBytesString(byte[] bytes) { return rawBytesString(bytes, RAW_BYTES_INDENT); } /** * Represents a byte array as a table of unsigned byte values in hex, * 16 per row ending in their corresponding character * representations (or a period (.) for each unprintable * character). The table begins and ends with a newline, includes a * header row, and uses a newline at the end of each row. * * @param bytes array of raw bytes treated as unsigned when * converted to hex display * @param indent number of spaces to indent each line of the * returned string * @return table representation of 16 bytes per row as hex and * character values */ static String rawBytesString(byte[] bytes, int indent) { // ported from libdtrace/common/dt_consume.c dt_print_bytes() int i, j; int u; StringBuilder buf = new StringBuilder(); String leftMargin = spaces(indent); buf.append('\n'); buf.append(leftMargin); buf.append(" "); for (i = 0; i < 16; i++) { buf.append(" "); buf.append("0123456789abcdef".charAt(i)); } buf.append(" 0123456789abcdef\n"); int nbytes = bytes.length; String hex; for (i = 0; i < nbytes; i += 16) { buf.append(leftMargin); buf.append(hexString(i, 5)); buf.append(':'); for (j = i; (j < (i + 16)) && (j < nbytes); ++j) { buf.append(hexString(unsignedByte(bytes[j]), 3)); } while ((j++ % 16) != 0) { buf.append(" "); } buf.append(" "); for (j = i; (j < (i + 16)) && (j < nbytes); ++j) { u = unsignedByte(bytes[j]); if ((u < ' ') || (u > '~')) { buf.append('.'); } else { buf.append((char) u); } } buf.append('\n'); } return buf.toString(); } static String valueToString(Object value) { String s; if (value instanceof byte[]) { s = rawBytesString((byte[])value); } else { s = value.toString(); } return s; } private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); // check class invariants try { validate(); } catch (Exception e) { InvalidObjectException x = new InvalidObjectException( e.getMessage()); x.initCause(e); throw x; } } /** * Gets the natural string representation of the traced D primitive. * * @return the value of {@link Object#toString} when called on * {@link #getValue()}; or if the value is an array of raw bytes, a * table displaying 16 bytes per row in unsigned hex followed by the * ASCII character representations of those bytes (each unprintable * character is represented by a period (.)) */ public String toString() { return ScalarRecord.valueToString(getValue()); } }