source: other-projects/trunk/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/ant/taskdefs/Redirector.java@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

File size: 27.2 KB
Line 
1/*
2 * Copyright 2003-2005 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17package org.apache.tools.ant.taskdefs;
18
19import java.io.File;
20import java.io.Reader;
21import java.io.InputStream;
22import java.io.IOException;
23import java.io.PrintStream;
24import java.io.OutputStream;
25import java.io.StringReader;
26import java.io.BufferedReader;
27import java.io.InputStreamReader;
28import java.io.PipedOutputStream;
29import java.io.ByteArrayInputStream;
30import java.io.ByteArrayOutputStream;
31import java.util.Arrays;
32import java.util.Vector;
33
34import org.apache.tools.ant.Project;
35import org.apache.tools.ant.ProjectComponent;
36import org.apache.tools.ant.Task;
37import org.apache.tools.ant.BuildException;
38import org.apache.tools.ant.filters.util.ChainReaderHelper;
39import org.apache.tools.ant.util.StringUtils;
40import org.apache.tools.ant.util.TeeOutputStream;
41import org.apache.tools.ant.util.ReaderInputStream;
42import org.apache.tools.ant.util.LeadPipeInputStream;
43import org.apache.tools.ant.util.LazyFileOutputStream;
44import org.apache.tools.ant.util.OutputStreamFunneler;
45import org.apache.tools.ant.util.ConcatFileInputStream;
46import org.apache.tools.ant.util.KeepAliveOutputStream;
47
48/**
49 * The Redirector class manages the setup and connection of
50 * input and output redirection for an Ant project component.
51 *
52 * @since Ant 1.6
53 */
54public class Redirector {
55
56 private static final String DEFAULT_ENCODING
57 = System.getProperty("file.encoding");
58
59 private class PropertyOutputStream extends ByteArrayOutputStream {
60 String property;
61 boolean closed = false;
62
63 PropertyOutputStream(String property) {
64 super();
65 this.property = property;
66 }
67
68 public void close() throws IOException {
69 if (!closed && !(append && appendProperties)) {
70 setPropertyFromBAOS(this, property);
71 closed = true;
72 }
73 }
74 }
75
76 /**
77 * The file(s) from which standard input is being taken.
78 * If > 1, files' content will be concatenated in the order received.
79 */
80 private File[] input;
81
82 /**
83 * The file(s) receiving standard output. Will also receive standard error
84 * unless standard error is redirected or logError is true.
85 */
86 private File[] out;
87
88 /**
89 * The file(s) to which standard error is being redirected
90 */
91 private File[] error;
92
93 /**
94 * Indicates if standard error should be logged to Ant's log system
95 * rather than the output. This has no effect if standard error is
96 * redirected to a file or property.
97 */
98 private boolean logError = false;
99
100 /**
101 * Buffer used to capture output for storage into a property
102 */
103 private PropertyOutputStream baos = null;
104
105 /**
106 * Buffer used to capture error output for storage into a property
107 */
108 private PropertyOutputStream errorBaos = null;
109
110 /** The name of the property into which output is to be stored */
111 private String outputProperty;
112
113 /** The name of the property into which error output is to be stored */
114 private String errorProperty;
115
116 /** String from which input is taken */
117 private String inputString;
118
119 /** Flag which indicates if error and output files are to be appended. */
120 private boolean append = false;
121
122 /** Flag which indicates that output should be always sent to the log */
123 private boolean alwaysLog = false;
124
125 /** Flag which indicates whether files should be created even when empty. */
126 private boolean createEmptyFiles = true;
127
128 /** The task for which this redirector is working */
129 private ProjectComponent managingTask;
130
131 /** The stream for output data */
132 private OutputStream outputStream = null;
133
134 /** The stream for error output */
135 private OutputStream errorStream = null;
136
137 /** The stream for input */
138 private InputStream inputStream = null;
139
140 /** Stream which is used for line oriented output */
141 private PrintStream outPrintStream = null;
142
143 /** Stream which is used for line oriented error output */
144 private PrintStream errorPrintStream = null;
145
146 /** The output filter chains */
147 private Vector outputFilterChains;
148
149 /** The error filter chains */
150 private Vector errorFilterChains;
151
152 /** The input filter chains */
153 private Vector inputFilterChains;
154
155 /** The output encoding */
156 private String outputEncoding = DEFAULT_ENCODING;
157
158 /** The error encoding */
159 private String errorEncoding = DEFAULT_ENCODING;
160
161 /** The input encoding */
162 private String inputEncoding = DEFAULT_ENCODING;
163
164 /** Whether to complete properties settings **/
165 private boolean appendProperties = true;
166
167 /** The thread group used for starting <code>StreamPumper</code> threads */
168 private ThreadGroup threadGroup = new ThreadGroup("redirector");
169
170 /**
171 * Create a redirector instance for the given task
172 *
173 * @param managingTask the task for which the redirector is to work
174 */
175 public Redirector(Task managingTask) {
176 this((ProjectComponent) managingTask);
177 }
178
179 /**
180 * Create a redirector instance for the given task
181 *
182 * @param managingTask the project component for which the
183 * redirector is to work
184 * @since Ant 1.6.3
185 */
186 public Redirector(ProjectComponent managingTask) {
187 this.managingTask = managingTask;
188 }
189
190 /**
191 * Set the input to use for the task
192 *
193 * @param input the file from which input is read.
194 */
195 public void setInput(File input) {
196 setInput((input == null) ? null : new File[] {input});
197 }
198
199 /**
200 * Set the input to use for the task
201 *
202 * @param input the files from which input is read.
203 */
204 public synchronized void setInput(File[] input) {
205 this.input = input;
206 }
207
208 /**
209 * Set the string to use as input
210 *
211 * @param inputString the string which is used as the input source
212 */
213 public synchronized void setInputString(String inputString) {
214 this.inputString = inputString;
215 }
216
217 /**
218 * Set a stream to use as input.
219 *
220 * @param inputStream the stream from which input will be read
221 * @since Ant 1.6.3
222 */
223 /*public*/ void setInputStream(InputStream inputStream) {
224 this.inputStream = inputStream;
225 }
226
227 /**
228 * File the output of the process is redirected to. If error is not
229 * redirected, it too will appear in the output
230 *
231 * @param out the file to which output stream is written
232 */
233 public void setOutput(File out) {
234 setOutput((out == null) ? null : new File[] {out});
235 }
236
237 /**
238 * Files the output of the process is redirected to. If error is not
239 * redirected, it too will appear in the output
240 *
241 * @param out the files to which output stream is written
242 */
243 public synchronized void setOutput(File[] out) {
244 this.out = out;
245 }
246
247 /**
248 * Set the output encoding.
249 *
250 * @param outputEncoding <CODE>String</CODE>.
251 */
252 public synchronized void setOutputEncoding(String outputEncoding) {
253 if (outputEncoding == null) {
254 throw new IllegalArgumentException(
255 "outputEncoding must not be null");
256 } else {
257 this.outputEncoding = outputEncoding;
258 }
259 }
260
261 /**
262 * Set the error encoding.
263 *
264 * @param errorEncoding <CODE>String</CODE>.
265 */
266 public synchronized void setErrorEncoding(String errorEncoding) {
267 if (errorEncoding == null) {
268 throw new IllegalArgumentException(
269 "errorEncoding must not be null");
270 } else {
271 this.errorEncoding = errorEncoding;
272 }
273 }
274
275 /**
276 * Set the input encoding.
277 *
278 * @param inputEncoding <CODE>String</CODE>.
279 */
280 public synchronized void setInputEncoding(String inputEncoding) {
281 if (inputEncoding == null) {
282 throw new IllegalArgumentException(
283 "inputEncoding must not be null");
284 } else {
285 this.inputEncoding = inputEncoding;
286 }
287 }
288
289 /**
290 * Controls whether error output of exec is logged. This is only useful
291 * when output is being redirected and error output is desired in the
292 * Ant log
293 *
294 * @param logError if true the standard error is sent to the Ant log system
295 * and not sent to output.
296 */
297 public synchronized void setLogError(boolean logError) {
298 this.logError = logError;
299 }
300
301 /**
302 * This <CODE>Redirector</CODE>'s subordinate
303 * <CODE>PropertyOutputStream</CODE>s will not set their respective
304 * properties <CODE>while (appendProperties && append)</CODE>.
305 *
306 * @param appendProperties whether to append properties.
307 */
308 public synchronized void setAppendProperties(boolean appendProperties) {
309 this.appendProperties = appendProperties;
310 }
311
312 /**
313 * Set the file to which standard error is to be redirected.
314 *
315 * @param error the file to which error is to be written
316 */
317 public void setError(File error) {
318 setError((error == null) ? null : new File[] {error});
319 }
320
321 /**
322 * Set the files to which standard error is to be redirected.
323 *
324 * @param error the file to which error is to be written
325 */
326 public synchronized void setError(File[] error) {
327 this.error = error;
328 }
329
330 /**
331 * Property name whose value should be set to the output of
332 * the process.
333 *
334 * @param outputProperty the name of the property to be set with the
335 * task's output.
336 */
337 public synchronized void setOutputProperty(String outputProperty) {
338 if (outputProperty == null
339 || !(outputProperty.equals(this.outputProperty))) {
340 this.outputProperty = outputProperty;
341 baos = null;
342 }
343 }
344
345 /**
346 * Whether output should be appended to or overwrite an existing file.
347 * Defaults to false.
348 *
349 * @param append if true output and error streams are appended to their
350 * respective files, if specified.
351 */
352 public synchronized void setAppend(boolean append) {
353 this.append = append;
354 }
355
356 /**
357 * If true, (error and non-error) output will be "teed", redirected
358 * as specified while being sent to Ant's logging mechanism as if no
359 * redirection had taken place. Defaults to false.
360 * @param alwaysLog <code>boolean</code>
361 * @since Ant 1.6.3
362 */
363 public synchronized void setAlwaysLog(boolean alwaysLog) {
364 this.alwaysLog = alwaysLog;
365 }
366
367 /**
368 * Whether output and error files should be created even when empty.
369 * Defaults to true.
370 * @param createEmptyFiles <CODE>boolean</CODE>.
371 */
372 public synchronized void setCreateEmptyFiles(boolean createEmptyFiles) {
373 this.createEmptyFiles = createEmptyFiles;
374 }
375
376 /**
377 * Property name whose value should be set to the error of
378 * the process.
379 *
380 * @param errorProperty the name of the property to be set
381 * with the error output.
382 */
383 public synchronized void setErrorProperty(String errorProperty) {
384 if (errorProperty == null
385 || !(errorProperty.equals(this.errorProperty))) {
386 this.errorProperty = errorProperty;
387 errorBaos = null;
388 }
389 }
390
391 /**
392 * Set the input <CODE>FilterChain</CODE>s.
393 *
394 * @param inputFilterChains <CODE>Vector</CODE> containing <CODE>FilterChain</CODE>.
395 */
396 public synchronized void setInputFilterChains(Vector inputFilterChains) {
397 this.inputFilterChains = inputFilterChains;
398 }
399
400 /**
401 * Set the output <CODE>FilterChain</CODE>s.
402 *
403 * @param outputFilterChains <CODE>Vector</CODE> containing <CODE>FilterChain</CODE>.
404 */
405 public synchronized void setOutputFilterChains(Vector outputFilterChains) {
406 this.outputFilterChains = outputFilterChains;
407 }
408
409 /**
410 * Set the error <CODE>FilterChain</CODE>s.
411 *
412 * @param errorFilterChains <CODE>Vector</CODE> containing <CODE>FilterChain</CODE>.
413 */
414 public synchronized void setErrorFilterChains(Vector errorFilterChains) {
415 this.errorFilterChains = errorFilterChains;
416 }
417
418 /**
419 * Set a property from a ByteArrayOutputStream
420 *
421 * @param baos contains the property value.
422 * @param propertyName the property name.
423 *
424 * @exception IOException if the value cannot be read form the stream.
425 */
426 private void setPropertyFromBAOS(ByteArrayOutputStream baos,
427 String propertyName) throws IOException {
428
429 BufferedReader in
430 = new BufferedReader(new StringReader(Execute.toString(baos)));
431 String line = null;
432 StringBuffer val = new StringBuffer();
433 while ((line = in.readLine()) != null) {
434 if (val.length() != 0) {
435 val.append(StringUtils.LINE_SEP);
436 }
437 val.append(line);
438 }
439 managingTask.getProject().setNewProperty(propertyName, val.toString());
440 }
441
442 /**
443 * Create the input, error and output streams based on the
444 * configuration options.
445 */
446 public synchronized void createStreams() {
447 if (out != null && out.length > 0) {
448 String logHead = new StringBuffer("Output ").append(
449 ((append) ? "appended" : "redirected")).append(
450 " to ").toString();
451 outputStream = foldFiles(out, logHead, Project.MSG_VERBOSE);
452 }
453 if (outputProperty != null) {
454 if (baos == null) {
455 baos = new PropertyOutputStream(outputProperty);
456 managingTask.log("Output redirected to property: "
457 + outputProperty, Project.MSG_VERBOSE);
458 }
459 //shield it from being closed by a filtering StreamPumper
460 OutputStream keepAliveOutput = new KeepAliveOutputStream(baos);
461 outputStream = (outputStream == null) ? keepAliveOutput
462 : new TeeOutputStream(outputStream, keepAliveOutput);
463 } else {
464 baos = null;
465 }
466
467 if (error != null && error.length > 0) {
468 String logHead = new StringBuffer("Error ").append(
469 ((append) ? "appended" : "redirected")).append(
470 " to ").toString();
471 errorStream = foldFiles(error, logHead, Project.MSG_VERBOSE);
472 } else if (!(logError || outputStream == null)) {
473 long funnelTimeout = 0L;
474 OutputStreamFunneler funneler
475 = new OutputStreamFunneler(outputStream, funnelTimeout);
476 try {
477 outputStream = funneler.getFunnelInstance();
478 errorStream = funneler.getFunnelInstance();
479 } catch (IOException eyeOhEx) {
480 throw new BuildException(
481 "error splitting output/error streams", eyeOhEx);
482 }
483 }
484 if (errorProperty != null) {
485 if (errorBaos == null) {
486 errorBaos = new PropertyOutputStream(errorProperty);
487 managingTask.log("Error redirected to property: " + errorProperty,
488 Project.MSG_VERBOSE);
489 }
490 //shield it from being closed by a filtering StreamPumper
491 OutputStream keepAliveError = new KeepAliveOutputStream(errorBaos);
492 errorStream = (error == null || error.length == 0) ? keepAliveError
493 : new TeeOutputStream(errorStream, keepAliveError);
494 } else {
495 errorBaos = null;
496 }
497 if (alwaysLog || outputStream == null) {
498 OutputStream outputLog
499 = new LogOutputStream(managingTask, Project.MSG_INFO);
500 outputStream = (outputStream == null)
501 ? outputLog : new TeeOutputStream(outputLog, outputStream);
502 }
503 if (alwaysLog || errorStream == null) {
504 OutputStream errorLog
505 = new LogOutputStream(managingTask, Project.MSG_WARN);
506 errorStream = (errorStream == null)
507 ? errorLog : new TeeOutputStream(errorLog, errorStream);
508 }
509 if ((outputFilterChains != null && outputFilterChains.size() > 0)
510 || !(outputEncoding.equalsIgnoreCase(inputEncoding))) {
511 try {
512 LeadPipeInputStream snk = new LeadPipeInputStream();
513 snk.setManagingComponent(managingTask);
514
515 InputStream outPumpIn = snk;
516
517 Reader reader = new InputStreamReader(outPumpIn, inputEncoding);
518
519 if (outputFilterChains != null && outputFilterChains.size() > 0) {
520 ChainReaderHelper helper = new ChainReaderHelper();
521 helper.setPrimaryReader(reader);
522 helper.setFilterChains(outputFilterChains);
523 reader = helper.getAssembledReader();
524 }
525 outPumpIn = new ReaderInputStream(reader, outputEncoding);
526
527 Thread t = new Thread(threadGroup, new StreamPumper(
528 outPumpIn, outputStream, true), "output pumper");
529 t.setPriority(Thread.MAX_PRIORITY);
530 outputStream = new PipedOutputStream(snk);
531 t.start();
532 } catch (IOException eyeOhEx) {
533 throw new BuildException(
534 "error setting up output stream", eyeOhEx);
535 }
536 }
537
538 if ((errorFilterChains != null && errorFilterChains.size() > 0)
539 || !(errorEncoding.equalsIgnoreCase(inputEncoding))) {
540 try {
541 LeadPipeInputStream snk = new LeadPipeInputStream();
542 snk.setManagingComponent(managingTask);
543
544 InputStream errPumpIn = snk;
545
546 Reader reader = new InputStreamReader(errPumpIn, inputEncoding);
547
548 if (errorFilterChains != null && errorFilterChains.size() > 0) {
549 ChainReaderHelper helper = new ChainReaderHelper();
550 helper.setPrimaryReader(reader);
551 helper.setFilterChains(errorFilterChains);
552 reader = helper.getAssembledReader();
553 }
554 errPumpIn = new ReaderInputStream(reader, errorEncoding);
555
556 Thread t = new Thread(threadGroup, new StreamPumper(
557 errPumpIn, errorStream, true), "error pumper");
558 t.setPriority(Thread.MAX_PRIORITY);
559 errorStream = new PipedOutputStream(snk);
560 t.start();
561 } catch (IOException eyeOhEx) {
562 throw new BuildException(
563 "error setting up error stream", eyeOhEx);
564 }
565 }
566
567 // if input files are specified, inputString and inputStream are ignored;
568 // classes that work with redirector attributes can enforce
569 // whatever warnings are needed
570 if (input != null && input.length > 0) {
571 managingTask.log("Redirecting input from file"
572 + ((input.length == 1) ? "" : "s"), Project.MSG_VERBOSE);
573 try {
574 inputStream = new ConcatFileInputStream(input);
575 } catch (IOException eyeOhEx) {
576 throw new BuildException(eyeOhEx);
577 }
578 ((ConcatFileInputStream) inputStream).setManagingComponent(managingTask);
579 } else if (inputString != null) {
580 managingTask.log("Using input \"" + inputString + "\"",
581 Project.MSG_VERBOSE);
582 inputStream = new ByteArrayInputStream(inputString.getBytes());
583 }
584
585 if (inputStream != null
586 && inputFilterChains != null && inputFilterChains.size() > 0) {
587 ChainReaderHelper helper = new ChainReaderHelper();
588 try {
589 helper.setPrimaryReader(
590 new InputStreamReader(inputStream, inputEncoding));
591 } catch (IOException eyeOhEx) {
592 throw new BuildException(
593 "error setting up input stream", eyeOhEx);
594 }
595 helper.setFilterChains(inputFilterChains);
596 inputStream = new ReaderInputStream(
597 helper.getAssembledReader(), inputEncoding);
598 }
599 }
600
601 /**
602 * Create the StreamHandler to use with our Execute instance.
603 *
604 * @return the execute stream handler to manage the input, output and
605 * error streams.
606 *
607 * @throws BuildException if the execute stream handler cannot be created.
608 */
609 public synchronized ExecuteStreamHandler createHandler()
610 throws BuildException {
611 createStreams();
612 return new PumpStreamHandler(outputStream, errorStream, inputStream);
613 }
614
615 /**
616 * Pass output sent to System.out to specified output.
617 *
618 * @param output the data to be output
619 */
620 protected synchronized void handleOutput(String output) {
621 if (outPrintStream == null) {
622 outPrintStream = new PrintStream(outputStream);
623 }
624 outPrintStream.print(output);
625 }
626
627 /**
628 * Handle an input request
629 *
630 * @param buffer the buffer into which data is to be read.
631 * @param offset the offset into the buffer at which data is stored.
632 * @param length the amount of data to read
633 *
634 * @return the number of bytes read
635 *
636 * @exception IOException if the data cannot be read
637 */
638 protected synchronized int handleInput(byte[] buffer, int offset,
639 int length) throws IOException {
640 if (inputStream == null) {
641 return managingTask.getProject().defaultInput(buffer, offset,
642 length);
643 } else {
644 return inputStream.read(buffer, offset, length);
645 }
646 }
647
648 /**
649 * Process data due to a flush operation.
650 *
651 * @param output the data being flushed.
652 */
653 protected synchronized void handleFlush(String output) {
654 if (outPrintStream == null) {
655 outPrintStream = new PrintStream(outputStream);
656 }
657 outPrintStream.print(output);
658 outPrintStream.flush();
659 }
660
661 /**
662 * Process error output
663 *
664 * @param output the error output data.
665 */
666 protected synchronized void handleErrorOutput(String output) {
667 if (errorPrintStream == null) {
668 errorPrintStream = new PrintStream(errorStream);
669 }
670 errorPrintStream.print(output);
671 }
672
673 /**
674 * Handle a flush operation on the error stream
675 *
676 * @param output the error information being flushed.
677 */
678 protected synchronized void handleErrorFlush(String output) {
679 if (errorPrintStream == null) {
680 errorPrintStream = new PrintStream(errorStream);
681 }
682 errorPrintStream.print(output);
683 }
684
685 /**
686 * Get the output stream for the redirector
687 *
688 * @return the redirector's output stream or null if no output
689 * has been configured
690 */
691 public synchronized OutputStream getOutputStream() {
692 return outputStream;
693 }
694
695 /**
696 * Get the error stream for the redirector
697 *
698 * @return the redirector's error stream or null if no output
699 * has been configured
700 */
701 public synchronized OutputStream getErrorStream() {
702 return errorStream;
703 }
704
705 /**
706 * Get the input stream for the redirector
707 *
708 * @return the redirector's input stream or null if no output
709 * has been configured
710 */
711 public synchronized InputStream getInputStream() {
712 return inputStream;
713 }
714
715 /**
716 * Complete redirection.
717 *
718 * This operation will close any streams and create any specified
719 * property values.
720 *
721 * @throws IOException if the output properties cannot be read from their
722 * output streams.
723 */
724 public synchronized void complete() throws IOException {
725 System.out.flush();
726 System.err.flush();
727
728 if (inputStream != null) {
729 inputStream.close();
730 }
731
732 outputStream.flush();
733 outputStream.close();
734
735 errorStream.flush();
736 errorStream.close();
737
738 //wait for the StreamPumpers to finish
739 while (threadGroup.activeCount() > 0) {
740 try {
741 managingTask.log("waiting for " + threadGroup.activeCount()
742 + " Threads:", Project.MSG_DEBUG);
743 Thread[] thread = new Thread[threadGroup.activeCount()];
744 threadGroup.enumerate(thread);
745 for (int i = 0; i < thread.length && thread[i] != null; i++) {
746 try {
747 managingTask.log(thread[i].toString(), Project.MSG_DEBUG);
748 } catch (NullPointerException enPeaEx) {
749 // Ignore exception
750 }
751 }
752 Thread.sleep(1000);
753 } catch (InterruptedException eyeEx) {
754 // Ignore exception
755 }
756 }
757
758 setProperties();
759
760 inputStream = null;
761 outputStream = null;
762 errorStream = null;
763 outPrintStream = null;
764 errorPrintStream = null;
765 }
766
767 /**
768 * Notify the <CODE>Redirector</CODE> that it is now okay
769 * to set any output and/or error properties.
770 */
771 public synchronized void setProperties() {
772 if (baos != null) {
773 try {
774 baos.close();
775 } catch (IOException eyeOhEx) {
776 // Ignore exception
777 }
778 }
779 if (errorBaos != null) {
780 try {
781 errorBaos.close();
782 } catch (IOException eyeOhEx) {
783 // Ignore exception
784 }
785 }
786 }
787
788 private OutputStream foldFiles(File[] file, String logHead, int loglevel) {
789 OutputStream result
790 = new LazyFileOutputStream(file[0], append, createEmptyFiles);
791
792 managingTask.log(logHead + file[0], loglevel);
793 char[] c = new char[logHead.length()];
794 Arrays.fill(c, ' ');
795 String indent = new String(c);
796
797 for (int i = 1; i < file.length; i++) {
798 outputStream = new TeeOutputStream(outputStream,
799 new LazyFileOutputStream(file[i], append, createEmptyFiles));
800 managingTask.log(indent + file[i], loglevel);
801 }
802 return result;
803 }
804}
Note: See TracBrowser for help on using the repository browser.