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
76    public String toString() {
77        return getClass().getName();
78    }
79
80    private class WrapperSinkTester extends CharSinkTester {
81        private ClosableStringWriter delegate;
82
83        @Override
84        public Writer create() throws Exception {
85            delegate = new ClosableStringWriter();
86            return CharWrapperTester.this.create(delegate);
87        }
88
89        @Override
90        public char[] getChars() throws Exception {
91            return decode(delegate.buffer.toString().toCharArray());
92        }
93
94        @Override
95        public String toString() {
96            return CharWrapperTester.this.toString();
97        }
98    }
99
100    public class WrapperTestCase extends TestCase {
101
102        private WrapperTestCase(String name) {
103            super(name);
104        }
105
106        @Override
107        public String getName() {
108            return CharWrapperTester.this.toString() + ":" + super.getName();
109        }
110
111        public void wrapperTestFlushThrowsViaFlushSuppressed() throws Exception {
112            FailOnFlushWriter delegate = new FailOnFlushWriter();
113            Writer o = create(delegate);
114            o.write("BUT");
115            o.write("TERS");
116            o.flush();
117            assertTrue(delegate.flushed);
118        }
119
120        public void wrapperTestFlushThrowsViaCloseSuppressed() throws Exception {
121            FailOnFlushWriter delegate = new FailOnFlushWriter();
122            Writer o = create(delegate);
123            o.write("BUT");
124            o.write("TERS");
125            o.close();
126            assertTrue(delegate.flushed);
127        }
128
129        public void wrapperTestFlushThrowsViaFlush() throws Exception {
130            FailOnFlushWriter delegate = new FailOnFlushWriter();
131
132            Writer o = create(delegate);
133            try {
134                // any of these is permitted to flush
135                o.write("BUT");
136                o.write("TERS");
137                o.flush();
138                assertTrue(delegate.flushed);
139                fail("flush exception ignored");
140            } catch (IOException expected) {
141                assertEquals("Flush failed", expected.getMessage());
142            }
143        }
144
145        public void wrapperTestFlushThrowsViaClose() throws Exception {
146            FailOnFlushWriter delegate = new FailOnFlushWriter();
147
148            Writer o = create(delegate);
149            try {
150                // any of these is permitted to flush
151                o.write("BUT");
152                o.write("TERS");
153                o.close();
154                assertTrue(delegate.flushed);
155                fail("flush exception ignored");
156            } catch (IOException expected) {
157                assertEquals("Flush failed", expected.getMessage());
158            }
159
160            try {
161                o.write("BARK");
162                fail("expected already closed exception");
163            } catch (IOException expected) {
164            }
165        }
166
167        public void wrapperTestCloseThrows() throws Exception {
168            FailOnCloseWriter delegate = new FailOnCloseWriter();
169            Writer o = create(delegate);
170            try {
171                o.close();
172                assertTrue(delegate.closed);
173                fail("close exception ignored");
174            } catch (IOException expected) {
175                assertEquals("Close failed", expected.getMessage());
176            }
177        }
178
179        public void wrapperTestCloseThrowsSuppressed() throws Exception {
180            FailOnCloseWriter delegate = new FailOnCloseWriter();
181            Writer o = create(delegate);
182            o.close();
183            assertTrue(delegate.closed);
184        }
185
186        // adding a new test? Don't forget to update createTests().
187    }
188
189    /**
190     * A custom Writer that respects the closed state. The built-in StringWriter
191     * doesn't respect close(), which makes testing wrapped streams difficult.
192     */
193    private static class ClosableStringWriter extends Writer {
194        private final StringBuilder buffer = new StringBuilder();
195        private boolean closed = false;
196
197        @Override
198        public void close() throws IOException {
199            closed = true;
200        }
201
202        @Override
203        public void flush() throws IOException {
204        }
205
206        @Override
207        public void write(char[] buf, int offset, int count) throws IOException {
208            if (closed) {
209                throw new IOException();
210            }
211            buffer.append(buf, offset, count);
212        }
213    }
214
215    private static class FailOnFlushWriter extends Writer {
216        boolean flushed = false;
217        boolean closed = false;
218
219        @Override
220        public void write(char[] buf, int offset, int count) throws IOException {
221            if (closed) {
222                throw new IOException("Already closed");
223            }
224        }
225
226        @Override
227        public void close() throws IOException {
228            closed = true;
229            flush();
230        }
231
232        @Override
233        public void flush() throws IOException {
234            if (!flushed) {
235                flushed = true;
236                throw new IOException("Flush failed");
237            }
238        }
239    }
240
241    private static class FailOnCloseWriter extends Writer {
242        boolean closed = false;
243
244        @Override
245        public void flush() throws IOException {
246        }
247
248        @Override
249        public void write(char[] buf, int offset, int count) throws IOException {
250        }
251
252        @Override
253        public void close() throws IOException {
254            closed = true;
255            throw new IOException("Close failed");
256        }
257    }
258}