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