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 2006 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 = -34046471695050108L;
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"})
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 
79     /**
80      * Creates a scalar record with the given DTrace primitive.
81      *
82      * @param v DTrace primitive data value
83      * @throws NullPointerException if the given value is null
84      * @throws ClassCastException if the given value is not a DTrace
85      * primitive type listed as a possible return value of {@link
86      * #getValue()}
87      */
88     public
89     ScalarRecord(Object v)
90     {
91 	value = v;
92 	validate();
93     }
94 
95     private void
96     validate()
97     {
98 	if (value == null) {
99 	    throw new NullPointerException();
100 	}
101 	// Short-circuit-evaluate common cases first
102 	if (!((value instanceof Number) ||
103 		(value instanceof String) ||
104 		(value instanceof byte[]))) {
105 	    throw new ClassCastException("value is not a D primitive");
106         }
107     }
108 
109     /**
110      * Gets the traced D primitive value of this record.
111      *
112      * @return a non-null value whose type is one of the following:
113      * <ul>
114      * <li>{@link Number}</li>
115      * <li>{@link String}</li>
116      * <li>byte[]</li>
117      * </ul>
118      */
119     public Object
120     getValue()
121     {
122 	return value;
123     }
124 
125     /**
126      * Compares the specified object with this record for equality.
127      * Defines equality as having the same value.
128      *
129      * @return {@code true} if and only if the specified object is also
130      * a {@code ScalarRecord} and the values returned by the {@link
131      * #getValue()} methods of both instances are equal, {@code false}
132      * otherwise.  Values are compared using {@link
133      * java.lang.Object#equals(Object o) Object.equals()}, unless they
134      * are arrays of raw bytes, in which case they are compared using
135      * {@link java.util.Arrays#equals(byte[] a, byte[] a2)}.
136      */
137     @Override
138     public boolean
139     equals(Object o)
140     {
141 	if (o instanceof ScalarRecord) {
142 	    ScalarRecord r = (ScalarRecord)o;
143 	    if (value instanceof byte[]) {
144 		if (r.value instanceof byte[]) {
145 		    byte[] a1 = (byte[])value;
146 		    byte[] a2 = (byte[])r.value;
147 		    return Arrays.equals(a1, a2);
148 		}
149 		return false;
150 	    }
151 	    return value.equals(r.value);
152 	}
153 	return false;
154     }
155 
156     /**
157      * Overridden to ensure that equal instances have equal hashcodes.
158      *
159      * @return {@link java.lang.Object#hashCode()} of {@link
160      * #getValue()}, or {@link java.util.Arrays#hashCode(byte[] a)} if
161      * the value is a raw byte array
162      */
163     @Override
164     public int
165     hashCode()
166     {
167 	if (value instanceof byte[]) {
168 	    return Arrays.hashCode((byte[])value);
169 	}
170 	return value.hashCode();
171     }
172 
173     private static final int BYTE_SIGN_BIT = 1 << 7;
174 
175     /**
176      * Static utility for treating a byte as unsigned by converting it
177      * to int without sign extending.
178      */
179     static int
180     unsignedByte(byte b)
181     {
182         if (b < 0) {
183 	    b ^= (byte)BYTE_SIGN_BIT;
184 	    return ((int)b) | BYTE_SIGN_BIT;
185 	}
186 	return (int)b;
187     }
188 
189     static String
190     hexString(int n, int width)
191     {
192 	String s = Integer.toHexString(n);
193 	int len = s.length();
194 	if (width < len) {
195 	    s = s.substring(len - width);
196 	} else if (width > len) {
197 	    s = (spaces(width - len) + s);
198 	}
199 	return s;
200     }
201 
202     static String
203     spaces(int n)
204     {
205 	StringBuffer buf = new StringBuffer();
206 	for (int i = 0; i < n; ++i) {
207 	    buf.append(' ');
208 	}
209 	return buf.toString();
210     }
211 
212     /**
213      * Represents a byte array as a table of unsigned byte values in hex,
214      * 16 per row ending in their corresponding character
215      * representations (or a period (&#46;) for each unprintable
216      * character).  Uses default indentation.
217      *
218      * @see ScalarRecord#rawBytesString(byte[] bytes, int indent)
219      */
220     static String
221     rawBytesString(byte[] bytes)
222     {
223 	return rawBytesString(bytes, RAW_BYTES_INDENT);
224     }
225 
226     /**
227      * Represents a byte array as a table of unsigned byte values in hex,
228      * 16 per row ending in their corresponding character
229      * representations (or a period (&#46;) for each unprintable
230      * character).  The table begins and ends with a newline, includes a
231      * header row, and uses a newline at the end of each row.
232      *
233      * @param bytes array of raw bytes treated as unsigned when
234      * converted to hex display
235      * @param indent number of spaces to indent each line of the
236      * returned string
237      * @return table representation of 16 bytes per row as hex and
238      * character values
239      */
240     static String
241     rawBytesString(byte[] bytes, int indent)
242     {
243 	// ported from libdtrace/common/dt_consume.c dt_print_bytes()
244 	int i, j;
245 	int u;
246 	StringBuffer buf = new StringBuffer();
247 	String leftMargin = spaces(indent);
248 	buf.append('\n');
249 	buf.append(leftMargin);
250 	buf.append("      ");
251 	for (i = 0; i < 16; i++) {
252 	    buf.append("  ");
253 	    buf.append("0123456789abcdef".charAt(i));
254 	}
255 	buf.append("  0123456789abcdef\n");
256 	int nbytes = bytes.length;
257 	String hex;
258 	for (i = 0; i < nbytes; i += 16) {
259 	    buf.append(leftMargin);
260 	    buf.append(hexString(i, 5));
261 	    buf.append(':');
262 
263 	    for (j = i; (j < (i + 16)) && (j < nbytes); ++j) {
264 		buf.append(hexString(unsignedByte(bytes[j]), 3));
265 	    }
266 
267 	    while ((j++ % 16) != 0) {
268 		buf.append("   ");
269 	    }
270 
271 	    buf.append("  ");
272 
273 	    for (j = i; (j < (i + 16)) && (j < nbytes); ++j) {
274 		u = unsignedByte(bytes[j]);
275 		if ((u < ' ') || (u > '~')) {
276 		    buf.append('.');
277 		} else {
278 		    buf.append((char) u);
279 		}
280 	    }
281 
282 	    buf.append('\n');
283 	}
284 
285 	return buf.toString();
286     }
287 
288     static String
289     valueToString(Object value)
290     {
291 	String s;
292 	if (value instanceof byte[]) {
293 	    s = rawBytesString((byte[])value);
294 	} else {
295 	    s = value.toString();
296 	}
297 	return s;
298     }
299 
300     private void
301     readObject(ObjectInputStream s)
302             throws IOException, ClassNotFoundException
303     {
304 	s.defaultReadObject();
305 	// check class invariants
306 	try {
307 	    validate();
308 	} catch (Exception e) {
309 	    throw new InvalidObjectException(e.getMessage());
310 	}
311     }
312 
313     /**
314      * Gets the natural string representation of the traced D primitive.
315      *
316      * @return the value of {@link Object#toString} when called on
317      * {@link #getValue()}; or if the value is an array of raw bytes, a
318      * table displaying 16 bytes per row in unsigned hex followed by the
319      * ASCII character representations of those bytes (each unprintable
320      * character is represented by a period (.))
321      */
322     public String
323     toString()
324     {
325 	return ScalarRecord.valueToString(getValue());
326     }
327 }
328