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 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 package org.opensolaris.os.dtrace;
27 
28 import java.io.*;
29 import java.beans.*;
30 import java.util.*;
31 
32 /**
33  * A formatted string generated by the DTrace {@code printf()} action.
34  * <p>
35  * Immutable.  Supports persistence using {@link java.beans.XMLEncoder}.
36  *
37  * @author Tom Erickson
38  */
39 public final class PrintfRecord implements Record, Serializable,
40 	Comparable <PrintfRecord> {
41     static final long serialVersionUID = 727237355963977675L;
42 
43     static {
44 	try {
45 	    BeanInfo info = Introspector.getBeanInfo(PrintfRecord.class);
46 	    PersistenceDelegate persistenceDelegate =
47 		    new DefaultPersistenceDelegate(
48 		    new String[] {"records", "formattedString"})
49 	    {
50 		/*
51 		 * Need to prevent DefaultPersistenceDelegate from using
52 		 * overridden equals() method, resulting in a
53 		 * StackOverFlowError.  Revert to PersistenceDelegate
54 		 * implementation.  See
55 		 * http://forum.java.sun.com/thread.jspa?threadID=
56 		 * 477019&tstart=135
57 		 */
58 		protected boolean
59 		mutatesTo(Object oldInstance, Object newInstance)
60 		{
61 		    return (newInstance != null && oldInstance != null &&
62 			    oldInstance.getClass() == newInstance.getClass());
63 		}
64 	    };
65 	    BeanDescriptor d = info.getBeanDescriptor();
66 	    d.setValue("persistenceDelegate", persistenceDelegate);
67 	} catch (IntrospectionException e) {
68 	    System.out.println(e);
69 	}
70     }
71 
72     /** @serial */
73     private List <ValueRecord> records;
74     /** @serial */
75     private String formattedString;
76 
77     // package-level access, called by ProbeData
PrintfRecord()78     PrintfRecord()
79     {
80 	records = new ArrayList <ValueRecord> ();
81     }
82 
83     /**
84      * Creates a record with the unformatted elements passed to the
85      * DTrace {@code printf()} action and the resulting formatted
86      * output.  Supports XML persistence.
87      *
88      * @param v variable number of unformatted elements passed to the
89      * DTrace {@code printf()} action
90      * @param s formatted {@code printf()} output
91      * @throws NullPointerException if the given list or any of its
92      * elements is {@code null}, or if the given formatted string is
93      * {@code null}
94      */
95     public
PrintfRecord(List <ValueRecord> v, String s)96     PrintfRecord(List <ValueRecord> v, String s)
97     {
98 	formattedString = s;
99 	records = new ArrayList <ValueRecord> (v.size());
100 	records.addAll(v);
101 	validate();
102     }
103 
104     private final void
validate()105     validate()
106     {
107 	if (formattedString == null) {
108 	    throw new NullPointerException("formatted string is null");
109 	}
110 	if (records == null) {
111 	    throw new NullPointerException("list of format args is null");
112 	}
113 	for (ValueRecord r : records) {
114 	    if (r == null) {
115 		throw new NullPointerException("format arg is null");
116 	    }
117 	}
118     }
119 
120     /**
121      * Called by ProbeData code to populate record list.
122      *
123      * @throws NullPointerException if o is null
124      */
125     void
addUnformattedElement(ScalarRecord rec)126     addUnformattedElement(ScalarRecord rec)
127     {
128 	records.add(rec);
129     }
130 
131     /**
132      * Gets the formatted string output of the DTrace {@code printf()}
133      * action.
134      *
135      * @return non-null formatted string output of the DTrace {@code
136      * printf()} action
137      */
138     public String
getFormattedString()139     getFormattedString()
140     {
141 	return formattedString;
142     }
143 
144     /**
145      * Package level access; called by ProbeData
146      */
147     void
setFormattedString(String s)148     setFormattedString(String s)
149     {
150 	if (s == null) {
151 	    throw new NullPointerException("formatted string is null");
152 	}
153 	formattedString = s;
154     }
155 
156     /**
157      * Gets the unformatted elements passed to the DTrace {@code
158      * printf()} action after the format string.
159      *
160      * @return non-null, unmodifiable list of unformatted elements
161      * passed to the DTrace {@code printf()} action that generated this
162      * record, in the order they appear in the argument list after the
163      * format string
164      */
165     public List <ValueRecord>
getRecords()166     getRecords()
167     {
168 	return Collections. <ValueRecord> unmodifiableList(records);
169     }
170 
171     /**
172      * Gets the number of DTrace {@code printf()} unformatted elements
173      * (arguments following the format string).  For example, the
174      * following action
175      * <pre><code>
176      *    printf("%s %d\n", "cat", 9);
177      * </code></pre>
178      * generates a {@code PrintfRecord} with a record count of two.
179      *
180      * @return the number of unformatted elements passed to the DTrace
181      * {@code printf()} action that generated this record.
182      */
183     public int
getRecordCount()184     getRecordCount()
185     {
186 	return records.size();
187     }
188 
189     /**
190      * Gets the unformatted element passed to the DTrace {@code
191      * printf()} action at the given offset in the {@code printf()}
192      * argument list after the format string, starting at offset zero
193      * for the first unformatted element.
194      *
195      * @return non-null record representing the unformatted {@code
196      * printf()} element at the given index (using the same order that
197      * they appear in the {@code printf()} argument list)
198      * @throws ArrayIndexOutOfBoundsException if the given index is
199      * out of range (index &lt; 0 || index &gt;= getRecordCount())
200      */
201     public ValueRecord
getRecord(int i)202     getRecord(int i)
203     {
204 	return records.get(i);
205     }
206 
207     /**
208      * Compares the specified object with this {@code PrintfRecord} for
209      * equality. Returns {@code true} if and only if the specified
210      * object is also a {@code PrintfRecord} and both records have the
211      * same formatted string and underlying data elements.
212      *
213      * @return {@code true} if and only if the specified object is also
214      * a {@code PrintfRecord} and both the formatted strings <i>and</i>
215      * the underlying data elements of both records are equal
216      */
217     @Override
218     public boolean
equals(Object o)219     equals(Object o)
220     {
221 	if (o instanceof PrintfRecord) {
222 	    PrintfRecord r = (PrintfRecord)o;
223 	    return (records.equals(r.records) &&
224 		    formattedString.equals(r.formattedString));
225 	}
226 	return false;
227     }
228 
229     /**
230      * Overridden to ensure that equal instances have equal hash codes.
231      */
232     @Override
233     public int
hashCode()234     hashCode()
235     {
236 	int hash = 17;
237 	hash = (37 * hash) + records.hashCode();
238 	hash = (37 * hash) + formattedString.hashCode();
239 	return hash;
240     }
241 
242     /**
243      * Compares the formatted string value of this record with that of
244      * the given record. Note that ordering {@code printf} records by
245      * their string values is incompatible with {@link #equals(Object o)
246      * equals()}, which also checks the underlying data elements for
247      * equality.
248      *
249      * @return a negative number, 0, or a positive number as this
250      * record's formatted string is lexicographically less than, equal
251      * to, or greater than the given record's formatted string
252      */
253     public int
compareTo(PrintfRecord r)254     compareTo(PrintfRecord r)
255     {
256 	return formattedString.compareTo(r.formattedString);
257     }
258 
259     private void
readObject(ObjectInputStream s)260     readObject(ObjectInputStream s)
261             throws IOException, ClassNotFoundException
262     {
263 	s.defaultReadObject();
264 	// Defensively copy record list before validating
265 	if (records == null) {
266 	    throw new InvalidObjectException("record list is null");
267 	}
268 	List <ValueRecord> copy = new ArrayList <ValueRecord> (records.size());
269 	copy.addAll(records);
270 	records = copy;
271 	// check invariants
272 	try {
273 	    validate();
274 	} catch (Exception e) {
275 	    InvalidObjectException x = new InvalidObjectException(
276 		    e.getMessage());
277 	    x.initCause(e);
278 	    throw x;
279 	}
280     }
281 
282     /**
283      * Gets the formatted string output of the DTrace {@code printf()}
284      * action.
285      */
286     public String
toString()287     toString()
288     {
289 	return formattedString;
290     }
291 }
292