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.util.*;
31 import java.io.*;
32 import java.beans.*;
33 
34 /**
35  * A single key-value pair in a DTrace aggregation.
36  * <p>
37  * Immutable.  Supports persistence using {@link java.beans.XMLEncoder}.
38  *
39  * @see Aggregation
40  * @author Tom Erickson
41  */
42 public final class AggregationRecord implements Record, Serializable {
43     static final long serialVersionUID = -4367614268579702616L;
44 
45     static {
46 	try {
47 	    BeanInfo info = Introspector.getBeanInfo(AggregationRecord.class);
48 	    PersistenceDelegate persistenceDelegate =
49 		    new DefaultPersistenceDelegate(
50 		    new String[] {"tuple", "value", "ordinal"})
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 Tuple tuple;
76     /** @serial */
77     private AggregationValue value;
78     /** @serial */
79     private int ordinal;
80 
81 
82     /**
83      * Creates an aggregation record with the given key and value.
84      * Supports XML persistence.
85      *
86      * @see #AggregationRecord(Tuple tupleKey, AggregationValue
87      * recordValue, int n)
88      */
89     public
90     AggregationRecord(Tuple tupleKey, AggregationValue recordValue)
91     {
92     //
93     // Called by native code, but public to support backwardly
94     // compatible XML encoding.
95     //
96 	tuple = tupleKey;
97 	value = recordValue;
98 	validate();
99     }
100 
101     /**
102      * Creates an aggregation record with the given key, value, and
103      * ordinal. Supports XML persistence.
104      *
105      * @param tupleKey aggregation tuple, may be empty (see {@link
106      * Tuple#EMPTY}) to indicate that this record's value belongs to an
107      * unkeyed aggregation declared without square brackets, for
108      * example: <pre>		{@code @a = count();}</pre>
109      * @param recordValue aggregated value associated with the given
110      * tuple
111      * @param n ordinal from zero (first) to n-1 (last) within the
112      * {@link Aggregate} containing this record
113      * @throws NullPointerException if the given key or value is
114      * {@code null}
115      * @throws IllegalArgumentException if the given ordinal is negative
116      */
117     public
118     AggregationRecord(Tuple tupleKey, AggregationValue recordValue, int n)
119     {
120 	tuple = tupleKey;
121 	value = recordValue;
122 	ordinal = n;
123 	validate();
124     }
125 
126     private final void
127     validate()
128     {
129 	if (tuple == null) {
130 	    throw new NullPointerException("key is null");
131 	}
132 	if (value == null) {
133 	    throw new NullPointerException("value is null");
134 	}
135 	if (ordinal < 0) {
136 	    throw new IllegalArgumentException("ordinal is negative");
137 	}
138     }
139 
140     /**
141      * Gets the multi-element key associated with {@link
142      * #getValue()}.
143      *
144      * @return non-null, possibly empty tuple
145      * @see Aggregation#getRecord(Tuple key)
146      */
147     public Tuple
148     getTuple()
149     {
150 	return tuple;
151     }
152 
153     /**
154      * Gets the value associated with {@link #getTuple()}.  Values
155      * generated by the DTrace actions {@code count()}, {@code sum()},
156      * {@code avg()}, {@code min()}, and {@code max()} are of type
157      * {@link Long}.  Values generated by the DTrace actions {@code
158      * quantize(}) and {@code lquantize()} are of type {@link
159      * Distribution}.
160      *
161      * @return non-null value keyed to {@link #getTuple()}
162      */
163     public AggregationValue
164     getValue()
165     {
166 	return value;
167     }
168 
169     /**
170      * Gets the ordinal generated when this AggregationRecord was added
171      * to its containing {@link Aggregate} by the native DTrace library,
172      * from zero (first) to n-1 (last). The sequence described by an
173      * aggregate's record ordinals reflects the setting of the {@link
174      * Option#aggsortkey aggsortkey}, {@link Option#aggsortkeypos
175      * aggsortkeypos}, {@link Option#aggsortpos aggsortpos}, and {@link
176      * Option#aggsortrev aggsortrev} DTrace options and matches the way
177      * that the records would be ordered by {@code dtrace(1M)}.
178      *
179      * @return non-negative ordinal from zero (first) to n-1 (last)
180      * within the {@code Aggregate} containing this record
181      * @see Aggregate#getOrderedRecords()
182      */
183     public int
184     getOrdinal()
185     {
186 	return ordinal;
187     }
188 
189     /**
190      * Package level access; called by Aggregate
191      */
192     void
193     setOrdinal(int n)
194     {
195 	if (n < 0) {
196 	    throw new IllegalArgumentException("ordinal is negative");
197 	}
198 	ordinal = n;
199     }
200 
201     /**
202      * Compares the specified object with this aggregation record for
203      * equality.  Defines equality as having the same tuple and value.
204      *
205      * @return {@code true} if and only if the specified object is an
206      * {@code AggregationRecord} and both records have equal tuples as
207      * defined by {@link Tuple#equals(Object o)} and equal values as
208      * defined by the implementation of {@link AggregationValue}
209      */
210     public boolean
211     equals(Object o)
212     {
213 	if (o instanceof AggregationRecord) {
214 	    AggregationRecord r = (AggregationRecord)o;
215 	    return (tuple.equals(r.tuple) &&
216 		    value.equals(r.value));
217 	}
218 	return false;
219     }
220 
221     /**
222      * Overridden to ensure that equal records have equal hash codes.
223      */
224     @Override
225     public int
226     hashCode()
227     {
228 	int hash = 17;
229 	hash = (37 * hash) + tuple.hashCode();
230 	hash = (37 * hash) + value.hashCode();
231 	return hash;
232     }
233 
234     private void
235     readObject(ObjectInputStream s)
236             throws IOException, ClassNotFoundException
237     {
238 	s.defaultReadObject();
239 	// Check class invariants
240 	try {
241 	    validate();
242 	} catch (Exception e) {
243 	    InvalidObjectException x = new InvalidObjectException(
244 		    e.getMessage());
245 	    x.initCause(e);
246 	    throw x;
247 	}
248     }
249 
250     /**
251      * Gets a string representation of this aggregation record useful
252      * for logging and not intended for display.  The exact details of
253      * the representation are unspecified and subject to change, but the
254      * following format may be regarded as typical:
255      * <pre><code>
256      * class-name[property1 = value1, property2 = value2]
257      * </code></pre>
258      */
259     @Override
260     public String
261     toString()
262     {
263 	StringBuilder buf = new StringBuilder();
264 	buf.append(AggregationRecord.class.getName());
265 	buf.append("[tuple = ");
266 	buf.append(tuple);
267 	buf.append(", value = ");
268 	buf.append(value);
269 	buf.append(']');
270 	return buf.toString();
271     }
272 }
273