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 2007 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.text.ParseException;
32 import java.io.*;
33 import java.beans.*;
34 
35 /**
36  * A DTrace probe description consists of provider, module, function,
37  * and name.  A single probe description may identify a single DTrace
38  * probe or match multiple probes.  Any field may be wildcarded by
39  * omission (set to null) or set to a glob-style pattern:
40  * <pre>
41  *    *		Matches any string, including the null string
42  *    ?		Matches any single character
43  *    [ ... ]	Matches any one of the enclosed characters. A pair of
44  *    			characters separated by - matches any character
45  *    			between the pair, inclusive. If the first
46  *    			character after the [ is !, any character not
47  *    			enclosed in the set is matched.
48  *    \		Interpret the next character as itself, without any
49  *    			special meaning
50  * </pre>
51  * Immutable.  Supports persistence using {@link java.beans.XMLEncoder}.
52  *
53  * @see Consumer#listProbes(ProbeDescription filter)
54  *
55  * @author Tom Erickson
56  */
57 public final class ProbeDescription implements Serializable,
58         Comparable <ProbeDescription>
59 {
60     static final long serialVersionUID = 5978023304364513667L;
61 
62     /**
63      * Instance with empty provider, module, function, and name fields
64      * matches all DTrace probes on a system.
65      */
66     public static final ProbeDescription EMPTY =
67 	    new ProbeDescription(null, null, null, null);
68 
69     private static final int ID_NONE = -1;
70 
71     /**
72      * Enumerates the provider, module, function, and name fields of a
73      * probe description.
74      */
75     public enum Spec {
76 	/** Probe provider */
77 	PROVIDER,
78 	/** Probe module */
79 	MODULE,
80 	/** Probe function */
81 	FUNCTION,
82 	/** Probe name (unqualified) */
83 	NAME
84     };
85 
86     static {
87 	try {
88 	    BeanInfo info = Introspector.getBeanInfo(ProbeDescription.class);
89 	    PersistenceDelegate persistenceDelegate =
90 		    new DefaultPersistenceDelegate(
91 		    new String[] {"ID", "provider", "module",
92 		    "function", "name"});
93 	    BeanDescriptor d = info.getBeanDescriptor();
94 	    d.setValue("persistenceDelegate", persistenceDelegate);
95 	} catch (IntrospectionException e) {
96 	    System.out.println(e);
97 	}
98     }
99 
100     /** @serial */
101     private int id = ID_NONE; // set by native code
102 
103     /** @serial */
104     private final String provider;
105     /** @serial */
106     private final String module;
107     /** @serial */
108     private final String function;
109     /** @serial */
110     private final String name;
111 
112     /**
113      * Creates a fully qualified probe description from the name given
114      * in the format <i>{@code provider:module:function:name}</i> or
115      * else a probe description that specifies only the unqualified
116      * probe name.
117      *
118      * @param probeName either the fully qualified name in the format
119      * <i>{@code provider:module:function:name}</i> or else (if no colon
120      * is present) the unqualified name interpreted as {@code
121      * :::probeName}
122      * @see ProbeDescription#ProbeDescription(String probeProvider,
123      * String probeModule, String probeFunction, String probeName)
124      * @see ProbeDescription#parse(String s)
125      */
126     public
ProbeDescription(String probeName)127     ProbeDescription(String probeName)
128     {
129 	if ((probeName != null) && (probeName.indexOf(':') >= 0)) {
130 	    ProbeDescription p;
131 	    try {
132 		p = ProbeDescription.parse(probeName);
133 	    } catch (ParseException e) {
134 		p = null;
135 	    }
136 
137 	    if (p == null) {
138 		provider = "";
139 		module = "";
140 		function = "";
141 		name = ((probeName == null) ? "" : probeName);
142 	    } else {
143 		provider = p.provider;
144 		module = p.module;
145 		function = p.function;
146 		name = p.name;
147 	    }
148 	} else {
149 	    provider = "";
150 	    module = "";
151 	    function = "";
152 	    name = ((probeName == null) ? "" : probeName);
153 	}
154     }
155 
156     /**
157      * Creates a probe description that specifies the probe name
158      * qualified only by the function name.
159      *
160      * @see ProbeDescription#ProbeDescription(String probeProvider,
161      * String probeModule, String probeFunction, String probeName)
162      */
163     public
ProbeDescription(String probeFunction, String probeName)164     ProbeDescription(String probeFunction, String probeName)
165     {
166 	this(null, null, probeFunction, probeName);
167     }
168 
169     /**
170      * Creates a probe description that specifies the probe name
171      * qualified by the function name and module name.
172      *
173      * @see ProbeDescription#ProbeDescription(String probeProvider,
174      * String probeModule, String probeFunction, String probeName)
175      */
176     public
ProbeDescription(String probeModule, String probeFunction, String probeName)177     ProbeDescription(String probeModule, String probeFunction,
178 	    String probeName)
179     {
180 	this(null, probeModule, probeFunction, probeName);
181     }
182 
183     /**
184      * Creates a fully qualified probe description.  If no pattern
185      * syntax is used and no field is omitted, the resulting description
186      * matches at most one DTrace probe.
187      *
188      * @param probeProvider provider name, may be null or empty to match
189      * all providers or use pattern syntax to match multiple providers
190      * @param probeModule module name, may be null or empty to match all
191      * modules or use pattern syntax to match multiple modules
192      * @param probeFunction function name, may be null or empty to match
193      * all functions or use pattern syntax to match multiple functions
194      * @param probeName unqualified probe name, may be null or empty to
195      * match all names or use pattern syntax to match multiple names
196      */
197     public
ProbeDescription(String probeProvider, String probeModule, String probeFunction, String probeName)198     ProbeDescription(String probeProvider,
199 	    String probeModule,
200 	    String probeFunction,
201 	    String probeName)
202     {
203 	provider = ((probeProvider == null) ? "" : probeProvider);
204 	module = ((probeModule == null) ? "" : probeModule);
205 	function = ((probeFunction == null) ? "" : probeFunction);
206 	name = ((probeName == null) ? "" : probeName);
207     }
208 
209     /**
210      * Supports XML persistence.
211      */
212     public
ProbeDescription(int probeID, String probeProvider, String probeModule, String probeFunction, String probeName)213     ProbeDescription(int probeID,
214 	    String probeProvider,
215 	    String probeModule,
216 	    String probeFunction,
217 	    String probeName)
218     {
219 	this(probeProvider, probeModule, probeFunction, probeName);
220 	id = probeID;
221     }
222 
223     /**
224      * Generates a probe description from a string in the same format
225      * returned by {@link #toString()}.  Parses the string from right to
226      * left.
227      * <pre><code>
228      * <i>provider:module:function:name</i>
229      * </code></pre>
230      *
231      * @return non-null probe description
232      * @throws ParseException if {@code s} does not have the expected
233      * format.  The error offset is the index of the first unexpected
234      * character encountered starting from the last character and
235      * reading backwards.
236      * @throws NullPointerException if the given string is {@code null}
237      */
238     public static ProbeDescription
parse(String s)239     parse(String s) throws ParseException
240     {
241 	ProbeDescription p;
242 
243 	// StringTokenizer and String.split() do not correctly handle
244 	// the case of consecutive delimiters
245 	List <String> list = new ArrayList <String> ();
246 	int len = s.length();
247 	int npos = len;
248 	char ch;
249 	for (int i = (len - 1); i >= 0; --i) {
250 	    ch = s.charAt(i);
251 	    if (ch == ':') {
252 		list.add(0, s.substring((i + 1), npos));
253 		npos = i;
254 	    }
255 	}
256 	list.add(0, s.substring(0, npos));
257 
258 	switch (list.size()) {
259 	    case 0:
260 		p = EMPTY;
261 		break;
262 	    case 1:
263 		p = new ProbeDescription(list.get(0));
264 		break;
265 	    case 2:
266 		p = new ProbeDescription(list.get(0), list.get(1));
267 		break;
268 	    case 3:
269 		p = new ProbeDescription(list.get(0), list.get(1),
270 			list.get(2));
271 		break;
272 	    case 4:
273 		p = new ProbeDescription(list.get(0), list.get(1),
274 			list.get(2), list.get(3));
275 		break;
276 	    default:
277 		// get error offset (parsing right-to-left)
278 		int offset = (s.length() - 4);
279 		len = list.size();
280 		for (int i = (len - 1); i >= (len - 4); --i) {
281 		    offset -= list.get(i).length();
282 		}
283 		throw new ParseException("Overspecified probe " +
284 			"description: \"" + s + "\"", offset);
285 	}
286 	return p;
287     }
288 
289     /**
290      * Gets the probe ID.
291      *
292      * @return ID generated from a sequence by the native DTrace
293      * library, identifies the probe among all probes on the system
294      */
295     public int
getID()296     getID()
297     {
298 	return id;
299     }
300 
301     /**
302      * Gets the provider name.
303      *
304      * @return non-null provider name, may be an empty string to
305      * indicate omission
306      */
307     public String
getProvider()308     getProvider()
309     {
310 	return provider;
311     }
312 
313     /**
314      * Gets the module name.
315      *
316      * @return non-null module name, may be an empty string to indicate
317      * omission
318      */
319     public String
getModule()320     getModule()
321     {
322 	return module;
323     }
324 
325     /**
326      * Gets the function name.
327      *
328      * @return non-null function name, may be an empty string to
329      * indicate omission
330      */
331     public String
getFunction()332     getFunction()
333     {
334 	return function;
335     }
336 
337     /**
338      * Gets the unqualified probe name.
339      *
340      * @return non-null probe name, may be an empty string to indicate
341      * omission
342      */
343     public String
getName()344     getName()
345     {
346 	return name;
347     }
348 
349     /**
350      * Returns {@code true} if provider, module, function, and name are
351      * all omitted.  An empty probe description matches all DTrace
352      * probes on a system.
353      *
354      * @return {@code true} if all probe fields are omitted, {@code
355      * false} otherwise
356      */
357     public boolean
isEmpty()358     isEmpty()
359     {
360 	if (provider.length() > 0) {
361 	    return false;
362 	}
363 	if (module.length() > 0) {
364 	    return false;
365 	}
366 	if (function.length() > 0) {
367 	    return false;
368 	}
369 	if (name.length() > 0) {
370 	    return false;
371 	}
372 	return true;
373     }
374 
375     /**
376      * Compares the specified object with this probe description for
377      * equality.  Defines equality as having the same fields.  Omitted
378      * fields must be omitted in both instances in order for them to be
379      * equal, but it makes no difference whether {@code null} or empty
380      * string was used to indicate omission.
381      *
382      * @return {@code true} if and only if all corresponding fields of
383      * both probe descriptions are either both omitted (null or empty)
384      * or else equal as defined by {@link String#equals(Object o)
385      * String.equals()}
386      */
387     public boolean
equals(Object o)388     equals(Object o)
389     {
390 	if (o instanceof ProbeDescription) {
391 	    ProbeDescription p = (ProbeDescription)o;
392 	    if ((id == ID_NONE) || (p.id == ID_NONE)) {
393 		return (compareTo(p) == 0);
394 	    } else {
395 		return (id == p.id);
396 	    }
397 	}
398 
399 	return false;
400     }
401 
402     /**
403      * Defines the natural ordering of probe descriptions.  Returns the
404      * natural ordering of the first unequal pair of corresponding
405      * fields (starting with the provider and continuing to the
406      * unqualified name only if all other fields are equal).
407      * Corresponding fields are equal if they are both omitted or both
408      * equal as defined by {@link String#equals(Object o)
409      * String.equals()}.  It makes no difference if {@code null} or
410      * empty string is used to indicate omission.  The behavior is
411      * consistent with the {@link #equals(Object o) equals()} method.
412      *
413      * @return -1, 0, or 1 as this probe description is less than, equal
414      * to, or greater than the given probe description
415      */
416     public int
compareTo(ProbeDescription p)417     compareTo(ProbeDescription p)
418     {
419 	int cmp = 0;
420 	cmp = provider.compareTo(p.provider);
421 	if (cmp == 0) {
422 	    cmp = module.compareTo(p.module);
423 	    if (cmp == 0) {
424 		cmp = function.compareTo(p.function);
425 		if (cmp == 0) {
426 		    cmp = name.compareTo(p.name);
427 		}
428 	    }
429 	}
430 	return (cmp);
431     }
432 
433     /**
434      * Overridden to ensure that equal probe descriptions have equal
435      * hashcodes.
436      */
437     @Override
438     public int
hashCode()439     hashCode()
440     {
441 	int hash = id;
442 	if (hash != ID_NONE) {
443 	    return hash;
444 	}
445 
446 	hash = 17;
447 	hash = (37 * hash) + provider.hashCode();
448 	hash = (37 * hash) + module.hashCode();
449 	hash = (37 * hash) + function.hashCode();
450 	hash = (37 * hash) + name.hashCode();
451 	return hash;
452     }
453 
454     private void
readObject(ObjectInputStream s)455     readObject(ObjectInputStream s)
456             throws IOException, ClassNotFoundException
457     {
458 	s.defaultReadObject();
459 	// check invariants
460 	if (provider == null) {
461 	    throw new InvalidObjectException("provider is null");
462 	}
463 	if (module == null) {
464 	    throw new InvalidObjectException("module is null");
465 	}
466 	if (function == null) {
467 	    throw new InvalidObjectException("function is null");
468 	}
469 	if (name == null) {
470 	    throw new InvalidObjectException("name is null");
471 	}
472     }
473 
474     /**
475      * Gets the string representation of this probe description.  The
476      * format is as follows:
477      * <pre><code>
478      * <i>provider:module:function:name</i>
479      * </code></pre>
480      * Individual fields may be empty, but none of the three delimiting
481      * colons is ever omitted.  If this instance uses pattern matching
482      * syntax to match multiple probes, that syntax is preserved in the
483      * string representation.
484      */
485     public String
toString()486     toString()
487     {
488 	StringBuilder buf = new StringBuilder();
489 	buf.append(provider);
490 	buf.append(':');
491 	buf.append(module);
492 	buf.append(':');
493 	buf.append(function);
494 	buf.append(':');
495 	buf.append(name);
496 	return buf.toString();
497     }
498 }
499