1fb3fb4f3Stomee /*
2fb3fb4f3Stomee  * CDDL HEADER START
3fb3fb4f3Stomee  *
4fb3fb4f3Stomee  * The contents of this file are subject to the terms of the
5fb3fb4f3Stomee  * Common Development and Distribution License (the "License").
6fb3fb4f3Stomee  * You may not use this file except in compliance with the License.
7fb3fb4f3Stomee  *
8fb3fb4f3Stomee  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9fb3fb4f3Stomee  * or http://www.opensolaris.org/os/licensing.
10fb3fb4f3Stomee  * See the License for the specific language governing permissions
11fb3fb4f3Stomee  * and limitations under the License.
12fb3fb4f3Stomee  *
13fb3fb4f3Stomee  * When distributing Covered Code, include this CDDL HEADER in each
14fb3fb4f3Stomee  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15fb3fb4f3Stomee  * If applicable, add the following below this CDDL HEADER, with the
16fb3fb4f3Stomee  * fields enclosed by brackets "[]" replaced with your own identifying
17fb3fb4f3Stomee  * information: Portions Copyright [yyyy] [name of copyright owner]
18fb3fb4f3Stomee  *
19fb3fb4f3Stomee  * CDDL HEADER END
20fb3fb4f3Stomee  */
21fb3fb4f3Stomee 
22fb3fb4f3Stomee /*
2391cfa10aStomee  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24fb3fb4f3Stomee  * Use is subject to license terms.
25fb3fb4f3Stomee  *
26fb3fb4f3Stomee  * ident	"%Z%%M%	%I%	%E% SMI"
27fb3fb4f3Stomee  */
28fb3fb4f3Stomee package org.opensolaris.os.dtrace;
29fb3fb4f3Stomee 
30fb3fb4f3Stomee import java.io.*;
31fb3fb4f3Stomee import java.util.Arrays;
32fb3fb4f3Stomee import java.beans.*;
33fb3fb4f3Stomee 
34fb3fb4f3Stomee /**
35fb3fb4f3Stomee  * A traced D primitive generated by a DTrace action such as {@code
36fb3fb4f3Stomee  * trace()} or {@code tracemem()}, or else an element in a composite
37fb3fb4f3Stomee  * value generated by DTrace.
38fb3fb4f3Stomee  * <p>
39fb3fb4f3Stomee  * Immutable.  Supports persistence using {@link java.beans.XMLEncoder}.
40fb3fb4f3Stomee  *
41fb3fb4f3Stomee  * @author Tom Erickson
42fb3fb4f3Stomee  */
43fb3fb4f3Stomee public final class ScalarRecord implements ValueRecord, Serializable {
44127bbe13Stomee     static final long serialVersionUID = -6920826443240176724L;
45fb3fb4f3Stomee     static final int RAW_BYTES_INDENT = 5;
46fb3fb4f3Stomee 
47fb3fb4f3Stomee     static {
48fb3fb4f3Stomee 	try {
49fb3fb4f3Stomee 	    BeanInfo info = Introspector.getBeanInfo(ScalarRecord.class);
50fb3fb4f3Stomee 	    PersistenceDelegate persistenceDelegate =
51fb3fb4f3Stomee 		    new DefaultPersistenceDelegate(
52127bbe13Stomee 		    new String[] {"value", "numberOfBytes"})
53fb3fb4f3Stomee 	    {
54fb3fb4f3Stomee 		/*
55fb3fb4f3Stomee 		 * Need to prevent DefaultPersistenceDelegate from using
56fb3fb4f3Stomee 		 * overridden equals() method, resulting in a
57fb3fb4f3Stomee 		 * StackOverFlowError.  Revert to PersistenceDelegate
58fb3fb4f3Stomee 		 * implementation.  See
59fb3fb4f3Stomee 		 * http://forum.java.sun.com/thread.jspa?threadID=
60fb3fb4f3Stomee 		 * 477019&tstart=135
61fb3fb4f3Stomee 		 */
62fb3fb4f3Stomee 		protected boolean
63fb3fb4f3Stomee 		mutatesTo(Object oldInstance, Object newInstance)
64fb3fb4f3Stomee 		{
65fb3fb4f3Stomee 		    return (newInstance != null && oldInstance != null &&
66fb3fb4f3Stomee 			    oldInstance.getClass() == newInstance.getClass());
67fb3fb4f3Stomee 		}
68fb3fb4f3Stomee 	    };
69fb3fb4f3Stomee 	    BeanDescriptor d = info.getBeanDescriptor();
70fb3fb4f3Stomee 	    d.setValue("persistenceDelegate", persistenceDelegate);
71fb3fb4f3Stomee 	} catch (IntrospectionException e) {
72fb3fb4f3Stomee 	    System.out.println(e);
73fb3fb4f3Stomee 	}
74fb3fb4f3Stomee     }
75fb3fb4f3Stomee 
76fb3fb4f3Stomee     /** @serial */
77fb3fb4f3Stomee     private final Object value;
78127bbe13Stomee     /** @serial */
79127bbe13Stomee     private int numberOfBytes;
80fb3fb4f3Stomee 
81fb3fb4f3Stomee     /**
82127bbe13Stomee      * Creates a scalar record with the given DTrace primitive and the
83127bbe13Stomee      * number of bytes used to store the primitive in the native DTrace
84127bbe13Stomee      * buffer.  Since traced 8- and 16-bit integers are promoted (as
85127bbe13Stomee      * unsigned values) to 32-bit integers, it may be important for
86127bbe13Stomee      * output formatting to know the number of bytes used to represent
87127bbe13Stomee      * the primitive before promotion.
88fb3fb4f3Stomee      *
89fb3fb4f3Stomee      * @param v DTrace primitive data value
90127bbe13Stomee      * @param nativeByteCount number of bytes used to store the given
91127bbe13Stomee      * primitive in the native DTrace buffer
92127bbe13Stomee      * @throws NullPointerException if the given value is {@code null}
93127bbe13Stomee      * @throws IllegalArgumentException if the given number of bytes is
94127bbe13Stomee      * not consistent with the given primitive type or is not greater
95127bbe13Stomee      * than zero
96fb3fb4f3Stomee      * @throws ClassCastException if the given value is not a DTrace
97fb3fb4f3Stomee      * primitive type listed as a possible return value of {@link
98fb3fb4f3Stomee      * #getValue()}
99fb3fb4f3Stomee      */
100fb3fb4f3Stomee     public
ScalarRecord(Object v, int nativeByteCount)101127bbe13Stomee     ScalarRecord(Object v, int nativeByteCount)
102fb3fb4f3Stomee     {
103fb3fb4f3Stomee 	value = v;
104127bbe13Stomee 	numberOfBytes = nativeByteCount;
105fb3fb4f3Stomee 	validate();
106fb3fb4f3Stomee     }
107fb3fb4f3Stomee 
10891cfa10aStomee     private final void
validate()109fb3fb4f3Stomee     validate()
110fb3fb4f3Stomee     {
111fb3fb4f3Stomee 	if (value == null) {
112fb3fb4f3Stomee 	    throw new NullPointerException();
113fb3fb4f3Stomee 	}
114127bbe13Stomee 
115fb3fb4f3Stomee 	// Short-circuit-evaluate common cases first
116127bbe13Stomee 	if (value instanceof Integer) {
117127bbe13Stomee 	    switch (numberOfBytes) {
118127bbe13Stomee 		case 1:
119127bbe13Stomee 		case 2:
120127bbe13Stomee 		case 4:
121127bbe13Stomee 		    break;
122127bbe13Stomee 		default:
123127bbe13Stomee 		    throw new IllegalArgumentException(
124127bbe13Stomee 			    "number of bytes is " + numberOfBytes +
125127bbe13Stomee 			    ", expected 1, 2, or 4 for Integer primitive");
126127bbe13Stomee 	    }
127127bbe13Stomee 	} else if (value instanceof Long) {
128127bbe13Stomee 	    if (numberOfBytes != 8) {
129127bbe13Stomee 		throw new IllegalArgumentException(
130127bbe13Stomee 			"number of bytes is " + numberOfBytes +
131127bbe13Stomee 			", expected 8 for Long primitive");
132127bbe13Stomee 	    }
133127bbe13Stomee 	} else if ((value instanceof String) || (value instanceof byte[])) {
134127bbe13Stomee 	    switch (numberOfBytes) {
135127bbe13Stomee 		case 1:
136127bbe13Stomee 		case 2:
137127bbe13Stomee 		case 4:
138127bbe13Stomee 		case 8:
139127bbe13Stomee 		    throw new IllegalArgumentException(
140127bbe13Stomee 			    "number of bytes is " + numberOfBytes +
141127bbe13Stomee 			    ", expected a number other than " +
142127bbe13Stomee 			    "1, 2, 4, or 8 for String or byte-array " +
143127bbe13Stomee 			    "primitive");
144127bbe13Stomee 	    }
145127bbe13Stomee 	} else if (value instanceof Number) {
146127bbe13Stomee 	    if (numberOfBytes <= 0) {
147127bbe13Stomee 		throw new IllegalArgumentException(
148127bbe13Stomee 			"number of bytes is " + numberOfBytes +
149127bbe13Stomee 			", must be greater than zero");
150127bbe13Stomee 	    }
151127bbe13Stomee 	} else {
152127bbe13Stomee 	    throw new ClassCastException(value.getClass().getName() +
153127bbe13Stomee 		    " value is not a D primitive");
154127bbe13Stomee 	}
155fb3fb4f3Stomee     }
156fb3fb4f3Stomee 
157fb3fb4f3Stomee     /**
158fb3fb4f3Stomee      * Gets the traced D primitive value of this record.
159fb3fb4f3Stomee      *
160fb3fb4f3Stomee      * @return a non-null value whose type is one of the following:
161fb3fb4f3Stomee      * <ul>
162fb3fb4f3Stomee      * <li>{@link Number}</li>
163fb3fb4f3Stomee      * <li>{@link String}</li>
164fb3fb4f3Stomee      * <li>byte[]</li>
165fb3fb4f3Stomee      * </ul>
166fb3fb4f3Stomee      */
167fb3fb4f3Stomee     public Object
getValue()168fb3fb4f3Stomee     getValue()
169fb3fb4f3Stomee     {
170fb3fb4f3Stomee 	return value;
171fb3fb4f3Stomee     }
172fb3fb4f3Stomee 
173127bbe13Stomee     /**
174127bbe13Stomee      * Gets the number of bytes used to store the primitive value of
175127bbe13Stomee      * this record in the native DTrace buffer.  Since traced 8- and
176127bbe13Stomee      * 16-bit integers are promoted (as unsigned values) to 32-bit
177127bbe13Stomee      * integers, it may be important for output formatting to know the
178127bbe13Stomee      * number of bytes used to represent the primitive before promotion.
179127bbe13Stomee      *
180127bbe13Stomee      * @return the number of bytes used to store the primitive value
181127bbe13Stomee      * of this record in the native DTrace buffer, guaranteed to be
182127bbe13Stomee      * greater than zero and consisitent with the type of the primitive
183127bbe13Stomee      * value
184127bbe13Stomee      */
185127bbe13Stomee     public int
getNumberOfBytes()186127bbe13Stomee     getNumberOfBytes()
187127bbe13Stomee     {
188127bbe13Stomee 	return numberOfBytes;
189127bbe13Stomee     }
190127bbe13Stomee 
191fb3fb4f3Stomee     /**
192fb3fb4f3Stomee      * Compares the specified object with this record for equality.
193fb3fb4f3Stomee      * Defines equality as having the same value.
194fb3fb4f3Stomee      *
195fb3fb4f3Stomee      * @return {@code true} if and only if the specified object is also
196fb3fb4f3Stomee      * a {@code ScalarRecord} and the values returned by the {@link
197fb3fb4f3Stomee      * #getValue()} methods of both instances are equal, {@code false}
198fb3fb4f3Stomee      * otherwise.  Values are compared using {@link
199fb3fb4f3Stomee      * java.lang.Object#equals(Object o) Object.equals()}, unless they
200fb3fb4f3Stomee      * are arrays of raw bytes, in which case they are compared using
201fb3fb4f3Stomee      * {@link java.util.Arrays#equals(byte[] a, byte[] a2)}.
202fb3fb4f3Stomee      */
203fb3fb4f3Stomee     @Override
204fb3fb4f3Stomee     public boolean
equals(Object o)205fb3fb4f3Stomee     equals(Object o)
206fb3fb4f3Stomee     {
207fb3fb4f3Stomee 	if (o instanceof ScalarRecord) {
208fb3fb4f3Stomee 	    ScalarRecord r = (ScalarRecord)o;
209fb3fb4f3Stomee 	    if (value instanceof byte[]) {
210fb3fb4f3Stomee 		if (r.value instanceof byte[]) {
211fb3fb4f3Stomee 		    byte[] a1 = (byte[])value;
212fb3fb4f3Stomee 		    byte[] a2 = (byte[])r.value;
213fb3fb4f3Stomee 		    return Arrays.equals(a1, a2);
214fb3fb4f3Stomee 		}
215fb3fb4f3Stomee 		return false;
216fb3fb4f3Stomee 	    }
217fb3fb4f3Stomee 	    return value.equals(r.value);
218fb3fb4f3Stomee 	}
219fb3fb4f3Stomee 	return false;
220fb3fb4f3Stomee     }
221fb3fb4f3Stomee 
222fb3fb4f3Stomee     /**
223fb3fb4f3Stomee      * Overridden to ensure that equal instances have equal hashcodes.
224fb3fb4f3Stomee      *
225fb3fb4f3Stomee      * @return {@link java.lang.Object#hashCode()} of {@link
226fb3fb4f3Stomee      * #getValue()}, or {@link java.util.Arrays#hashCode(byte[] a)} if
227fb3fb4f3Stomee      * the value is a raw byte array
228fb3fb4f3Stomee      */
229fb3fb4f3Stomee     @Override
230fb3fb4f3Stomee     public int
hashCode()231fb3fb4f3Stomee     hashCode()
232fb3fb4f3Stomee     {
233fb3fb4f3Stomee 	if (value instanceof byte[]) {
234fb3fb4f3Stomee 	    return Arrays.hashCode((byte[])value);
235fb3fb4f3Stomee 	}
236fb3fb4f3Stomee 	return value.hashCode();
237fb3fb4f3Stomee     }
238fb3fb4f3Stomee 
239fb3fb4f3Stomee     private static final int BYTE_SIGN_BIT = 1 << 7;
240fb3fb4f3Stomee 
241fb3fb4f3Stomee     /**
242fb3fb4f3Stomee      * Static utility for treating a byte as unsigned by converting it
243fb3fb4f3Stomee      * to int without sign extending.
244fb3fb4f3Stomee      */
245fb3fb4f3Stomee     static int
unsignedByte(byte b)246fb3fb4f3Stomee     unsignedByte(byte b)
247fb3fb4f3Stomee     {
248fb3fb4f3Stomee         if (b < 0) {
249fb3fb4f3Stomee 	    b ^= (byte)BYTE_SIGN_BIT;
250fb3fb4f3Stomee 	    return ((int)b) | BYTE_SIGN_BIT;
251fb3fb4f3Stomee 	}
252fb3fb4f3Stomee 	return (int)b;
253fb3fb4f3Stomee     }
254fb3fb4f3Stomee 
255fb3fb4f3Stomee     static String
hexString(int n, int width)256fb3fb4f3Stomee     hexString(int n, int width)
257fb3fb4f3Stomee     {
258fb3fb4f3Stomee 	String s = Integer.toHexString(n);
259fb3fb4f3Stomee 	int len = s.length();
260fb3fb4f3Stomee 	if (width < len) {
261fb3fb4f3Stomee 	    s = s.substring(len - width);
262fb3fb4f3Stomee 	} else if (width > len) {
263fb3fb4f3Stomee 	    s = (spaces(width - len) + s);
264fb3fb4f3Stomee 	}
265fb3fb4f3Stomee 	return s;
266fb3fb4f3Stomee     }
267fb3fb4f3Stomee 
268fb3fb4f3Stomee     static String
spaces(int n)269fb3fb4f3Stomee     spaces(int n)
270fb3fb4f3Stomee     {
271*4ae67516Stomee 	StringBuilder buf = new StringBuilder();
272fb3fb4f3Stomee 	for (int i = 0; i < n; ++i) {
273fb3fb4f3Stomee 	    buf.append(' ');
274fb3fb4f3Stomee 	}
275fb3fb4f3Stomee 	return buf.toString();
276fb3fb4f3Stomee     }
277fb3fb4f3Stomee 
278fb3fb4f3Stomee     /**
279fb3fb4f3Stomee      * Represents a byte array as a table of unsigned byte values in hex,
280fb3fb4f3Stomee      * 16 per row ending in their corresponding character
281fb3fb4f3Stomee      * representations (or a period (&#46;) for each unprintable
282fb3fb4f3Stomee      * character).  Uses default indentation.
283fb3fb4f3Stomee      *
284fb3fb4f3Stomee      * @see ScalarRecord#rawBytesString(byte[] bytes, int indent)
285fb3fb4f3Stomee      */
286fb3fb4f3Stomee     static String
rawBytesString(byte[] bytes)287fb3fb4f3Stomee     rawBytesString(byte[] bytes)
288fb3fb4f3Stomee     {
289fb3fb4f3Stomee 	return rawBytesString(bytes, RAW_BYTES_INDENT);
290fb3fb4f3Stomee     }
291fb3fb4f3Stomee 
292fb3fb4f3Stomee     /**
293fb3fb4f3Stomee      * Represents a byte array as a table of unsigned byte values in hex,
294fb3fb4f3Stomee      * 16 per row ending in their corresponding character
295fb3fb4f3Stomee      * representations (or a period (&#46;) for each unprintable
296fb3fb4f3Stomee      * character).  The table begins and ends with a newline, includes a
297fb3fb4f3Stomee      * header row, and uses a newline at the end of each row.
298fb3fb4f3Stomee      *
299fb3fb4f3Stomee      * @param bytes array of raw bytes treated as unsigned when
300fb3fb4f3Stomee      * converted to hex display
301fb3fb4f3Stomee      * @param indent number of spaces to indent each line of the
302fb3fb4f3Stomee      * returned string
303fb3fb4f3Stomee      * @return table representation of 16 bytes per row as hex and
304fb3fb4f3Stomee      * character values
305fb3fb4f3Stomee      */
306fb3fb4f3Stomee     static String
rawBytesString(byte[] bytes, int indent)307fb3fb4f3Stomee     rawBytesString(byte[] bytes, int indent)
308fb3fb4f3Stomee     {
309fb3fb4f3Stomee 	// ported from libdtrace/common/dt_consume.c dt_print_bytes()
310fb3fb4f3Stomee 	int i, j;
311fb3fb4f3Stomee 	int u;
312*4ae67516Stomee 	StringBuilder buf = new StringBuilder();
313fb3fb4f3Stomee 	String leftMargin = spaces(indent);
314fb3fb4f3Stomee 	buf.append('\n');
315fb3fb4f3Stomee 	buf.append(leftMargin);
316fb3fb4f3Stomee 	buf.append("      ");
317fb3fb4f3Stomee 	for (i = 0; i < 16; i++) {
318fb3fb4f3Stomee 	    buf.append("  ");
319fb3fb4f3Stomee 	    buf.append("0123456789abcdef".charAt(i));
320fb3fb4f3Stomee 	}
321fb3fb4f3Stomee 	buf.append("  0123456789abcdef\n");
322fb3fb4f3Stomee 	int nbytes = bytes.length;
323fb3fb4f3Stomee 	String hex;
324fb3fb4f3Stomee 	for (i = 0; i < nbytes; i += 16) {
325fb3fb4f3Stomee 	    buf.append(leftMargin);
326fb3fb4f3Stomee 	    buf.append(hexString(i, 5));
327fb3fb4f3Stomee 	    buf.append(':');
328fb3fb4f3Stomee 
329fb3fb4f3Stomee 	    for (j = i; (j < (i + 16)) && (j < nbytes); ++j) {
330fb3fb4f3Stomee 		buf.append(hexString(unsignedByte(bytes[j]), 3));
331fb3fb4f3Stomee 	    }
332fb3fb4f3Stomee 
333fb3fb4f3Stomee 	    while ((j++ % 16) != 0) {
334fb3fb4f3Stomee 		buf.append("   ");
335fb3fb4f3Stomee 	    }
336fb3fb4f3Stomee 
337fb3fb4f3Stomee 	    buf.append("  ");
338fb3fb4f3Stomee 
339fb3fb4f3Stomee 	    for (j = i; (j < (i + 16)) && (j < nbytes); ++j) {
340fb3fb4f3Stomee 		u = unsignedByte(bytes[j]);
341fb3fb4f3Stomee 		if ((u < ' ') || (u > '~')) {
342fb3fb4f3Stomee 		    buf.append('.');
343fb3fb4f3Stomee 		} else {
344fb3fb4f3Stomee 		    buf.append((char) u);
345fb3fb4f3Stomee 		}
346fb3fb4f3Stomee 	    }
347fb3fb4f3Stomee 
348fb3fb4f3Stomee 	    buf.append('\n');
349fb3fb4f3Stomee 	}
350fb3fb4f3Stomee 
351fb3fb4f3Stomee 	return buf.toString();
352fb3fb4f3Stomee     }
353fb3fb4f3Stomee 
354fb3fb4f3Stomee     static String
valueToString(Object value)355fb3fb4f3Stomee     valueToString(Object value)
356fb3fb4f3Stomee     {
357fb3fb4f3Stomee 	String s;
358fb3fb4f3Stomee 	if (value instanceof byte[]) {
359fb3fb4f3Stomee 	    s = rawBytesString((byte[])value);
360fb3fb4f3Stomee 	} else {
361fb3fb4f3Stomee 	    s = value.toString();
362fb3fb4f3Stomee 	}
363fb3fb4f3Stomee 	return s;
364fb3fb4f3Stomee     }
365fb3fb4f3Stomee 
366fb3fb4f3Stomee     private void
readObject(ObjectInputStream s)367fb3fb4f3Stomee     readObject(ObjectInputStream s)
368fb3fb4f3Stomee             throws IOException, ClassNotFoundException
369fb3fb4f3Stomee     {
370fb3fb4f3Stomee 	s.defaultReadObject();
371fb3fb4f3Stomee 	// check class invariants
372fb3fb4f3Stomee 	try {
373fb3fb4f3Stomee 	    validate();
374fb3fb4f3Stomee 	} catch (Exception e) {
375*4ae67516Stomee 	    InvalidObjectException x = new InvalidObjectException(
376*4ae67516Stomee 		    e.getMessage());
377*4ae67516Stomee 	    x.initCause(e);
378*4ae67516Stomee 	    throw x;
379fb3fb4f3Stomee 	}
380fb3fb4f3Stomee     }
381fb3fb4f3Stomee 
382fb3fb4f3Stomee     /**
383fb3fb4f3Stomee      * Gets the natural string representation of the traced D primitive.
384fb3fb4f3Stomee      *
385fb3fb4f3Stomee      * @return the value of {@link Object#toString} when called on
386fb3fb4f3Stomee      * {@link #getValue()}; or if the value is an array of raw bytes, a
387fb3fb4f3Stomee      * table displaying 16 bytes per row in unsigned hex followed by the
388fb3fb4f3Stomee      * ASCII character representations of those bytes (each unprintable
389fb3fb4f3Stomee      * character is represented by a period (.))
390fb3fb4f3Stomee      */
391fb3fb4f3Stomee     public String
toString()392fb3fb4f3Stomee     toString()
393fb3fb4f3Stomee     {
394fb3fb4f3Stomee 	return ScalarRecord.valueToString(getValue());
395fb3fb4f3Stomee     }
396fb3fb4f3Stomee }
397