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.util.*;
32 import java.beans.*;
33 import java.util.*;
34 
35 /**
36  * Multi-element key to a value in an {@link Aggregation}.
37  * <p>
38  * Tuple equality is based on the length of each tuple and the equality
39  * of each corresponding element.  The natural ordering of tuples is
40  * based on a lenient comparison designed not to throw exceptions when
41  * corresponding elements are not mutually comparable or the number of
42  * tuple elements differs.
43  * <p>
44  * Immutable.  Supports persistence using {@link java.beans.XMLEncoder}.
45  *
46  * @author Tom Erickson
47  */
48 public final class Tuple implements Serializable, Comparable <Tuple>,
49        Iterable<ValueRecord>
50 {
51     static final long serialVersionUID = 5192674716869462720L;
52 
53     /**
54      * The empty tuple has zero elements and may be used to obtain the
55      * singleton {@link AggregationRecord} of a non-keyed {@link
56      * Aggregation}, such as the one derived from the D statement
57      * <code>&#64;a = count()</code>.  (In D, an aggregation without
58      * square brackets aggregates a single value.)
59      */
60     public static final Tuple EMPTY = new Tuple();
61 
62     static {
63 	try {
64 	    BeanInfo info = Introspector.getBeanInfo(Tuple.class);
65 	    PersistenceDelegate persistenceDelegate =
66 		    new DefaultPersistenceDelegate(
67 		    new String[] {"elements"})
68 	    {
69 		/*
70 		 * Need to prevent DefaultPersistenceDelegate from using
71 		 * overridden equals() method, resulting in a
72 		 * StackOverFlowError.  Revert to PersistenceDelegate
73 		 * implementation.  See
74 		 * http://forum.java.sun.com/thread.jspa?threadID=
75 		 * 477019&tstart=135
76 		 */
77 		protected boolean
78 		mutatesTo(Object oldInstance, Object newInstance)
79 		{
80 		    return (newInstance != null && oldInstance != null &&
81 			    oldInstance.getClass() == newInstance.getClass());
82 		}
83 	    };
84 	    BeanDescriptor d = info.getBeanDescriptor();
85 	    d.setValue("persistenceDelegate", persistenceDelegate);
86 	} catch (IntrospectionException e) {
87 	    System.out.println(e);
88 	}
89     }
90 
91     /** @serial */
92     private java.util.List <ValueRecord> elements;
93 
94     private
Tuple()95     Tuple()
96     {
97 	//
98 	// expected to be a short list (usually one to three elements)
99 	//
100 	elements = new ArrayList <ValueRecord> (4);
101     }
102 
103     /**
104      * Creates a tuple with the given elements in the given order.
105      *
106      * @param tupleElements ordered series of tuple elements
107      * @throws NullPointerException if the given array or any of its
108      * elements is {@code null}
109      */
110     public
Tuple(ValueRecord .... tupleElements)111     Tuple(ValueRecord ... tupleElements)
112     {
113 	this();
114 	if (tupleElements == null) {
115 	    throw new NullPointerException("null array");
116 	}
117 	for (ValueRecord r : tupleElements) {
118 	    if (r == null) {
119 		throw new NullPointerException("null element");
120 	    }
121 	    elements.add(r);
122 	}
123     }
124 
125     /**
126      * Creates a tuple with the given element list in the given list
127      * order.
128      *
129      * @param tupleElements ordered list of tuple elements
130      * @throws NullPointerException if the given list or any of its
131      * elements is {@code null}
132      */
133     public
Tuple(List <ValueRecord> tupleElements)134     Tuple(List <ValueRecord> tupleElements)
135     {
136 	this();
137 	if (tupleElements == null) {
138 	    throw new NullPointerException("element list is null");
139 	}
140 	for (ValueRecord r : tupleElements) {
141 	    if (r == null) {
142 		throw new NullPointerException("null element");
143 	    }
144 	    elements.add(r);
145 	}
146     }
147 
148     /**
149      * Called by native code.
150      *
151      * @throws NullPointerException if element is null
152      * @throws IllegalArgumentException if element is neither a {@link
153      * ValueRecord} nor one of the DTrace primitive types returned by
154      * {@link ScalarRecord#getValue()}
155      */
156     private void
addElement(ValueRecord element)157     addElement(ValueRecord element)
158     {
159 	if (element == null) {
160 	    throw new NullPointerException("tuple element is null at " +
161 		    "index " + elements.size());
162 	}
163 	elements.add(element);
164     }
165 
166     /**
167      * Gets a modifiable list of this tuple's elements in the same order
168      * as their corresponding variables in the original D program tuple.
169      * Modifying the returned list has no effect on this tuple.
170      * Supports XML persistence.
171      *
172      * @return a modifiable list of this tuple's elements in the same order
173      * as their corresponding variables in the original D program tuple
174      */
175     public List <ValueRecord>
getElements()176     getElements()
177     {
178 	return new ArrayList <ValueRecord> (elements);
179     }
180 
181     /**
182      * Gets a read-only {@code List} view of this tuple.
183      *
184      * @return a read-only {@code List} view of this tuple
185      */
186     public List <ValueRecord>
asList()187     asList()
188     {
189 	return Collections. <ValueRecord> unmodifiableList(elements);
190     }
191 
192     /**
193      * Gets the number of elements in this tuple.
194      *
195      * @return non-negative element count
196      */
197     public int
size()198     size()
199     {
200 	return elements.size();
201     }
202 
203     /**
204      * Returns {@code true} if this tuple has no elements.
205      *
206      * @return {@code true} if this tuple has no elements, {@code false}
207      * otherwise
208      * @see Tuple#EMPTY
209      */
210     public boolean
isEmpty()211     isEmpty()
212     {
213 	return elements.isEmpty();
214     }
215 
216     /**
217      * Gets the element at the given tuple index (starting at zero).
218      *
219      * @return non-null tuple element at the given zero-based index
220      */
221     public ValueRecord
get(int index)222     get(int index)
223     {
224 	return elements.get(index);
225     }
226 
227     /**
228      * Gets an iterator over the elements of this tuple.
229      *
230      * @return an iterator over the elements of this tuple
231      */
232     public Iterator<ValueRecord>
iterator()233     iterator()
234     {
235 	return elements.iterator();
236     }
237 
238     /**
239      * Compares the specified object with this {@code Tuple} instance
240      * for equality.  Defines equality as having the same elements in
241      * the same order.
242      *
243      * @return {@code true} if and only if the specified object is of
244      * type {@code Tuple} and both instances have the same size and
245      * equal elements at corresponding tuple indexes
246      */
247     public boolean
equals(Object o)248     equals(Object o)
249     {
250 	if (o instanceof Tuple) {
251 	    Tuple t = (Tuple)o;
252 	    return elements.equals(t.elements);
253 	}
254 	return false;
255     }
256 
257     /**
258      * Overridden to ensure that equals instances have equal hash codes.
259      */
260     public int
hashCode()261     hashCode()
262     {
263 	return elements.hashCode();
264     }
265 
266     // lenient sort does not throw exceptions
267     @SuppressWarnings("unchecked")
268     private static int
compareObjects(Object o1, Object o2)269     compareObjects(Object o1, Object o2)
270     {
271 	int cmp;
272 
273 	if (o1 instanceof Comparable) {
274 	    Class c1 = o1.getClass();
275 	    Class c2 = o2.getClass();
276 	    if (c1.equals(c2)) {
277 		cmp = ProbeData.compareUnsigned(Comparable.class.cast(o1),
278 			Comparable.class.cast(o2));
279 	    } else {
280 		// Compare string values.
281 		String s1 = o1.toString();
282 		String s2 = o2.toString();
283 		cmp = s1.compareTo(s2);
284 	    }
285 	} else if (o1 instanceof byte[] && o2 instanceof byte[]) {
286 	    byte[] a1 = byte[].class.cast(o1);
287 	    byte[] a2 = byte[].class.cast(o2);
288 	    cmp = ProbeData.compareByteArrays(a1, a2);
289 	} else {
290 	    // Compare string values.
291 	    String s1 = o1.toString();
292 	    String s2 = o2.toString();
293 	    cmp = s1.compareTo(s2);
294 	}
295 
296 	return cmp;
297     }
298 
299     /**
300      * Defines the natural ordering of tuples.  Uses a lenient algorithm
301      * designed not to throw exceptions.  Sorts tuples by the natural
302      * ordering of corresponding elements, starting with the first pair
303      * of corresponding elements and comparing subsequent pairs only
304      * when all previous pairs are equal (as a tie breaker).  If
305      * corresponding elements are not mutually comparable, it compares
306      * the string values of those elements.  If all corresponding
307      * elements are equal, then the tuple with more elements sorts
308      * higher than the tuple with fewer elements.
309      *
310      * @return a negative integer, zero, or a postive integer as this
311      * tuple is less than, equal to, or greater than the given tuple
312      * @see Tuple#compare(Tuple t1, Tuple t2, int pos)
313      */
314     public int
compareTo(Tuple t)315     compareTo(Tuple t)
316     {
317 	int cmp = 0;
318 	int len = size();
319 	int tlen = t.size();
320 	for (int i = 0; (cmp == 0) && (i < len) && (i < tlen); ++i) {
321 	    cmp = Tuple.compare(this, t, i);
322 	}
323 	if (cmp == 0) {
324 	    cmp = (len < tlen ? -1 : (len > tlen ? 1 : 0));
325 	}
326 	return cmp;
327     }
328 
329     /**
330      * Compares corresponding tuple elements at the given zero-based
331      * index. Elements are ordered as defined in the native DTrace
332      * library, which treats integer values as unsigned when sorting.
333      *
334      * @param t1 first tuple
335      * @param t2 second tuple
336      * @param pos nth tuple element, starting at zero
337      * @return a negative integer, zero, or a postive integer as the
338      * element in the first tuple is less than, equal to, or greater
339      * than the element in the second tuple
340      * @throws IndexOutOfBoundsException if the given tuple index {@code
341      * pos} is out of range {@code (pos < 0 || pos >= size())} for
342      * either of the given tuples
343      */
344     public static int
compare(Tuple t1, Tuple t2, int pos)345     compare(Tuple t1, Tuple t2, int pos)
346     {
347 	int cmp = 0;
348 	ValueRecord rec1 = t1.get(pos);
349 	ValueRecord rec2 = t2.get(pos);
350 	Object val1;
351 	Object val2;
352 	if (rec1 instanceof ScalarRecord) {
353 	    val1 = rec1.getValue();
354 	} else {
355 	    val1 = rec1;
356 	}
357 	if (rec2 instanceof ScalarRecord) {
358 	    val2 = rec2.getValue();
359 	} else {
360 	    val2 = rec2;
361 	}
362 	cmp = compareObjects(val1, val2);
363 	return (cmp);
364     }
365 
366     private void
readObject(ObjectInputStream s)367     readObject(ObjectInputStream s)
368             throws IOException, ClassNotFoundException
369     {
370 	s.defaultReadObject();
371 	// Make a defensive copy of elements
372 	if (elements == null) {
373 	    throw new InvalidObjectException("element list is null");
374 	}
375 	List <ValueRecord> copy = new ArrayList <ValueRecord>
376 		(elements.size());
377 	copy.addAll(elements);
378 	elements = copy;
379 	// check class invariants
380 	for (ValueRecord e : elements) {
381 	    if (e == null) {
382 		throw new InvalidObjectException("null element");
383 	    }
384 	}
385     }
386 
387     /**
388      * Gets a string representation of this tuple's elements in the same
389      * format as that returned by {@link AbstractCollection#toString()}.
390      * The representation, although specified, is subject to change.
391      */
392     public String
toString()393     toString()
394     {
395 	return elements.toString();
396     }
397 }
398