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 2006 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
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
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
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
157     addElement(Object element)
158     {
159 	if (element == null) {
160 	    throw new NullPointerException("tuple element is null at " +
161 		    "index " + elements.size());
162 	}
163 	if (element instanceof ValueRecord) {
164 	    elements.add(ValueRecord.class.cast(element));
165 	} else {
166 	    elements.add(new ScalarRecord(element));
167 	}
168     }
169 
170     /**
171      * Gets a modifiable list of this tuple's elements in the same order
172      * as their corresponding variables in the original D program tuple.
173      * Modifying the returned list has no effect on this tuple.
174      * Supports XML persistence.
175      *
176      * @return a modifiable list of this tuple's elements in the same order
177      * as their corresponding variables in the original D program tuple
178      */
179     public List <ValueRecord>
180     getElements()
181     {
182 	return new ArrayList <ValueRecord> (elements);
183     }
184 
185     /**
186      * Gets a read-only {@code List} view of this tuple.
187      *
188      * @return a read-only {@code List} view of this tuple
189      */
190     public List <ValueRecord>
191     asList()
192     {
193 	return Collections.unmodifiableList(elements);
194     }
195 
196     /**
197      * Gets the number of elements in this tuple.
198      *
199      * @return non-negative element count
200      */
201     public int
202     size()
203     {
204 	return elements.size();
205     }
206 
207     /**
208      * Returns {@code true} if this tuple has no elements.
209      *
210      * @return {@code true} if this tuple has no elements, {@code false}
211      * otherwise
212      * @see Tuple#EMPTY
213      */
214     public boolean
215     isEmpty()
216     {
217 	return elements.isEmpty();
218     }
219 
220     /**
221      * Gets the element at the given tuple index (starting at zero).
222      *
223      * @return non-null tuple element at the given zero-based index
224      */
225     public ValueRecord
226     get(int index)
227     {
228 	return elements.get(index);
229     }
230 
231     /**
232      * Gets an iterator over the elements of this tuple.
233      *
234      * @return an iterator over the elements of this tuple
235      */
236     public Iterator<ValueRecord>
237     iterator()
238     {
239 	return elements.iterator();
240     }
241 
242     /**
243      * Compares the specified object with this {@code Tuple} instance
244      * for equality.  Defines equality as having the same elements in
245      * the same order.
246      *
247      * @return {@code true} if and only if the specified object is of
248      * type {@code Tuple} and both instances have the same size and
249      * equal elements at corresponding tuple indexes
250      */
251     public boolean
252     equals(Object o)
253     {
254 	if (o instanceof Tuple) {
255 	    Tuple t = (Tuple)o;
256 	    return elements.equals(t.elements);
257 	}
258 	return false;
259     }
260 
261     /**
262      * Overridden to ensure that equals instances have equal hash codes.
263      */
264     public int
265     hashCode()
266     {
267 	return elements.hashCode();
268     }
269 
270     // lenient sort does not throw exceptions
271     @SuppressWarnings("unchecked")
272     private int
273     compareObjects(Object o1, Object o2)
274     {
275 	int cmp;
276 	Class c1 = o1.getClass();
277 	Class c2 = o2.getClass();
278 	if (c1.isAssignableFrom(c2) && (o1 instanceof Comparable)) {
279 	    Comparable c = Comparable.class.cast(o1);
280 	    cmp = c.compareTo(c1.cast(o2));
281 	} else {
282 	    // Compare string values.  If matching, compare object class.
283 	    String s1 = o1.toString();
284 	    String s2 = o2.toString();
285 	    cmp = s1.compareTo(s2);
286 	    if (cmp == 0) {
287 		cmp = c1.getName().compareTo(c2.getName());
288 	    }
289 	}
290 	return cmp;
291     }
292 
293     /**
294      * Defines the natural ordering of tuples.  Uses a lenient algorithm
295      * designed not to throw exceptions.  Sorts tuples by the natural
296      * ordering of corresponding elements, starting with the first pair
297      * of corresponding elements and comparing subsequent pairs only
298      * when all previous pairs are equal (as a tie breaker).  If
299      * corresponding elements are not mutually comparable, it compares
300      * the string values of those elements, then if the string values
301      * are equal, it compares the class names of those elements' types.
302      * If all corresponding elements are equal, then the tuple with more
303      * elements sorts higher than the tuple with fewer elements.
304      *
305      * @return a negative integer, zero, or a postive integer as this
306      * tuple is less than, equal to, or greater than the given tuple
307      */
308     public int
309     compareTo(Tuple t)
310     {
311 	int cmp = 0;
312 	int len = size();
313 	int tlen = t.size();
314 	ValueRecord rec;
315 	ValueRecord trec;
316 	Object val;
317 	Object tval;
318 	for (int i = 0; (cmp == 0) && (i < len) && (i < tlen); ++i) {
319 	    rec = get(i);
320 	    trec = t.get(i);
321 	    if (rec instanceof ScalarRecord) {
322 		val = rec.getValue();
323 	    } else {
324 		val = rec;
325 	    }
326 	    if (trec instanceof ScalarRecord) {
327 		tval = trec.getValue();
328 	    } else {
329 		tval = trec;
330 	    }
331 	    cmp = compareObjects(val, tval);
332 	}
333 	if (cmp == 0) {
334 	    cmp = (len < tlen ? -1 : (len > tlen ? 1 : 0));
335 	}
336 	return cmp;
337     }
338 
339     private void
340     readObject(ObjectInputStream s)
341             throws IOException, ClassNotFoundException
342     {
343 	s.defaultReadObject();
344 	// Make a defensive copy of elements
345 	if (elements == null) {
346 	    throw new InvalidObjectException("element list is null");
347 	}
348 	List <ValueRecord> copy = new ArrayList <ValueRecord>
349 		(elements.size());
350 	copy.addAll(elements);
351 	elements = copy;
352 	// check class invariants
353 	for (ValueRecord e : elements) {
354 	    if (e == null) {
355 		throw new InvalidObjectException("null element");
356 	    }
357 	}
358     }
359 
360     /**
361      * Gets a string representation of this tuple's elements in the same
362      * format as that returned by {@link AbstractCollection#toString()}.
363      * The representation, although specified, is subject to change.
364      */
365     public String
366     toString()
367     {
368 	return elements.toString();
369     }
370 }
371