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 org.apache.harmony.testframework;
19
20import junit.framework.TestCase;
21import junit.framework.TestSuite;
22
23import java.io.IOException;
24import java.io.Writer;
25
26/**
27 * Tests behaviour common to wrapping and filtering implementations of {@link
28 * Writer}.
29 */
30public abstract class CharWrapperTester {
31
32    private boolean throwsExceptions = true;
33
34    /**
35     * Creates a new output stream that receives one stream of chars, optionally
36     * transforms it, and emits another stream of chars to {@code delegate}.
37     */
38    public abstract Writer create(Writer delegate) throws Exception;
39
40    /**
41     * Decodes the chars received by the delegate into their original form: the
42     * chars originally received by this wrapper.
43     */
44    public abstract char[] decode(char[] delegateChars) throws Exception;
45
46    /**
47     * Configures whether the writer is expected to throw exceptions when an
48     * error is encountered. Classes like {@code PrintWriter} report errors via
49     * an API method instead.
50     */
51    public CharWrapperTester setThrowsExceptions(boolean throwsExceptions) {
52        this.throwsExceptions = throwsExceptions;
53        return this;
54    }
55
56    public final TestSuite createTests() {
57        TestSuite result = new TestSuite();
58        result.addTest(new WrapperSinkTester()
59                .setThrowsExceptions(throwsExceptions)
60                .createTests());
61
62        if (throwsExceptions) {
63            result.addTest(new WrapperTestCase("wrapperTestFlushThrowsViaFlush"));
64            result.addTest(new WrapperTestCase("wrapperTestFlushThrowsViaClose"));
65            result.addTest(new WrapperTestCase("wrapperTestCloseThrows"));
66        } else {
67            result.addTest(new WrapperTestCase("wrapperTestFlushThrowsViaFlushSuppressed"));
68            result.addTest(new WrapperTestCase("wrapperTestFlushThrowsViaCloseSuppressed"));
69            result.addTest(new WrapperTestCase("wrapperTestCloseThrowsSuppressed"));
70        }
71
72        return result;
73    }
74
75    @Override public String toString() {
76        return getClass().getName();
77    }
78
79    private class WrapperSinkTester extends CharSinkTester {
80        private ClosableStringWriter delegate;
81
82        @Override public Writer create() throws Exception {
83            delegate = new ClosableStringWriter();
84            return CharWrapperTester.this.create(delegate);
85        }
86
87        @Override public char[] getChars() throws Exception {
88            return decode(delegate.buffer.toString().toCharArray());
89        }
90
91        @Override public String toString() {
92            return CharWrapperTester.this.toString();
93        }
94    }
95
96    public class WrapperTestCase extends TestCase {
97
98        private WrapperTestCase(String name) {
99            super(name);
100        }
101
102        @Override public String getName() {
103            return CharWrapperTester.this.toString() + ":" + super.getName();
104        }
105
106        public void wrapperTestFlushThrowsViaFlushSuppressed() throws Exception {
107            FailOnFlushWriter delegate = new FailOnFlushWriter();
108            Writer o = create(delegate);
109            o.write("BUT");
110            o.write("TERS");
111            o.flush();
112            assertTrue(delegate.flushed);
113        }
114
115        public void wrapperTestFlushThrowsViaCloseSuppressed() throws Exception {
116            FailOnFlushWriter delegate = new FailOnFlushWriter();
117            Writer o = create(delegate);
118            o.write("BUT");
119            o.write("TERS");
120            o.close();
121            assertTrue(delegate.flushed);
122        }
123
124        public void wrapperTestFlushThrowsViaFlush() throws Exception {
125            FailOnFlushWriter delegate = new FailOnFlushWriter();
126
127            Writer o = create(delegate);
128            try {
129                // any of these is permitted to flush
130                o.write("BUT");
131                o.write("TERS");
132                o.flush();
133                assertTrue(delegate.flushed);
134                fail("flush exception ignored");
135            } catch (IOException expected) {
136                assertEquals("Flush failed" , expected.getMessage());
137            }
138        }
139
140        public void wrapperTestFlushThrowsViaClose() throws Exception {
141            FailOnFlushWriter delegate = new FailOnFlushWriter();
142
143            Writer o = create(delegate);
144            try {
145                // any of these is permitted to flush
146                o.write("BUT");
147                o.write("TERS");
148                o.close();
149                assertTrue(delegate.flushed);
150                fail("flush exception ignored");
151            } catch (IOException expected) {
152                assertEquals("Flush failed" , expected.getMessage());
153            }
154
155            try {
156                o.write("BARK");
157                fail("expected already closed exception");
158            } catch (IOException expected) {
159            }
160        }
161
162        public void wrapperTestCloseThrows() throws Exception {
163            FailOnCloseWriter delegate = new FailOnCloseWriter();
164            Writer o = create(delegate);
165            try {
166                o.close();
167                assertTrue(delegate.closed);
168                fail("close exception ignored");
169            } catch (IOException expected) {
170                assertEquals("Close failed" , expected.getMessage());
171            }
172        }
173
174        public void wrapperTestCloseThrowsSuppressed() throws Exception {
175            FailOnCloseWriter delegate = new FailOnCloseWriter();
176            Writer o = create(delegate);
177            o.close();
178            assertTrue(delegate.closed);
179        }
180
181        // adding a new test? Don't forget to update createTests().
182    }
183
184    /**
185     * A custom Writer that respects the closed state. The built-in StringWriter
186     * doesn't respect close(), which makes testing wrapped streams difficult.
187     */
188    private static class ClosableStringWriter extends Writer {
189        private final StringBuilder buffer = new StringBuilder();
190        private boolean closed = false;
191
192        @Override public void close() throws IOException {
193            closed = true;
194        }
195
196        @Override public void flush() throws IOException {}
197
198        @Override public void write(char[] buf, int offset, int count) throws IOException {
199            if (closed) {
200                throw new IOException();
201            }
202            buffer.append(buf, offset, count);
203        }
204    }
205
206    private static class FailOnFlushWriter extends Writer {
207        boolean flushed = false;
208        boolean closed = false;
209
210        @Override public void write(char[] buf, int offset, int count) throws IOException {
211            if (closed) {
212                throw new IOException("Already closed");
213            }
214        }
215
216        @Override public void close() throws IOException {
217            closed = true;
218            flush();
219        }
220
221        @Override public void flush() throws IOException {
222            if (!flushed) {
223                flushed = true;
224                throw new IOException("Flush failed");
225            }
226        }
227    }
228
229    private static class FailOnCloseWriter extends Writer {
230        boolean closed = false;
231
232        @Override public void flush() throws IOException {}
233
234        @Override public void write(char[] buf, int offset, int count) throws IOException {}
235
236        @Override public void close() throws IOException {
237            closed = true;
238            throw new IOException("Close failed");
239        }
240    }
241}