summaryrefslogtreecommitdiffstats
path: root/luni/src/main/java/java/util/logging/StreamHandler.java
blob: 60b4321d3177865282d29a9aad1e89301ad628d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package java.util.logging;

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

/**
 * A {@code StreamHandler} object writes log messages to an output stream, that
 * is, objects of the class {@link java.io.OutputStream}.
 * <p>
 * A {@code StreamHandler} object reads the following properties from the log
 * manager to initialize itself. A default value will be used if a property is
 * not found or has an invalid value.
 * <ul>
 * <li>java.util.logging.StreamHandler.encoding specifies the encoding this
 * handler will use to encode log messages. Default is the encoding used by the
 * current platform.
 * <li>java.util.logging.StreamHandler.filter specifies the name of the filter
 * class to be associated with this handler. No <code>Filter</code> is used by
 * default.
 * <li>java.util.logging.StreamHandler.formatter specifies the name of the
 * formatter class to be associated with this handler. Default is
 * {@code java.util.logging.SimpleFormatter}.
 * <li>java.util.logging.StreamHandler.level specifies the logging level.
 * Defaults is {@code Level.INFO}.
 * </ul>
 * <p>
 * This class is not thread-safe.
 */
public class StreamHandler extends Handler {

    // the output stream this handler writes to
    private OutputStream os;

    // the writer that writes to the output stream
    private Writer writer;

    // the flag indicating whether the writer has been initialized
    private boolean writerNotInitialized;

    /**
     * Constructs a {@code StreamHandler} object. The new stream handler
     * does not have an associated output stream.
     */
    public StreamHandler() {
        initProperties("INFO", null, "java.util.logging.SimpleFormatter", null);
        this.os = null;
        this.writer = null;
        this.writerNotInitialized = true;
    }

    /**
     * Constructs a {@code StreamHandler} object with the supplied output
     * stream. Default properties are read.
     *
     * @param os
     *            the output stream this handler writes to.
     */
    StreamHandler(OutputStream os) {
        this();
        this.os = os;
    }

    /**
     * Constructs a {@code StreamHandler} object. The specified default values
     * will be used if the corresponding properties are not found in the log
     * manager's properties.
     */
    StreamHandler(String defaultLevel, String defaultFilter,
            String defaultFormatter, String defaultEncoding) {
        initProperties(defaultLevel, defaultFilter, defaultFormatter,
                defaultEncoding);
        this.os = null;
        this.writer = null;
        this.writerNotInitialized = true;
    }

    /**
     * Constructs a {@code StreamHandler} object with the supplied output stream
     * and formatter.
     *
     * @param os
     *            the output stream this handler writes to.
     * @param formatter
     *            the formatter this handler uses to format the output.
     * @throws NullPointerException
     *             if {@code os} or {@code formatter} is {@code null}.
     */
    public StreamHandler(OutputStream os, Formatter formatter) {
        this();
        if (os == null) {
            throw new NullPointerException("os == null");
        }
        if (formatter == null) {
            throw new NullPointerException("formatter == null");
        }
        this.os = os;
        internalSetFormatter(formatter);
    }

    // initialize the writer
    private void initializeWriter() {
        this.writerNotInitialized = false;
        if (getEncoding() == null) {
            this.writer = new OutputStreamWriter(this.os);
        } else {
            try {
                this.writer = new OutputStreamWriter(this.os, getEncoding());
            } catch (UnsupportedEncodingException e) {
                /*
                 * Should not happen because it's checked in
                 * super.initProperties().
                 */
            }
        }
        write(getFormatter().getHead(this));
    }

    // Write a string to the output stream.
    private void write(String s) {
        try {
            this.writer.write(s);
        } catch (Exception e) {
            getErrorManager().error("Exception occurred when writing to the output stream", e,
                    ErrorManager.WRITE_FAILURE);
        }
    }

    /**
     * Sets the output stream this handler writes to. Note it does nothing else.
     *
     * @param newOs
     *            the new output stream
     */
    void internalSetOutputStream(OutputStream newOs) {
        this.os = newOs;
    }

    /**
     * Sets the output stream this handler writes to. If there's an existing
     * output stream, the tail string of the associated formatter will be
     * written to it. Then it will be flushed, closed and replaced with
     * {@code os}.
     *
     * @param os
     *            the new output stream.
     * @throws NullPointerException
     *             if {@code os} is {@code null}.
     */
    protected void setOutputStream(OutputStream os) {
        if (os == null) {
            throw new NullPointerException("os == null");
        }
        LogManager.getLogManager().checkAccess();
        close(true);
        this.writer = null;
        this.os = os;
        this.writerNotInitialized = true;
    }

    /**
     * Sets the character encoding used by this handler. A {@code null} value
     * indicates that the default encoding should be used.
     *
     * @param encoding
     *            the character encoding to set.
     * @throws UnsupportedEncodingException
     *             if the specified encoding is not supported by the runtime.
     */
    @Override
    public void setEncoding(String encoding) throws UnsupportedEncodingException {
        // flush first before set new encoding
        this.flush();
        super.setEncoding(encoding);
        // renew writer only if the writer exists
        if (this.writer != null) {
            if (getEncoding() == null) {
                this.writer = new OutputStreamWriter(this.os);
            } else {
                try {
                    this.writer = new OutputStreamWriter(this.os, getEncoding());
                } catch (UnsupportedEncodingException e) {
                    /*
                     * Should not happen because it's checked in
                     * super.initProperties().
                     */
                    throw new AssertionError(e);
                }
            }
        }
    }

    /**
     * Closes this handler, but the underlying output stream is only closed if
     * {@code closeStream} is {@code true}. Security is not checked.
     *
     * @param closeStream
     *            whether to close the underlying output stream.
     */
    void close(boolean closeStream) {
        if (this.os != null) {
            if (this.writerNotInitialized) {
                initializeWriter();
            }
            write(getFormatter().getTail(this));
            try {
                this.writer.flush();
                if (closeStream) {
                    this.writer.close();
                    this.writer = null;
                    this.os = null;
                }
            } catch (Exception e) {
                getErrorManager().error("Exception occurred when closing the output stream", e,
                        ErrorManager.CLOSE_FAILURE);
            }
        }
    }

    /**
     * Closes this handler. The tail string of the formatter associated with
     * this handler is written out. A flush operation and a subsequent close
     * operation is then performed upon the output stream. Client applications
     * should not use a handler after closing it.
     */
    @Override
    public void close() {
        LogManager.getLogManager().checkAccess();
        close(true);
    }

    /**
     * Flushes any buffered output.
     */
    @Override
    public void flush() {
        if (this.os != null) {
            try {
                if (this.writer != null) {
                    this.writer.flush();
                } else {
                    this.os.flush();
                }
            } catch (Exception e) {
                getErrorManager().error("Exception occurred when flushing the output stream",
                        e, ErrorManager.FLUSH_FAILURE);
            }
        }
    }

    /**
     * Accepts a logging request. The log record is formatted and written to the
     * output stream if the following three conditions are met:
     * <ul>
     * <li>the supplied log record has at least the required logging level;
     * <li>the supplied log record passes the filter associated with this
     * handler, if any;
     * <li>the output stream associated with this handler is not {@code null}.
     * </ul>
     * If it is the first time a log record is written out, the head string of
     * the formatter associated with this handler is written out first.
     *
     * @param record
     *            the log record to be logged.
     */
    @Override
    public synchronized void publish(LogRecord record) {
        try {
            if (this.isLoggable(record)) {
                if (this.writerNotInitialized) {
                    initializeWriter();
                }
                String msg = null;
                try {
                    msg = getFormatter().format(record);
                } catch (Exception e) {
                    getErrorManager().error("Exception occurred when formatting the log record",
                            e, ErrorManager.FORMAT_FAILURE);
                }
                write(msg);
            }
        } catch (Exception e) {
            getErrorManager().error("Exception occurred when logging the record", e,
                    ErrorManager.GENERIC_FAILURE);
        }
    }

    /**
     * Determines whether the supplied log record needs to be logged. The
     * logging levels are checked as well as the filter. The output stream of
     * this handler is also checked. If it is {@code null}, this method returns
     * {@code false}.
     * <p>
     * Notice : Case of no output stream will return {@code false}.
     *
     * @param record
     *            the log record to be checked.
     * @return {@code true} if {@code record} needs to be logged, {@code false}
     *         otherwise.
     */
    @Override
    public boolean isLoggable(LogRecord record) {
        if (record == null) {
            return false;
        }
        if (this.os != null && super.isLoggable(record)) {
            return true;
        }
        return false;
    }
}