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 (.) 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 (.) 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