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