Print this page
4719 Common patchset for jdk1.7 support preparation
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/LocalConsumer.java
+++ new/usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/LocalConsumer.java
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
13 13 * When distributing Covered Code, include this CDDL HEADER in each
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21
22 22 /*
23 23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
24 24 * Use is subject to license terms.
25 25 *
26 26 * ident "%Z%%M% %I% %E% SMI"
27 27 */
28 28 package org.opensolaris.os.dtrace;
29 29
30 30 import java.io.*;
31 31 import java.util.*;
32 32 import java.net.InetAddress;
33 33 import java.net.UnknownHostException;
34 34 import javax.swing.event.EventListenerList;
35 35 import java.util.logging.*;
36 36
37 37 /**
38 38 * Interface to the native DTrace library, each instance is a single
39 39 * DTrace consumer.
40 40 *
41 41 * @author Tom Erickson
42 42 */
43 43 public class LocalConsumer implements Consumer {
44 44 //
45 45 // Implementation notes:
46 46 //
47 47 // libdtrace is *not* thread-safe. You cannot make multiple calls
48 48 // into it simultaneously from different threads, even if those
49 49 // threads are operating on different dtrace_hdl_t's. Calls to
50 50 // libdtrace are synchronized on a global lock, LocalConsumer.class.
51 51
52 52 static Logger logger = Logger.getLogger(LocalConsumer.class.getName());
53 53
54 54 // Needs to match the version in dtrace_jni.c
55 55 private static final int DTRACE_JNI_VERSION = 3;
56 56
57 57 private static final Option[] DEFAULT_OPTIONS = new Option[] {
58 58 new Option(Option.bufsize, Option.kb(256)),
59 59 new Option(Option.aggsize, Option.kb(256)),
60 60 };
61 61
62 62 private static native void _loadJniTable();
63 63
64 64 // Undocumented configuration options
65 65 private static boolean debug;
66 66 private static int maxConsumers;
67 67
68 68 static {
69 69 LocalConsumer.configureLogging();
70 70 // Undocumented configuration options settable using
71 71 // java -Doption=value
72 72 LocalConsumer.getConfigurationOptions();
73 73
74 74 Utility.loadLibrary("libdtrace_jni.so.1", debug);
75 75
76 76 _checkVersion(DTRACE_JNI_VERSION);
77 77 _setDebug(debug);
78 78 if (maxConsumers > 0) {
79 79 _setMaximumConsumers(maxConsumers);
80 80 }
81 81
82 82 //
83 83 // Last of all in case configuration options affect the loading
84 84 // of the JNI table.
85 85 //
86 86 _loadJniTable();
87 87 }
88 88
89 89 // Native JNI interface (see lib/libdtrace_jni/dtrace_jni.c)
90 90 private static native void _checkVersion(int version);
91 91 private native void _open(OpenFlag[] flags) throws DTraceException;
92 92 private native Program _compileString(String program, String[] args)
93 93 throws DTraceException;
94 94 private native Program.File _compileFile(String path, String[] args)
95 95 throws DTraceException;
96 96 private native void _exec(Program program) throws DTraceException;
97 97 private native void _getProgramInfo(Program program)
98 98 throws DTraceException;
99 99 private native void _setOption(String option, String value)
100 100 throws DTraceException;
101 101 private native long _getOption(String option) throws DTraceException;
102 102 private native boolean _isEnabled();
103 103 private native void _checkProgramEnabling();
104 104 private native void _go() throws DTraceException;
105 105 private native void _stop() throws DTraceException;
106 106 private native void _consume() throws DTraceException;
107 107 private native void _interrupt();
108 108 private native void _close();
109 109 private native Aggregate _getAggregate(AggregateSpec spec)
110 110 throws DTraceException;
111 111 private native int _createProcess(String cmd) throws DTraceException;
112 112 private native void _grabProcess(int pid) throws DTraceException;
113 113 private native void _listProbes(List <ProbeDescription> probeList,
114 114 ProbeDescription filter);
115 115 private native void _listProbeDetail(List <Probe> probeList,
116 116 ProbeDescription filter);
117 117 private native void _listCompiledProbes(
118 118 List <ProbeDescription> probeList, Program program);
119 119 private native void _listCompiledProbeDetail(
120 120 List <Probe> probeList, Program program);
121 121 private static native String _getVersion();
122 122 private static native int _openCount();
123 123 //
124 124 // Releases memory held in the JNI layer after dtrace_close() has
125 125 // released critical system resources like file descriptors, and
126 126 // calls to libdtrace are no longer needed (or possible).
127 127 //
128 128 private native void _destroy();
129 129 // Called by LogDistribution
130 130 static native long _quantizeBucket(int i);
131 131 //
132 132 // Cannot be static because the necessary dtrace handle is specific
133 133 // to this Consumer.
134 134 //
135 135 private native String _lookupKernelFunction(Number address);
136 136 private native String _lookupUserFunction(int pid, Number address);
137 137 private static native String _getExecutableName();
138 138
139 139 // Undocumented configuration options
140 140 private static native void _setMaximumConsumers(int max);
141 141 private static native void _setDebug(boolean debug);
142 142
143 143 protected EventListenerList listenerList;
144 144 protected ExceptionHandler exceptionHandler;
145 145
146 146 private int _handle = -1; // native C identifier (do not modify)
147 147 private final Identifier id; // java identifier
148 148
149 149 private enum State {
150 150 INIT,
151 151 OPEN,
152 152 COMPILED,
153 153 GO,
154 154 STARTED,
155 155 STOPPED,
156 156 CLOSED
157 157 }
158 158
159 159 private State state = State.INIT;
160 160 private boolean stopCalled;
161 161 private boolean abortCalled;
162 162
163 163 //
164 164 // Per-consumer lock used in native code to prevent conflict between
165 165 // the native consumer loop and the getAggregate() thread without
166 166 // locking this LocalConsumer. A distinct per-consumer lock allows
167 167 // the stop() method to be synchronized without causing deadlock
168 168 // when the consumer loop grabs the per-consumer lock before
169 169 // dtrace_work().
170 170 //
171 171 private Object consumerLock;
172 172
173 173 //
174 174 // stopLock is a synchronization lock used to ensure that the stop()
175 175 // method does not return until this consumer has actually stopped.
176 176 // Correct lock ordering is needed to ensure that listeners cannot
177 177 // deadlock this consumer:
178 178 // 1. stop() grabs the lock on this consumer before determining if
179 179 // this consumer is running (to ensure valid state).
180 180 // 2. Once stop() determines that this consumer is actually running,
181 181 // it releases the lock on this consumer. Failing to release the
182 182 // lock makes it possible for a ConsumerListener to deadlock this
183 183 // consumer by calling any synchronized LocalConcumer method
184 184 // (because the listener called by the worker thread prevents the
185 185 // worker thread from finishing while it waits for stop() to
186 186 // release the lock, which it will never do until the worker
187 187 // thread finishes).
188 188 // 3. stop() interrupts this consumer and grabs the stopLock, then
189 189 // waits on the stopLock for this consumer to stop (i.e. for the
190 190 // worker thread to finish).
191 191 // 4. The interrupted worker thread grabs the stopLock when it
192 192 // finishes so it can notify waiters on the stopLock (in this
193 193 // case the stop() method) that the worker thread is finished.
194 194 // The workEnded flag (whose access is protected by the
195 195 // stopLock), is used in case the interrupted worker thread
196 196 // finishes and grabs the stopLock before the stop() method does.
197 197 // Setting the flag in that case tells the stop() method it has
198 198 // nothing to wait for (otherwise stop() would wait forever,
199 199 // since there is no one left after the worker thread finishes to
200 200 // notify the stop() method to stop waiting).
201 201 // 5. The worker thread updates the state member to STOPPED and
202 202 // notifies listeners while it holds the stopLock and before it
203 203 // notifies waiters on the stopLock. This is to ensure that
204 204 // state has been updated to STOPPED and that listeners have
205 205 // executed consumerStopped() before the stop() method returns,
206 206 // to ensure valid state and in case the caller of stop() is
207 207 // relying on anything having been done by consumerStopped()
208 208 // before it proceeds to the next statement.
209 209 // 6. The worker thread notifies waiters on the stopLock before
210 210 // releasing it. stop() returns.
211 211 //
212 212 private Object stopLock;
213 213 private boolean workEnded;
214 214
215 215 private static int sequence = 0;
216 216
217 217 private static void
218 218 configureLogging()
219 219 {
220 220 logger.setUseParentHandlers(false);
221 221 Handler handler = new ConsoleHandler();
222 222 handler.setLevel(Level.ALL);
223 223 logger.addHandler(handler);
224 224 logger.setLevel(Level.OFF);
225 225 }
226 226
227 227 private static Integer
228 228 getIntegerProperty(String name)
229 229 {
230 230 Integer value = null;
231 231 String property = System.getProperty(name);
232 232 if (property != null && property.length() != 0) {
233 233 try {
234 234 value = Integer.parseInt(property);
235 235 System.out.println(name + "=" + value);
236 236 } catch (NumberFormatException e) {
237 237 System.err.println("Warning: property ignored: " +
238 238 name + "=" + property);
239 239 }
240 240 }
241 241 return value;
242 242 }
243 243
244 244 private static void
245 245 getConfigurationOptions()
246 246 {
247 247 Integer property;
248 248 property = getIntegerProperty("JAVA_DTRACE_API_DEBUG");
249 249 if (property != null) {
250 250 debug = (property != 0);
251 251 }
252 252 property = getIntegerProperty("JAVA_DTRACE_MAX_CONSUMERS");
253 253 if (property != null) {
254 254 maxConsumers = property;
255 255 }
256 256 }
257 257
258 258 /**
259 259 * Creates a consumer that interacts with the native DTrace library
260 260 * on the local system.
261 261 */
262 262 public
263 263 LocalConsumer()
264 264 {
265 265 id = new LocalConsumer.Identifier(this);
266 266 consumerLock = new Object();
267 267 stopLock = new Object();
268 268 listenerList = new EventListenerList();
269 269 }
270 270
271 271 /**
272 272 * Called by native C code only
273 273 */
274 274 private int
275 275 getHandle()
276 276 {
277 277 return _handle;
278 278 }
279 279
280 280 /**
281 281 * Called by native C code only
282 282 */
283 283 private void
284 284 setHandle(int n)
285 285 {
286 286 _handle = n;
287 287 }
288 288
289 289 public synchronized void
290 290 open(OpenFlag ... flags) throws DTraceException
291 291 {
292 292 if (state == State.CLOSED) {
293 293 throw new IllegalStateException("cannot reopen a closed consumer");
294 294 }
295 295 if (state != State.INIT) {
296 296 throw new IllegalStateException("consumer already open");
297 297 }
298 298
299 299 for (OpenFlag flag : flags) {
300 300 if (flag == null) {
301 301 throw new NullPointerException("open flag is null");
302 302 }
303 303 }
304 304
305 305 synchronized (LocalConsumer.class) {
306 306 _open(flags);
307 307 }
308 308
309 309 state = State.OPEN;
310 310 setOptions(DEFAULT_OPTIONS);
311 311
312 312 if (abortCalled) {
313 313 _interrupt();
314 314 }
315 315
316 316 if (logger.isLoggable(Level.INFO)) {
317 317 logger.info("consumer table count: " + _openCount());
318 318 }
319 319 }
320 320
321 321 private synchronized void
322 322 checkCompile()
323 323 {
324 324 switch (state) {
325 325 case INIT:
326 326 throw new IllegalStateException("consumer not open");
327 327 case OPEN:
328 328 case COMPILED: // caller may compile more than one program
329 329 break;
330 330 case GO:
331 331 case STARTED:
332 332 throw new IllegalStateException("go() already called");
333 333 case STOPPED:
334 334 throw new IllegalStateException("consumer stopped");
335 335 case CLOSED:
336 336 throw new IllegalStateException("consumer closed");
337 337 }
338 338 }
339 339
340 340 public synchronized Program
341 341 compile(String program, String ... macroArgs) throws DTraceException
342 342 {
343 343 if (program == null) {
344 344 throw new NullPointerException("program string is null");
345 345 }
346 346 checkCompile();
347 347 Program p = null;
348 348
349 349 String[] argv = null;
350 350 if (macroArgs != null) {
351 351 for (String macroArg : macroArgs) {
352 352 if (macroArg == null) {
353 353 throw new NullPointerException("macro argument is null");
354 354 }
355 355 }
356 356 argv = new String[macroArgs.length + 1];
357 357 synchronized (LocalConsumer.class) {
358 358 //
359 359 // Could be an application with an embedded JVM, not
360 360 // necessarily "java".
361 361 //
362 362 argv[0] = _getExecutableName();
363 363 }
364 364 System.arraycopy(macroArgs, 0, argv, 1, macroArgs.length);
365 365 } else {
366 366 synchronized (LocalConsumer.class) {
367 367 argv = new String[] { _getExecutableName() };
368 368 }
369 369 }
370 370 synchronized (LocalConsumer.class) {
371 371 p = _compileString(program, argv);
372 372 }
373 373 p.consumerID = id;
374 374 p.contents = program;
375 375 p.validate();
376 376 state = State.COMPILED;
377 377
378 378 return p;
379 379 }
380 380
381 381 public synchronized Program
382 382 compile(File program, String ... macroArgs) throws DTraceException,
383 383 IOException, SecurityException
384 384 {
385 385 if (program == null) {
386 386 throw new NullPointerException("program file is null");
387 387 }
388 388 if (!program.canRead()) {
389 389 throw new FileNotFoundException("failed to open " +
390 390 program.getName());
391 391 }
392 392 checkCompile();
393 393 Program.File p = null;
394 394
395 395 String[] argv = null;
396 396 if (macroArgs != null) {
397 397 for (String macroArg : macroArgs) {
398 398 if (macroArg == null) {
399 399 throw new NullPointerException("macro argument is null");
400 400 }
401 401 }
402 402 argv = new String[macroArgs.length + 1];
403 403 argv[0] = program.getPath();
404 404 System.arraycopy(macroArgs, 0, argv, 1, macroArgs.length);
405 405 } else {
406 406 macroArgs = new String[] { program.getPath() };
407 407 }
408 408 synchronized (LocalConsumer.class) {
409 409 p = _compileFile(program.getPath(), argv);
410 410 }
411 411 p.consumerID = id;
412 412 p.contents = Program.getProgramString(program);
413 413 p.file = program;
414 414 p.validate();
415 415 p.validateFile();
416 416 state = State.COMPILED;
417 417
418 418 return p;
419 419 }
420 420
421 421 private synchronized void
422 422 checkProgram(Program program)
423 423 {
424 424 if (program == null) {
425 425 throw new NullPointerException("program is null");
426 426 }
427 427 if (!id.equals(program.consumerID)) {
428 428 throw new IllegalArgumentException("program not compiled " +
429 429 "by this consumer");
430 430 }
431 431 }
432 432
433 433 public void
434 434 enable() throws DTraceException
435 435 {
436 436 enable(null);
437 437 }
438 438
439 439 public synchronized void
440 440 enable(Program program) throws DTraceException
441 441 {
442 442 switch (state) {
443 443 case INIT:
444 444 throw new IllegalStateException("consumer not open");
445 445 case OPEN:
446 446 throw new IllegalStateException("no compiled program");
447 447 case COMPILED:
448 448 break;
449 449 case GO:
450 450 case STARTED:
451 451 throw new IllegalStateException("go() already called");
452 452 case STOPPED:
453 453 throw new IllegalStateException("consumer stopped");
454 454 case CLOSED:
455 455 throw new IllegalStateException("consumer closed");
456 456 }
457 457
458 458 // Compile all programs if null
459 459 if (program != null) {
460 460 checkProgram(program);
461 461 }
462 462
463 463 //
464 464 // Left to native code to throw IllegalArgumentException if the
465 465 // program is already enabled, since only the native code knows
466 466 // the enabled state.
467 467 //
468 468 synchronized (LocalConsumer.class) {
469 469 _exec(program);
470 470 }
471 471 }
472 472
473 473 public synchronized void
474 474 getProgramInfo(Program program) throws DTraceException
475 475 {
476 476 checkProgram(program);
477 477 if (state == State.CLOSED) {
478 478 throw new IllegalStateException("consumer closed");
479 479 }
480 480
481 481 //
482 482 // The given program was compiled by this consumer, so we can
483 483 // assert the following:
484 484 //
485 485 assert ((state != State.INIT) && (state != State.OPEN));
486 486
487 487 synchronized (LocalConsumer.class) {
488 488 _getProgramInfo(program);
489 489 }
490 490 }
491 491
492 492 private void
493 493 setOptions(Option[] options) throws DTraceException
494 494 {
495 495 for (Option o : options) {
496 496 setOption(o.getName(), o.getValue());
497 497 }
498 498 }
499 499
500 500 public void
501 501 setOption(String option) throws DTraceException
502 502 {
503 503 setOption(option, Option.VALUE_SET);
504 504 }
505 505
506 506 public void
507 507 unsetOption(String option) throws DTraceException
508 508 {
509 509 setOption(option, Option.VALUE_UNSET);
510 510 }
511 511
512 512 public synchronized void
513 513 setOption(String option, String value) throws DTraceException
514 514 {
515 515 if (option == null) {
516 516 throw new NullPointerException("option is null");
517 517 }
518 518 if (value == null) {
519 519 throw new NullPointerException("option value is null");
520 520 }
521 521
522 522 switch (state) {
523 523 case INIT:
524 524 throw new IllegalStateException("consumer not open");
525 525 case OPEN:
526 526 case COMPILED:
527 527 case GO:
528 528 case STARTED: // Some options can be set on a running consumer
529 529 case STOPPED: // Allowed (may affect getAggregate())
530 530 break;
531 531 case CLOSED:
532 532 throw new IllegalStateException("consumer closed");
533 533 }
534 534
535 535 synchronized (LocalConsumer.class) {
536 536 _setOption(option, value);
537 537 }
538 538 }
539 539
540 540 public synchronized long
541 541 getOption(String option) throws DTraceException
542 542 {
543 543 if (option == null) {
544 544 throw new NullPointerException("option is null");
545 545 }
546 546
547 547 switch (state) {
548 548 case INIT:
549 549 throw new IllegalStateException("consumer not open");
550 550 case OPEN:
551 551 case COMPILED:
552 552 case GO:
553 553 case STARTED:
554 554 case STOPPED:
555 555 break;
556 556 case CLOSED:
557 557 throw new IllegalStateException("consumer closed");
558 558 }
559 559
560 560 long value;
561 561 synchronized (LocalConsumer.class) {
562 562 value = _getOption(option);
563 563 }
564 564 return value;
565 565 }
566 566
567 567 public final synchronized boolean
568 568 isOpen()
569 569 {
570 570 return ((state != State.INIT) && (state != State.CLOSED));
571 571 }
572 572
573 573 public final synchronized boolean
574 574 isEnabled()
575 575 {
576 576 if (state != State.COMPILED) {
577 577 return false;
578 578 }
579 579
580 580 return _isEnabled();
581 581 }
582 582
583 583 public final synchronized boolean
584 584 isRunning()
585 585 {
586 586 return (state == State.STARTED);
587 587 }
588 588
589 589 public final synchronized boolean
590 590 isClosed()
591 591 {
592 592 return (state == State.CLOSED);
593 593 }
594 594
595 595 /**
596 596 * Called in the runnable target of the thread returned by {@link
597 597 * #createThread()} to run this DTrace consumer.
598 598 *
599 599 * @see #createThread()
600 600 */
601 601 protected final void
602 602 work()
603 603 {
604 604 try {
605 605 synchronized (this) {
606 606 if (state != State.GO) {
607 607 //
608 608 // stop() was called after go() but before the
609 609 // consumer started
610 610 //
611 611 return; // executes finally block before returning
612 612 }
613 613
614 614 state = State.STARTED;
615 615 fireConsumerStarted(new ConsumerEvent(this,
616 616 System.nanoTime()));
617 617 }
618 618
619 619 //
620 620 // We should not prevent other consumers from running
621 621 // concurrently while this consumer blocks on the native
622 622 // consumer loop. Instead, native code will acquire the
623 623 // LocalConsumer.class monitor as needed before calling
624 624 // libdtrace functions.
625 625 //
626 626 _consume();
627 627
628 628 } catch (Throwable e) {
629 629 if (exceptionHandler != null) {
↓ open down ↓ |
629 lines elided |
↑ open up ↑ |
630 630 exceptionHandler.handleException(e);
631 631 } else {
632 632 e.printStackTrace();
633 633 }
634 634 } finally {
635 635 synchronized (stopLock) {
636 636 // Notify listeners while holding stopLock to guarantee
637 637 // that listeners finish executing consumerStopped()
638 638 // before the stop() method returns.
639 639 synchronized (this) {
640 - if (state == State.STOPPED || state == state.CLOSED) {
640 + if (state == State.STOPPED || state == State.CLOSED) {
641 641 //
642 642 // This consumer was stopped just after calling
643 643 // go() but before starting (the premature return
644 644 // case at the top of this work() method). It is
645 645 // possible to call close() on a consumer that has
646 646 // been stopped before starting. In that case the
647 647 // premature return above still takes us here in the
648 648 // finally clause, and we must not revert the CLOSED
649 649 // state to STOPPED.
650 650 //
651 651 } else {
652 652 state = State.STOPPED;
653 653 fireConsumerStopped(new ConsumerEvent(this,
654 654 System.nanoTime()));
655 655 }
656 656 }
657 657
658 658 // Notify the stop() method to stop waiting
659 659 workEnded = true;
660 660 stopLock.notifyAll();
661 661 }
662 662 }
663 663 }
664 664
665 665 /**
666 666 * Creates the background thread started by {@link #go()} to run
667 667 * this consumer. Override this method if you need to set
668 668 * non-default {@code Thread} options or create the thread in a
669 669 * {@code ThreadGroup}. If you don't need to create the thread
670 670 * yourself, set the desired options on {@code super.createThread()}
671 671 * before returning it. Otherwise, the {@code Runnable} target of
672 672 * the created thread must call {@link #work()} in order to run this
673 673 * DTrace consumer. For example, to modify the default background
674 674 * consumer thread:
675 675 * <pre><code>
676 676 * protected Thread
677 677 * createThread()
678 678 * {
679 679 * Thread t = super.createThread();
680 680 * t.setPriority(Thread.MIN_PRIORITY);
681 681 * return t;
682 682 * }
683 683 * </code></pre>
684 684 * Or if you need to create your own thread:
685 685 * <pre></code>
686 686 * protected Thread
687 687 * createThread()
688 688 * {
689 689 * Runnable target = new Runnable() {
690 690 * public void run() {
691 691 * work();
692 692 * }
693 693 * };
694 694 * String name = "Consumer " + UserApplication.sequence++;
695 695 * Thread t = new Thread(UserApplication.threadGroup,
696 696 * target, name);
697 697 * return t;
698 698 * }
699 699 * </code></pre>
700 700 * Do not start the returned thread, otherwise {@code go()} will
701 701 * throw an {@link IllegalThreadStateException} when it tries to
702 702 * start the returned thread a second time.
703 703 */
704 704 protected Thread
705 705 createThread()
706 706 {
707 707 Thread t = new Thread(new Runnable() {
708 708 public void run() {
709 709 work();
710 710 }
711 711 }, "DTrace consumer " + id);
712 712 return t;
713 713 }
714 714
715 715 /**
716 716 * @inheritDoc
717 717 * @throws IllegalThreadStateException if a subclass calls {@link
718 718 * Thread#start()} on the value of {@link #createThread()}
719 719 * @see #createThread()
720 720 */
721 721 public void
722 722 go() throws DTraceException
723 723 {
724 724 go(null);
725 725 }
726 726
727 727 /**
728 728 * @inheritDoc
729 729 * @throws IllegalThreadStateException if a subclass calls {@link
730 730 * Thread#start()} on the value of {@link #createThread()}
731 731 * @see #createThread()
732 732 */
733 733 public synchronized void
734 734 go(ExceptionHandler h) throws DTraceException
735 735 {
736 736 switch (state) {
737 737 case INIT:
738 738 throw new IllegalStateException("consumer not open");
739 739 case OPEN:
740 740 throw new IllegalStateException("no compiled program");
741 741 case COMPILED:
742 742 //
743 743 // Throws IllegalStateException if not all compiled programs are
744 744 // also enabled. Does not make any calls to libdtrace.
745 745 //
746 746 _checkProgramEnabling();
747 747 break;
748 748 case GO:
749 749 case STARTED:
750 750 throw new IllegalStateException("go() already called");
751 751 case STOPPED:
752 752 throw new IllegalStateException("consumer stopped");
753 753 case CLOSED:
754 754 throw new IllegalStateException("consumer closed");
755 755 default:
756 756 throw new IllegalArgumentException("unknown state: " + state);
757 757 }
758 758
759 759 synchronized (LocalConsumer.class) {
760 760 _go();
761 761 }
762 762
763 763 state = State.GO;
764 764 exceptionHandler = h;
765 765 Thread t = createThread();
766 766 t.start();
767 767 }
768 768
769 769 /**
770 770 * @inheritDoc
771 771 *
772 772 * @throws IllegalThreadStateException if attempting to {@code
773 773 * stop()} a running consumer while holding the lock on that
774 774 * consumer
775 775 */
776 776 public void
777 777 stop()
778 778 {
779 779 boolean running = false;
780 780
781 781 synchronized (this) {
782 782 switch (state) {
783 783 case INIT:
784 784 throw new IllegalStateException("consumer not open");
785 785 case OPEN:
786 786 case COMPILED:
787 787 throw new IllegalStateException("go() not called");
788 788 case GO:
789 789 try {
790 790 synchronized (LocalConsumer.class) {
791 791 _stop();
792 792 }
793 793 state = State.STOPPED;
794 794 fireConsumerStopped(new ConsumerEvent(this,
795 795 System.nanoTime()));
796 796 } catch (DTraceException e) {
797 797 if (exceptionHandler != null) {
798 798 exceptionHandler.handleException(e);
799 799 } else {
800 800 e.printStackTrace();
801 801 }
802 802 }
803 803 break;
804 804 case STARTED:
805 805 running = true;
806 806 break;
807 807 case STOPPED:
808 808 //
809 809 // The work() thread that runs the native consumer
810 810 // loop may have terminated because of the exit()
811 811 // action in a DTrace program. In that case, a
812 812 // RuntimeException is inappropriate because there
813 813 // is no misuse of the API. Creating a new checked
814 814 // exception type to handle this case seems to offer
815 815 // no benefit for the trouble to the caller.
816 816 // Instead, the situation calls for stop() to be
817 817 // quietly tolerant.
818 818 //
819 819 if (stopCalled) {
820 820 throw new IllegalStateException(
821 821 "consumer already stopped");
822 822 }
823 823 logger.fine("consumer already stopped");
824 824 break;
825 825 case CLOSED:
826 826 throw new IllegalStateException("consumer closed");
827 827 default:
828 828 throw new IllegalArgumentException("unknown state: " +
829 829 state);
830 830 }
831 831
832 832 stopCalled = true;
833 833 }
834 834
835 835 if (running) {
836 836 if (Thread.holdsLock(this)) {
837 837 throw new IllegalThreadStateException("The current " +
838 838 "thread cannot stop this LocalConsumer while " +
839 839 "holding the lock on this LocalConsumer");
840 840 }
841 841
842 842 //
843 843 // Calls no libdtrace methods, so no synchronization is
844 844 // needed. Sets a native flag that causes the consumer
845 845 // thread to exit the consumer loop and call native
846 846 // dtrace_stop() at the end of the current interval (after
847 847 // grabbing the global Consumer.class lock required for any
848 848 // libdtrace call).
849 849 //
850 850 _interrupt();
851 851
852 852 synchronized (stopLock) {
853 853 //
854 854 // Wait for work() to set workEnded. If the work()
855 855 // thread got the stopLock first, then workEnded is
856 856 // already set.
857 857 //
858 858 while (!workEnded) {
859 859 try {
860 860 stopLock.wait();
861 861 } catch (InterruptedException e) {
862 862 logger.warning(e.toString());
863 863 // do nothing but re-check the condition for
864 864 // waiting
865 865 }
866 866 }
867 867 }
868 868 }
869 869 }
870 870
871 871 public synchronized void
872 872 abort()
873 873 {
874 874 if ((state != State.INIT) && (state != State.CLOSED)) {
875 875 _interrupt();
876 876 }
877 877 abortCalled = true;
878 878 }
879 879
880 880 /**
881 881 * @inheritDoc
882 882 *
883 883 * @throws IllegalThreadStateException if attempting to {@code
884 884 * close()} a running consumer while holding the lock on that
885 885 * consumer
886 886 */
887 887 public void
888 888 close()
889 889 {
890 890 synchronized (this) {
891 891 if ((state == State.INIT) || (state == State.CLOSED)) {
892 892 state = State.CLOSED;
893 893 return;
894 894 }
895 895 }
896 896
897 897 try {
898 898 stop();
899 899 } catch (IllegalStateException e) {
900 900 // ignore (we don't have synchronized state access because
901 901 // it is illegal to call stop() while holding the lock on
902 902 // this consumer)
903 903 }
904 904
905 905 synchronized (this) {
906 906 if (state != State.CLOSED) {
907 907 synchronized (LocalConsumer.class) {
908 908 _close();
909 909 }
910 910 _destroy();
911 911 state = State.CLOSED;
912 912
913 913 if (logger.isLoggable(Level.INFO)) {
914 914 logger.info("consumer table count: " + _openCount());
915 915 }
916 916 }
917 917 }
918 918 }
919 919
920 920 public void
921 921 addConsumerListener(ConsumerListener l)
922 922 {
923 923 listenerList.add(ConsumerListener.class, l);
924 924 }
925 925
926 926 public void
927 927 removeConsumerListener(ConsumerListener l)
928 928 {
929 929 listenerList.remove(ConsumerListener.class, l);
930 930 }
931 931
932 932 public Aggregate
933 933 getAggregate() throws DTraceException
934 934 {
935 935 // include all, clear none
936 936 return getAggregate(null, Collections. <String> emptySet());
937 937 }
938 938
939 939 public Aggregate
940 940 getAggregate(Set <String> includedAggregationNames)
941 941 throws DTraceException
942 942 {
943 943 return getAggregate(includedAggregationNames,
944 944 Collections. <String> emptySet());
945 945 }
946 946
947 947 public Aggregate
948 948 getAggregate(Set <String> includedAggregationNames,
949 949 Set <String> clearedAggregationNames)
950 950 throws DTraceException
951 951 {
952 952 AggregateSpec spec = new AggregateSpec();
953 953
954 954 if (includedAggregationNames == null) {
955 955 spec.setIncludeByDefault(true);
956 956 } else {
957 957 spec.setIncludeByDefault(false);
958 958 for (String included : includedAggregationNames) {
959 959 spec.addIncludedAggregationName(included);
960 960 }
961 961 }
962 962
963 963 if (clearedAggregationNames == null) {
964 964 spec.setClearByDefault(true);
965 965 } else {
966 966 spec.setClearByDefault(false);
967 967 for (String cleared : clearedAggregationNames) {
968 968 spec.addClearedAggregationName(cleared);
969 969 }
970 970 }
971 971
972 972 return getAggregate(spec);
973 973 }
974 974
975 975 private synchronized Aggregate
976 976 getAggregate(AggregateSpec spec) throws DTraceException
977 977 {
978 978 //
979 979 // It should be possible to request aggregation data after a
980 980 // consumer has stopped but not after it has been closed.
981 981 //
982 982 checkGoCalled();
983 983
984 984 //
985 985 // Getting the aggregate is a time-consuming request that should not
986 986 // prevent other consumers from running concurrently. Instead,
987 987 // native code will acquire the LocalConsumer.class monitor as
988 988 // needed before calling libdtrace functions.
989 989 //
990 990 Aggregate aggregate = _getAggregate(spec);
991 991 return aggregate;
992 992 }
993 993
994 994 private synchronized void
995 995 checkGoCalled()
996 996 {
997 997 switch (state) {
998 998 case INIT:
999 999 throw new IllegalStateException("consumer not open");
1000 1000 case OPEN:
1001 1001 case COMPILED:
1002 1002 throw new IllegalStateException("go() not called");
1003 1003 case GO:
1004 1004 case STARTED:
1005 1005 case STOPPED:
1006 1006 break;
1007 1007 case CLOSED:
1008 1008 throw new IllegalStateException("consumer closed");
1009 1009 }
1010 1010 }
1011 1011
1012 1012 private synchronized void
1013 1013 checkGoNotCalled()
1014 1014 {
1015 1015 switch (state) {
1016 1016 case INIT:
1017 1017 throw new IllegalStateException("consumer not open");
1018 1018 case OPEN:
1019 1019 case COMPILED:
1020 1020 break;
1021 1021 case GO:
1022 1022 case STARTED:
1023 1023 throw new IllegalStateException("go() already called");
1024 1024 case STOPPED:
1025 1025 throw new IllegalStateException("consumer stopped");
1026 1026 case CLOSED:
1027 1027 throw new IllegalStateException("consumer closed");
1028 1028 }
1029 1029 }
1030 1030
1031 1031 public synchronized int
1032 1032 createProcess(String command) throws DTraceException
1033 1033 {
1034 1034 if (command == null) {
1035 1035 throw new NullPointerException("command is null");
1036 1036 }
1037 1037
1038 1038 checkGoNotCalled();
1039 1039
1040 1040 int pid;
1041 1041 synchronized (LocalConsumer.class) {
1042 1042 pid = _createProcess(command);
1043 1043 }
1044 1044 return pid;
1045 1045 }
1046 1046
1047 1047 public synchronized void
1048 1048 grabProcess(int pid) throws DTraceException
1049 1049 {
1050 1050 checkGoNotCalled();
1051 1051
1052 1052 synchronized (LocalConsumer.class) {
1053 1053 _grabProcess(pid);
1054 1054 }
1055 1055 }
1056 1056
1057 1057 public synchronized List <ProbeDescription>
1058 1058 listProbes(ProbeDescription filter) throws DTraceException
1059 1059 {
1060 1060 checkGoNotCalled();
1061 1061 List <ProbeDescription> probeList =
1062 1062 new LinkedList <ProbeDescription> ();
1063 1063 if (filter == ProbeDescription.EMPTY) {
1064 1064 filter = null;
1065 1065 }
1066 1066 synchronized (LocalConsumer.class) {
1067 1067 _listProbes(probeList, filter);
1068 1068 }
1069 1069 return probeList;
1070 1070 }
1071 1071
1072 1072 public synchronized List <Probe>
1073 1073 listProbeDetail(ProbeDescription filter) throws DTraceException
1074 1074 {
1075 1075 checkGoNotCalled();
1076 1076 List <Probe> probeList = new LinkedList <Probe> ();
1077 1077 if (filter == ProbeDescription.EMPTY) {
1078 1078 filter = null;
1079 1079 }
1080 1080 synchronized (LocalConsumer.class) {
1081 1081 _listProbeDetail(probeList, filter);
1082 1082 }
1083 1083 return probeList;
1084 1084 }
1085 1085
1086 1086 public synchronized List <ProbeDescription>
1087 1087 listProgramProbes(Program program) throws DTraceException
1088 1088 {
1089 1089 checkProgram(program);
1090 1090 checkGoNotCalled();
1091 1091 List <ProbeDescription> probeList =
1092 1092 new LinkedList <ProbeDescription> ();
1093 1093 synchronized (LocalConsumer.class) {
1094 1094 _listCompiledProbes(probeList, program);
1095 1095 }
1096 1096 return probeList;
1097 1097 }
1098 1098
1099 1099 public synchronized List <Probe>
1100 1100 listProgramProbeDetail(Program program) throws DTraceException
1101 1101 {
1102 1102 checkProgram(program);
1103 1103 checkGoNotCalled();
1104 1104 List <Probe> probeList = new LinkedList <Probe> ();
1105 1105 synchronized (LocalConsumer.class) {
1106 1106 _listCompiledProbeDetail(probeList, program);
1107 1107 }
1108 1108 return probeList;
1109 1109 }
1110 1110
1111 1111 public synchronized String
1112 1112 lookupKernelFunction(int address)
1113 1113 {
1114 1114 checkGoCalled();
1115 1115 synchronized (LocalConsumer.class) {
1116 1116 return _lookupKernelFunction(new Integer(address));
1117 1117 }
1118 1118 }
1119 1119
1120 1120 public synchronized String
1121 1121 lookupKernelFunction(long address)
1122 1122 {
1123 1123 checkGoCalled();
1124 1124 synchronized (LocalConsumer.class) {
1125 1125 return _lookupKernelFunction(new Long(address));
1126 1126 }
1127 1127 }
1128 1128
1129 1129 public synchronized String
1130 1130 lookupUserFunction(int pid, int address)
1131 1131 {
1132 1132 checkGoCalled();
1133 1133 synchronized (LocalConsumer.class) {
1134 1134 return _lookupUserFunction(pid, new Integer(address));
1135 1135 }
1136 1136 }
1137 1137
1138 1138 public synchronized String
1139 1139 lookupUserFunction(int pid, long address)
1140 1140 {
1141 1141 checkGoCalled();
1142 1142 synchronized (LocalConsumer.class) {
1143 1143 return _lookupUserFunction(pid, new Long(address));
1144 1144 }
1145 1145 }
1146 1146
1147 1147 public String
1148 1148 getVersion()
1149 1149 {
1150 1150 synchronized (LocalConsumer.class) {
1151 1151 return LocalConsumer._getVersion();
1152 1152 }
1153 1153 }
1154 1154
1155 1155 /**
1156 1156 * Called by native code.
1157 1157 */
1158 1158 private void
1159 1159 nextProbeData(ProbeData probeData) throws ConsumerException
1160 1160 {
1161 1161 fireDataReceived(new DataEvent(this, probeData));
1162 1162 }
1163 1163
1164 1164 /**
1165 1165 * Called by native code.
1166 1166 */
1167 1167 private void
1168 1168 dataDropped(Drop drop) throws ConsumerException
1169 1169 {
1170 1170 fireDataDropped(new DropEvent(this, drop));
1171 1171 }
1172 1172
1173 1173 /**
1174 1174 * Called by native code.
1175 1175 */
1176 1176 private void
1177 1177 errorEncountered(Error error) throws ConsumerException
1178 1178 {
1179 1179 fireErrorEncountered(new ErrorEvent(this, error));
1180 1180 }
1181 1181
1182 1182 /**
1183 1183 * Called by native code.
1184 1184 */
1185 1185 private void
1186 1186 processStateChanged(ProcessState processState) throws ConsumerException
1187 1187 {
1188 1188 fireProcessStateChanged(new ProcessEvent(this, processState));
1189 1189 }
1190 1190
1191 1191 protected void
1192 1192 fireDataReceived(DataEvent e) throws ConsumerException
1193 1193 {
1194 1194 // Guaranteed to return a non-null array
1195 1195 Object[] listeners = listenerList.getListenerList();
1196 1196 // Process the listeners last to first, notifying
1197 1197 // those that are interested in this event
1198 1198 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1199 1199 if (listeners[i] == ConsumerListener.class) {
1200 1200 ((ConsumerListener)listeners[i + 1]).dataReceived(e);
1201 1201 }
1202 1202 }
1203 1203 }
1204 1204
1205 1205 protected void
1206 1206 fireDataDropped(DropEvent e) throws ConsumerException
1207 1207 {
1208 1208 // Guaranteed to return a non-null array
1209 1209 Object[] listeners = listenerList.getListenerList();
1210 1210 // Process the listeners last to first, notifying
1211 1211 // those that are interested in this event
1212 1212 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1213 1213 if (listeners[i] == ConsumerListener.class) {
1214 1214 ((ConsumerListener)listeners[i + 1]).dataDropped(e);
1215 1215 }
1216 1216 }
1217 1217 }
1218 1218
1219 1219 protected void
1220 1220 fireErrorEncountered(ErrorEvent e) throws ConsumerException
1221 1221 {
1222 1222 // Guaranteed to return a non-null array
1223 1223 Object[] listeners = listenerList.getListenerList();
1224 1224 // Process the listeners last to first, notifying
1225 1225 // those that are interested in this event
1226 1226 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1227 1227 if (listeners[i] == ConsumerListener.class) {
1228 1228 ((ConsumerListener)listeners[i + 1]).errorEncountered(e);
1229 1229 }
1230 1230 }
1231 1231 }
1232 1232
1233 1233 protected void
1234 1234 fireProcessStateChanged(ProcessEvent e) throws ConsumerException
1235 1235 {
1236 1236 // Guaranteed to return a non-null array
1237 1237 Object[] listeners = listenerList.getListenerList();
1238 1238 // Process the listeners last to first, notifying
1239 1239 // those that are interested in this event
1240 1240 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1241 1241 if (listeners[i] == ConsumerListener.class) {
1242 1242 ((ConsumerListener)listeners[i + 1]).processStateChanged(e);
1243 1243 }
1244 1244 }
1245 1245 }
1246 1246
1247 1247 protected void
1248 1248 fireConsumerStarted(ConsumerEvent e)
1249 1249 {
1250 1250 // Guaranteed to return a non-null array
1251 1251 Object[] listeners = listenerList.getListenerList();
1252 1252 // Process the listeners last to first, notifying
1253 1253 // those that are interested in this event
1254 1254 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1255 1255 if (listeners[i] == ConsumerListener.class) {
1256 1256 ((ConsumerListener)listeners[i + 1]).consumerStarted(e);
1257 1257 }
1258 1258 }
1259 1259 }
1260 1260
1261 1261 protected void
1262 1262 fireConsumerStopped(ConsumerEvent e)
1263 1263 {
1264 1264 // Guaranteed to return a non-null array
1265 1265 Object[] listeners = listenerList.getListenerList();
1266 1266 // Process the listeners last to first, notifying
1267 1267 // those that are interested in this event
1268 1268 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1269 1269 if (listeners[i] == ConsumerListener.class) {
1270 1270 ((ConsumerListener)listeners[i + 1]).consumerStopped(e);
1271 1271 }
1272 1272 }
1273 1273 }
1274 1274
1275 1275 // Called by native code
1276 1276 private void
1277 1277 intervalBegan()
1278 1278 {
1279 1279 fireIntervalBegan(new ConsumerEvent(this, System.nanoTime()));
1280 1280 }
1281 1281
1282 1282 protected void
1283 1283 fireIntervalBegan(ConsumerEvent e)
1284 1284 {
1285 1285 // Guaranteed to return a non-null array
1286 1286 Object[] listeners = listenerList.getListenerList();
1287 1287 // Process the listeners last to first, notifying
1288 1288 // those that are interested in this event
1289 1289 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1290 1290 if (listeners[i] == ConsumerListener.class) {
1291 1291 ((ConsumerListener)listeners[i + 1]).intervalBegan(e);
1292 1292 }
1293 1293 }
1294 1294 }
1295 1295
1296 1296 // Called by native code
1297 1297 private void
1298 1298 intervalEnded()
1299 1299 {
1300 1300 fireIntervalEnded(new ConsumerEvent(this, System.nanoTime()));
1301 1301 }
1302 1302
1303 1303 protected void
1304 1304 fireIntervalEnded(ConsumerEvent e)
1305 1305 {
1306 1306 // Guaranteed to return a non-null array
1307 1307 Object[] listeners = listenerList.getListenerList();
1308 1308 // Process the listeners last to first, notifying
1309 1309 // those that are interested in this event
1310 1310 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1311 1311 if (listeners[i] == ConsumerListener.class) {
1312 1312 ((ConsumerListener)listeners[i + 1]).intervalEnded(e);
1313 1313 }
1314 1314 }
1315 1315 }
1316 1316
1317 1317 /**
1318 1318 * Gets a string representation of this consumer useful for logging
1319 1319 * and not intended for display. The exact details of the
1320 1320 * representation are unspecified and subject to change, but the
1321 1321 * following format may be regarded as typical:
1322 1322 * <pre><code>
1323 1323 * class-name[property1 = value1, property2 = value2]
1324 1324 * </code></pre>
1325 1325 */
1326 1326 public String
1327 1327 toString()
1328 1328 {
1329 1329 StringBuilder buf = new StringBuilder(LocalConsumer.class.getName());
1330 1330 synchronized (this) {
1331 1331 buf.append("[open = ");
1332 1332 buf.append(isOpen());
1333 1333 buf.append(", enabled = ");
1334 1334 buf.append(isEnabled());
1335 1335 buf.append(", running = ");
1336 1336 buf.append(isRunning());
1337 1337 buf.append(", closed = ");
1338 1338 buf.append(isClosed());
1339 1339 }
1340 1340 buf.append(']');
1341 1341 return buf.toString();
1342 1342 }
1343 1343
1344 1344 /**
1345 1345 * Ensures that the {@link #close()} method of this consumer has
1346 1346 * been called before it is garbage-collected. The intended safety
1347 1347 * net is weak because the JVM does not guarantee that an object
1348 1348 * will be garbage-collected when it is no longer referenced. Users
1349 1349 * of the API should call {@code close()} to ensure that all
1350 1350 * resources associated with this consumer are reclaimed in a timely
1351 1351 * manner.
1352 1352 *
1353 1353 * @see #close()
1354 1354 */
1355 1355 protected void
1356 1356 finalize()
1357 1357 {
1358 1358 close();
1359 1359 }
1360 1360
1361 1361 private String
1362 1362 getTag()
1363 1363 {
1364 1364 return super.toString();
1365 1365 }
1366 1366
1367 1367 //
1368 1368 // Uniquely identifies a consumer across systems so it is possible
1369 1369 // to validate that an object such as a Program passed to a remote
1370 1370 // client over a socket was created by this consumer and no other.
1371 1371 //
1372 1372 static class Identifier implements Serializable {
1373 1373 static final long serialVersionUID = 2183165132305302834L;
1374 1374
1375 1375 // local identifier
1376 1376 private int id;
1377 1377 private long timestamp;
1378 1378 // remote identifier
1379 1379 private InetAddress localHost;
1380 1380 private String tag; // in case localHost not available
1381 1381
1382 1382 private
1383 1383 Identifier(LocalConsumer consumer)
1384 1384 {
1385 1385 id = LocalConsumer.sequence++;
1386 1386 timestamp = System.currentTimeMillis();
1387 1387 try {
1388 1388 localHost = InetAddress.getLocalHost();
1389 1389 } catch (UnknownHostException e) {
1390 1390 localHost = null;
1391 1391 }
1392 1392 tag = consumer.getTag();
1393 1393 }
1394 1394
1395 1395 @Override
1396 1396 public boolean
1397 1397 equals(Object o)
1398 1398 {
1399 1399 if (o == this) {
1400 1400 return true;
1401 1401 }
1402 1402 if (o instanceof Identifier) {
1403 1403 Identifier i = (Identifier)o;
1404 1404 return ((id == i.id) &&
1405 1405 (timestamp == i.timestamp) &&
1406 1406 ((localHost == null) ? (i.localHost == null) :
1407 1407 localHost.equals(i.localHost)) &&
1408 1408 tag.equals(i.tag));
1409 1409 }
1410 1410 return false;
1411 1411 }
1412 1412
1413 1413 @Override
1414 1414 public int
1415 1415 hashCode()
1416 1416 {
1417 1417 int hash = 17;
1418 1418 hash = (37 * hash) + id;
1419 1419 hash = (37 * hash) + ((int)(timestamp ^ (timestamp >>> 32)));
1420 1420 hash = (37 * hash) + (localHost == null ? 0 :
1421 1421 localHost.hashCode());
1422 1422 hash = (37 * hash) + tag.hashCode();
1423 1423 return hash;
1424 1424 }
1425 1425
1426 1426 @Override
1427 1427 public String
1428 1428 toString()
1429 1429 {
1430 1430 StringBuilder buf = new StringBuilder();
1431 1431 buf.append(Identifier.class.getName());
1432 1432 buf.append("[id = ");
1433 1433 buf.append(id);
1434 1434 buf.append(", timestamp = ");
1435 1435 buf.append(timestamp);
1436 1436 buf.append(", localHost = ");
1437 1437 buf.append(localHost);
1438 1438 buf.append(", tag = ");
1439 1439 buf.append(tag);
1440 1440 buf.append(']');
1441 1441 return buf.toString();
1442 1442 }
1443 1443 }
1444 1444 }
↓ open down ↓ |
794 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX