1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.io;
19
20import java.nio.charset.Charset;
21import java.nio.charset.IllegalCharsetNameException;
22import java.util.Arrays;
23import java.util.Formatter;
24import java.util.IllegalFormatException;
25import java.util.Locale;
26
27/**
28 * Wraps an existing {@link OutputStream} and provides convenience methods for
29 * writing common data types in a human readable format. This is not to be
30 * confused with DataOutputStream which is used for encoding common data types
31 * so that they can be read back in. No {@code IOException} is thrown by this
32 * class. Instead, callers should use {@link #checkError()} to see if a problem
33 * has occurred in this stream.
34 */
35public class PrintStream extends FilterOutputStream implements Appendable, Closeable {
36    /**
37     * indicates whether or not this PrintStream has incurred an error.
38     */
39    private boolean ioError;
40
41    /**
42     * indicates whether or not this PrintStream should flush its contents after
43     * printing a new line.
44     */
45    private boolean autoFlush;
46
47    private String encoding;
48
49    /**
50     * Constructs a new {@code PrintStream} with {@code out} as its target
51     * stream. By default, the new print stream does not automatically flush its
52     * contents to the target stream when a newline is encountered.
53     *
54     * @param out
55     *            the target output stream.
56     * @throws NullPointerException
57     *             if {@code out} is {@code null}.
58     */
59    public PrintStream(OutputStream out) {
60        super(out);
61        if (out == null) {
62            throw new NullPointerException("out == null");
63        }
64    }
65
66    /**
67     * Constructs a new {@code PrintStream} with {@code out} as its target
68     * stream. The parameter {@code autoFlush} determines if the print stream
69     * automatically flushes its contents to the target stream when a newline is
70     * encountered.
71     *
72     * @param out
73     *            the target output stream.
74     * @param autoFlush
75     *            indicates whether to flush contents upon encountering a
76     *            newline sequence.
77     * @throws NullPointerException
78     *             if {@code out} is {@code null}.
79     */
80    public PrintStream(OutputStream out, boolean autoFlush) {
81        super(out);
82        if (out == null) {
83            throw new NullPointerException("out == null");
84        }
85        this.autoFlush = autoFlush;
86    }
87
88    /**
89     * Constructs a new {@code PrintStream} with {@code out} as its target
90     * stream and using the character encoding {@code charsetName} while writing. The
91     * parameter {@code autoFlush} determines if the print stream automatically
92     * flushes its contents to the target stream when a newline is encountered.
93     *
94     * @param out
95     *            the target output stream.
96     * @param autoFlush
97     *            indicates whether or not to flush contents upon encountering a
98     *            newline sequence.
99     * @param charsetName
100     *            the non-null string describing the desired character encoding.
101     * @throws NullPointerException
102     *             if {@code out} or {@code charsetName} are {@code null}.
103     * @throws UnsupportedEncodingException
104     *             if the encoding specified by {@code charsetName} is not supported.
105     */
106    public PrintStream(OutputStream out, boolean autoFlush, String charsetName)
107            throws UnsupportedEncodingException {
108        super(out);
109        if (out == null) {
110            throw new NullPointerException("out == null");
111        } else if (charsetName == null) {
112            throw new NullPointerException("charsetName == null");
113        }
114        this.autoFlush = autoFlush;
115        try {
116            if (!Charset.isSupported(charsetName)) {
117                throw new UnsupportedEncodingException(charsetName);
118            }
119        } catch (IllegalCharsetNameException e) {
120            throw new UnsupportedEncodingException(charsetName);
121        }
122        encoding = charsetName;
123    }
124
125    /**
126     * Constructs a new {@code PrintStream} with {@code file} as its target. The
127     * VM's default character set is used for character encoding.
128     *
129     * @param file
130     *            the target file. If the file already exists, its contents are
131     *            removed, otherwise a new file is created.
132     * @throws FileNotFoundException
133     *             if an error occurs while opening or creating the target file.
134     */
135    public PrintStream(File file) throws FileNotFoundException {
136        super(new FileOutputStream(file));
137    }
138
139    /**
140     * Constructs a new {@code PrintStream} with {@code file} as its target. The
141     * character set named {@code charsetName} is used for character encoding.
142     *
143     * @param file
144     *            the target file. If the file already exists, its contents are
145     *            removed, otherwise a new file is created.
146     * @param charsetName
147     *            the name of the character set used for character encoding.
148     * @throws FileNotFoundException
149     *             if an error occurs while opening or creating the target file.
150     * @throws NullPointerException
151     *             if {@code charsetName} is {@code null}.
152     * @throws UnsupportedEncodingException
153     *             if the encoding specified by {@code charsetName} is not supported.
154     */
155    public PrintStream(File file, String charsetName) throws FileNotFoundException,
156            UnsupportedEncodingException {
157        super(new FileOutputStream(file));
158        if (charsetName == null) {
159            throw new NullPointerException("charsetName == null");
160        }
161        if (!Charset.isSupported(charsetName)) {
162            throw new UnsupportedEncodingException(charsetName);
163        }
164        encoding = charsetName;
165    }
166
167    /**
168     * Constructs a new {@code PrintStream} with the file identified by
169     * {@code fileName} as its target. The VM's default character
170     * set is used for character encoding.
171     *
172     * @param fileName
173     *            the target file's name. If the file already exists, its
174     *            contents are removed, otherwise a new file is created.
175     * @throws FileNotFoundException
176     *             if an error occurs while opening or creating the target file.
177     */
178    public PrintStream(String fileName) throws FileNotFoundException {
179        this(new File(fileName));
180    }
181
182    /**
183     * Constructs a new {@code PrintStream} with the file identified by
184     * {@code fileName} as its target. The character set named {@code charsetName} is
185     * used for character encoding.
186     *
187     * @param fileName
188     *            the target file's name. If the file already exists, its
189     *            contents are removed, otherwise a new file is created.
190     * @param charsetName
191     *            the name of the character set used for character encoding.
192     * @throws FileNotFoundException
193     *             if an error occurs while opening or creating the target file.
194     * @throws NullPointerException
195     *             if {@code charsetName} is {@code null}.
196     * @throws UnsupportedEncodingException
197     *             if the encoding specified by {@code charsetName} is not supported.
198     */
199    public PrintStream(String fileName, String charsetName)
200            throws FileNotFoundException, UnsupportedEncodingException {
201        this(new File(fileName), charsetName);
202    }
203
204    /**
205     * Flushes this stream and returns the value of the error flag.
206     *
207     * @return {@code true} if either an {@code IOException} has been thrown
208     *         previously or if {@code setError()} has been called;
209     *         {@code false} otherwise.
210     * @see #setError()
211     */
212    public boolean checkError() {
213        OutputStream delegate = out;
214        if (delegate == null) {
215            return ioError;
216        }
217
218        flush();
219        return ioError || delegate.checkError();
220    }
221
222    /**
223     * Sets the error state of the stream to false.
224     * @since 1.6
225     */
226    protected void clearError() {
227        ioError = false;
228    }
229
230    /**
231     * Closes this print stream. Flushes this stream and then closes the target
232     * stream. If an I/O error occurs, this stream's error state is set to
233     * {@code true}.
234     */
235    @Override
236    public synchronized void close() {
237        flush();
238        if (out != null) {
239            try {
240                out.close();
241                out = null;
242            } catch (IOException e) {
243                setError();
244            }
245        }
246    }
247
248    /**
249     * Ensures that all pending data is sent out to the target stream. It also
250     * flushes the target stream. If an I/O error occurs, this stream's error
251     * state is set to {@code true}.
252     */
253    @Override
254    public synchronized void flush() {
255        if (out != null) {
256            try {
257                out.flush();
258                return;
259            } catch (IOException e) {
260                // Ignored, fall through to setError
261            }
262        }
263        setError();
264    }
265
266    /**
267     * Formats {@code args} according to the format string {@code format}, and writes the result
268     * to this stream. This method uses the user's default locale.
269     * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".
270     *
271     * @param format the format string (see {@link java.util.Formatter#format})
272     * @param args
273     *            the list of arguments passed to the formatter. If there are
274     *            more arguments than required by {@code format},
275     *            additional arguments are ignored.
276     * @return this stream.
277     * @throws IllegalFormatException
278     *             if the format string is illegal or incompatible with the
279     *             arguments, if there are not enough arguments or if any other
280     *             error regarding the format string or arguments is detected.
281     * @throws NullPointerException if {@code format == null}
282     */
283    public PrintStream format(String format, Object... args) {
284        return format(Locale.getDefault(), format, args);
285    }
286
287    /**
288     * Writes a string formatted by an intermediate {@link Formatter} to this
289     * stream using the specified locale, format string and arguments.
290     *
291     * @param l
292     *            the locale used in the method. No localization will be applied
293     *            if {@code l} is {@code null}.
294     * @param format the format string (see {@link java.util.Formatter#format})
295     * @param args
296     *            the list of arguments passed to the formatter. If there are
297     *            more arguments than required by {@code format},
298     *            additional arguments are ignored.
299     * @return this stream.
300     * @throws IllegalFormatException
301     *             if the format string is illegal or incompatible with the
302     *             arguments, if there are not enough arguments or if any other
303     *             error regarding the format string or arguments is detected.
304     * @throws NullPointerException if {@code format == null}
305     */
306    public PrintStream format(Locale l, String format, Object... args) {
307        if (format == null) {
308            throw new NullPointerException("format == null");
309        }
310        new Formatter(this, l).format(format, args);
311        return this;
312    }
313
314    /**
315     * Prints a formatted string. The behavior of this method is the same as
316     * this stream's {@code #format(String, Object...)} method.
317     *
318     * <p>The {@code Locale} used is the user's default locale.
319     * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".
320     *
321     * @param format the format string (see {@link java.util.Formatter#format})
322     * @param args
323     *            the list of arguments passed to the formatter. If there are
324     *            more arguments than required by {@code format},
325     *            additional arguments are ignored.
326     * @return this stream.
327     * @throws IllegalFormatException
328     *             if the format string is illegal or incompatible with the
329     *             arguments, if there are not enough arguments or if any other
330     *             error regarding the format string or arguments is detected.
331     * @throws NullPointerException if {@code format == null}
332     */
333    public PrintStream printf(String format, Object... args) {
334        return format(format, args);
335    }
336
337    /**
338     * Prints a formatted string. The behavior of this method is the same as
339     * this stream's {@code #format(Locale, String, Object...)} method.
340     *
341     * @param l
342     *            the locale used in the method. No localization will be applied
343     *            if {@code l} is {@code null}.
344     * @param format the format string (see {@link java.util.Formatter#format})
345     * @param args
346     *            the list of arguments passed to the formatter. If there are
347     *            more arguments than required by {@code format},
348     *            additional arguments are ignored.
349     * @return this stream.
350     * @throws IllegalFormatException
351     *             if the format string is illegal or incompatible with the
352     *             arguments, if there are not enough arguments or if any other
353     *             error regarding the format string or arguments is detected.
354     * @throws NullPointerException if {@code format == null}.
355     */
356    public PrintStream printf(Locale l, String format, Object... args) {
357        return format(l, format, args);
358    }
359
360    /**
361     * Put the line separator String onto the print stream.
362     */
363    private void newline() {
364        print(System.lineSeparator());
365    }
366
367    /**
368     * Prints the string representation of the character array {@code chars}.
369     */
370    public void print(char[] chars) {
371        print(new String(chars, 0, chars.length));
372    }
373
374    /**
375     * Prints the string representation of the char {@code c}.
376     */
377    public void print(char c) {
378        print(String.valueOf(c));
379    }
380
381    /**
382     * Prints the string representation of the double {@code d}.
383     */
384    public void print(double d) {
385        print(String.valueOf(d));
386    }
387
388    /**
389     * Prints the string representation of the float {@code f}.
390     */
391    public void print(float f) {
392        print(String.valueOf(f));
393    }
394
395    /**
396     * Prints the string representation of the int {@code i}.
397     */
398    public void print(int i) {
399        print(String.valueOf(i));
400    }
401
402    /**
403     * Prints the string representation of the long {@code l}.
404     */
405    public void print(long l) {
406        print(String.valueOf(l));
407    }
408
409    /**
410     * Prints the string representation of the Object {@code o}, or {@code "null"}.
411     */
412    public void print(Object o) {
413        print(String.valueOf(o));
414    }
415
416    /**
417     * Prints a string to the target stream. The string is converted to an array
418     * of bytes using the encoding chosen during the construction of this
419     * stream. The bytes are then written to the target stream with
420     * {@code write(int)}.
421     *
422     * <p>If an I/O error occurs, this stream's error state is set to {@code true}.
423     *
424     * @param str
425     *            the string to print to the target stream.
426     * @see #write(int)
427     */
428    public synchronized void print(String str) {
429        if (out == null) {
430            setError();
431            return;
432        }
433        if (str == null) {
434            print("null");
435            return;
436        }
437
438        try {
439            if (encoding == null) {
440                write(str.getBytes());
441            } else {
442                write(str.getBytes(encoding));
443            }
444        } catch (IOException e) {
445            setError();
446        }
447    }
448
449    /**
450     * Prints the string representation of the boolean {@code b}.
451     */
452    public void print(boolean b) {
453        print(String.valueOf(b));
454    }
455
456    /**
457     * Prints a newline.
458     */
459    public void println() {
460        newline();
461    }
462
463    /**
464     * Prints the string representation of the character array {@code chars} followed by a newline.
465     */
466    public void println(char[] chars) {
467        println(new String(chars, 0, chars.length));
468    }
469
470    /**
471     * Prints the string representation of the char {@code c} followed by a newline.
472     */
473    public void println(char c) {
474        println(String.valueOf(c));
475    }
476
477    /**
478     * Prints the string representation of the double {@code d} followed by a newline.
479     */
480    public void println(double d) {
481        println(String.valueOf(d));
482    }
483
484    /**
485     * Prints the string representation of the float {@code f} followed by a newline.
486     */
487   public void println(float f) {
488        println(String.valueOf(f));
489    }
490
491   /**
492     * Prints the string representation of the int {@code i} followed by a newline.
493     */
494    public void println(int i) {
495        println(String.valueOf(i));
496    }
497
498    /**
499     * Prints the string representation of the long {@code l} followed by a newline.
500     */
501    public void println(long l) {
502        println(String.valueOf(l));
503    }
504
505    /**
506     * Prints the string representation of the Object {@code o}, or {@code "null"},
507     * followed by a newline.
508     */
509    public void println(Object o) {
510        println(String.valueOf(o));
511    }
512
513    /**
514     * Prints a string followed by a newline. The string is converted to an array of bytes using
515     * the encoding chosen during the construction of this stream. The bytes are
516     * then written to the target stream with {@code write(int)}.
517     *
518     * <p>If an I/O error occurs, this stream's error state is set to {@code true}.
519     *
520     * @param str
521     *            the string to print to the target stream.
522     * @see #write(int)
523     */
524    public synchronized void println(String str) {
525        print(str);
526        newline();
527    }
528
529    /**
530     * Prints the string representation of the boolean {@code b} followed by a newline.
531     */
532    public void println(boolean b) {
533        println(String.valueOf(b));
534    }
535
536    /**
537     * Sets the error flag of this print stream to true.
538     */
539    protected void setError() {
540        ioError = true;
541    }
542
543    /**
544     * Writes {@code count} bytes from {@code buffer} starting at {@code offset}
545     * to the target stream. If autoFlush is set, this stream gets flushed after
546     * writing the buffer.
547     *
548     * <p>This stream's error flag is set to {@code true} if this stream is closed
549     * or an I/O error occurs.
550     *
551     * @param buffer
552     *            the buffer to be written.
553     * @param offset
554     *            the index of the first byte in {@code buffer} to write.
555     * @param length
556     *            the number of bytes in {@code buffer} to write.
557     * @throws IndexOutOfBoundsException
558     *             if {@code offset < 0} or {@code count < 0}, or if {@code
559     *             offset + count} is bigger than the length of {@code buffer}.
560     * @see #flush()
561     */
562    @Override
563    public void write(byte[] buffer, int offset, int length) {
564        Arrays.checkOffsetAndCount(buffer.length, offset, length);
565        synchronized (this) {
566            if (out == null) {
567                setError();
568                return;
569            }
570            try {
571                out.write(buffer, offset, length);
572                if (autoFlush) {
573                    flush();
574                }
575            } catch (IOException e) {
576                setError();
577            }
578        }
579    }
580
581    /**
582     * Writes one byte to the target stream. Only the least significant byte of
583     * the integer {@code oneByte} is written. This stream is flushed if
584     * {@code oneByte} is equal to the character {@code '\n'} and this stream is
585     * set to autoFlush.
586     * <p>
587     * This stream's error flag is set to {@code true} if it is closed or an I/O
588     * error occurs.
589     *
590     * @param oneByte
591     *            the byte to be written
592     */
593    @Override
594    public synchronized void write(int oneByte) {
595        if (out == null) {
596            setError();
597            return;
598        }
599        try {
600            out.write(oneByte);
601            int b = oneByte & 0xFF;
602            // 0x0A is ASCII newline, 0x15 is EBCDIC newline.
603            boolean isNewline = b == 0x0A || b == 0x15;
604            if (autoFlush && isNewline) {
605                flush();
606            }
607        } catch (IOException e) {
608            setError();
609        }
610    }
611
612    /**
613     * Appends the char {@code c}.
614     * @return this stream.
615     */
616    public PrintStream append(char c) {
617        print(c);
618        return this;
619    }
620
621    /**
622     * Appends the CharSequence {@code charSequence}, or {@code "null"}.
623     * @return this stream.
624     */
625    public PrintStream append(CharSequence charSequence) {
626        if (charSequence == null) {
627            print("null");
628        } else {
629            print(charSequence.toString());
630        }
631        return this;
632    }
633
634    /**
635     * Appends a subsequence of CharSequence {@code charSequence}, or {@code "null"}.
636     *
637     * @param charSequence
638     *            the character sequence appended to the target stream.
639     * @param start
640     *            the index of the first char in the character sequence appended
641     *            to the target stream.
642     * @param end
643     *            the index of the character following the last character of the
644     *            subsequence appended to the target stream.
645     * @return this stream.
646     * @throws IndexOutOfBoundsException
647     *             if {@code start > end}, {@code start < 0}, {@code end < 0} or
648     *             either {@code start} or {@code end} are greater or equal than
649     *             the length of {@code charSequence}.
650     */
651    public PrintStream append(CharSequence charSequence, int start, int end) {
652        if (charSequence == null) {
653            charSequence = "null";
654        }
655        print(charSequence.subSequence(start, end).toString());
656        return this;
657    }
658}
659