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