1/*
2 * Copyright (C) 2012 The Guava Authors
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 com.google.common.io;
18
19import static com.google.common.base.Preconditions.checkNotNull;
20import static com.google.common.io.SourceSinkFactory.ByteSinkFactory;
21import static com.google.common.io.SourceSinkFactory.ByteSourceFactory;
22import static com.google.common.io.SourceSinkFactory.CharSinkFactory;
23import static com.google.common.io.SourceSinkFactory.CharSourceFactory;
24
25import com.google.common.base.Charsets;
26import com.google.common.jdk5backport.Arrays;
27
28import java.io.ByteArrayOutputStream;
29import java.io.File;
30import java.io.FileInputStream;
31import java.io.FileOutputStream;
32import java.io.IOException;
33import java.io.InputStream;
34import java.io.InputStreamReader;
35import java.io.OutputStream;
36import java.io.OutputStreamWriter;
37import java.io.Reader;
38import java.io.UnsupportedEncodingException;
39import java.io.Writer;
40import java.nio.CharBuffer;
41import java.util.logging.Logger;
42
43import javax.annotation.Nullable;
44
45/**
46 * {@link SourceSinkFactory} implementations.
47 *
48 * @author Colin Decker
49 */
50public class SourceSinkFactories {
51
52  private SourceSinkFactories() {}
53
54  public static CharSourceFactory stringCharSourceFactory() {
55    return new StringSourceFactory();
56  }
57
58  public static ByteSourceFactory byteArraySourceFactory() {
59    return new ByteArraySourceFactory();
60  }
61
62  public static ByteSourceFactory emptyByteSourceFactory() {
63    return new EmptyByteSourceFactory();
64  }
65
66  public static CharSourceFactory emptyCharSourceFactory() {
67    return new EmptyCharSourceFactory();
68  }
69
70  public static ByteSourceFactory fileByteSourceFactory() {
71    return new FileByteSourceFactory();
72  }
73
74  public static ByteSinkFactory fileByteSinkFactory() {
75    return new FileByteSinkFactory(null);
76  }
77
78  public static ByteSinkFactory appendingFileByteSinkFactory() {
79    String initialString = IoTestCase.ASCII + IoTestCase.I18N;
80    try {
81      return new FileByteSinkFactory(initialString.getBytes(Charsets.UTF_8.name()));
82    } catch (UnsupportedEncodingException e) {
83      throw new AssertionError(e);
84    }
85  }
86
87  public static CharSourceFactory fileCharSourceFactory() {
88    return new FileCharSourceFactory();
89  }
90
91  public static CharSinkFactory fileCharSinkFactory() {
92    return new FileCharSinkFactory(null);
93  }
94
95  public static CharSinkFactory appendingFileCharSinkFactory() {
96    String initialString = IoTestCase.ASCII + IoTestCase.I18N;
97    return new FileCharSinkFactory(initialString);
98  }
99
100  public static ByteSourceFactory urlByteSourceFactory() {
101    return new UrlByteSourceFactory();
102  }
103
104  public static CharSourceFactory urlCharSourceFactory() {
105    return new UrlCharSourceFactory();
106  }
107
108  public static CharSourceFactory asCharSourceFactory(final ByteSourceFactory factory) {
109    checkNotNull(factory);
110    return new CharSourceFactory() {
111      @Override
112      public CharSource createSource(String string) throws IOException {
113        return factory.createSource(string.getBytes(Charsets.UTF_8.name()))
114            .asCharSource(Charsets.UTF_8);
115      }
116
117      @Override
118      public String getExpected(String data) {
119        try {
120          return new String(factory.getExpected(data.getBytes(Charsets.UTF_8.name())),
121              Charsets.UTF_8.name());
122        } catch (UnsupportedEncodingException e) {
123          throw new AssertionError();
124        }
125      }
126
127      @Override
128      public void tearDown() throws IOException {
129        factory.tearDown();
130      }
131    };
132  }
133
134  public static CharSinkFactory asCharSinkFactory(final ByteSinkFactory factory) {
135    checkNotNull(factory);
136    return new CharSinkFactory() {
137      @Override
138      public CharSink createSink() throws IOException {
139        return factory.createSink().asCharSink(Charsets.UTF_8);
140      }
141
142      @Override
143      public String getSinkContents() throws IOException {
144        return new String(factory.getSinkContents(), Charsets.UTF_8.name());
145      }
146
147      @Override
148      public String getExpected(String data) {
149        /*
150         * Get what the byte sink factory would expect for no written bytes, then append expected
151         * string to that.
152         */
153        byte[] factoryExpectedForNothing = factory.getExpected(new byte[0]);
154        try {
155          return new String(factoryExpectedForNothing, Charsets.UTF_8.name()) + checkNotNull(data);
156        } catch (UnsupportedEncodingException e) {
157          throw new AssertionError();
158        }
159      }
160
161      @Override
162      public void tearDown() throws IOException {
163        factory.tearDown();
164      }
165    };
166  }
167
168  public static ByteSourceFactory asSlicedByteSourceFactory(final ByteSourceFactory factory,
169      final int off, final int len) {
170    checkNotNull(factory);
171    return new ByteSourceFactory() {
172      @Override
173      public ByteSource createSource(byte[] bytes) throws IOException {
174        return factory.createSource(bytes).slice(off, len);
175      }
176
177      @Override
178      public byte[] getExpected(byte[] bytes) {
179        byte[] baseExpected = factory.getExpected(bytes);
180        return Arrays.copyOfRange(baseExpected, off, Math.min(baseExpected.length, off + len));
181      }
182
183      @Override
184      public void tearDown() throws IOException {
185        factory.tearDown();
186      }
187    };
188  }
189
190  private static class StringSourceFactory implements CharSourceFactory {
191
192    @Override
193    public CharSource createSource(String data) throws IOException {
194      return CharSource.wrap(data);
195    }
196
197    @Override
198    public String getExpected(String data) {
199      return data;
200    }
201
202    @Override
203    public void tearDown() throws IOException {
204    }
205  }
206
207  private static class ByteArraySourceFactory implements ByteSourceFactory {
208
209    @Override
210    public ByteSource createSource(byte[] bytes) throws IOException {
211      return ByteSource.wrap(bytes);
212    }
213
214    @Override
215    public byte[] getExpected(byte[] bytes) {
216      return bytes;
217    }
218
219    @Override
220    public void tearDown() throws IOException {
221    }
222  }
223
224  private static class EmptyCharSourceFactory implements CharSourceFactory {
225
226    @Override
227    public CharSource createSource(String data) throws IOException {
228      return CharSource.empty();
229    }
230
231    @Override
232    public String getExpected(String data) {
233      return "";
234    }
235
236    @Override
237    public void tearDown() throws IOException {
238    }
239  }
240
241  private static class EmptyByteSourceFactory implements ByteSourceFactory {
242
243    @Override
244    public ByteSource createSource(byte[] bytes) throws IOException {
245      return ByteSource.empty();
246    }
247
248    @Override
249    public byte[] getExpected(byte[] bytes) {
250      return new byte[0];
251    }
252
253    @Override
254    public void tearDown() throws IOException {
255    }
256  }
257
258  private abstract static class FileFactory {
259
260    private static final Logger logger = Logger.getLogger(FileFactory.class.getName());
261
262    private final ThreadLocal<File> fileThreadLocal = new ThreadLocal<File>();
263
264    protected File createFile() throws IOException {
265      File file = File.createTempFile("SinkSourceFile", "txt");
266      fileThreadLocal.set(file);
267      return file;
268    }
269
270    protected File getFile() {
271      return fileThreadLocal.get();
272    }
273
274    public final void tearDown() throws IOException {
275      if (!fileThreadLocal.get().delete()) {
276        logger.warning("Unable to delete file: " + fileThreadLocal.get());
277      }
278      fileThreadLocal.remove();
279    }
280  }
281
282  private static class FileByteSourceFactory extends FileFactory implements ByteSourceFactory {
283
284    @Override
285    public ByteSource createSource(byte[] bytes) throws IOException {
286      checkNotNull(bytes);
287      File file = createFile();
288      OutputStream out = new FileOutputStream(file);
289      try {
290        out.write(bytes);
291      } finally {
292        out.close();
293      }
294      return Files.asByteSource(file);
295    }
296
297    @Override
298    public byte[] getExpected(byte[] bytes) {
299      return checkNotNull(bytes);
300    }
301  }
302
303  private static class FileByteSinkFactory extends FileFactory implements ByteSinkFactory {
304
305    private final byte[] initialBytes;
306
307    private FileByteSinkFactory(@Nullable byte[] initialBytes) {
308      this.initialBytes = initialBytes;
309    }
310
311    @Override
312    public ByteSink createSink() throws IOException {
313      File file = createFile();
314      if (initialBytes != null) {
315        FileOutputStream out = new FileOutputStream(file);
316        try {
317          out.write(initialBytes);
318        } finally {
319          out.close();
320        }
321        return Files.asByteSink(file, FileWriteMode.APPEND);
322      }
323      return Files.asByteSink(file);
324    }
325
326    @Override
327    public byte[] getExpected(byte[] bytes) {
328      if (initialBytes == null) {
329        return checkNotNull(bytes);
330      } else {
331        byte[] result = new byte[initialBytes.length + bytes.length];
332        System.arraycopy(initialBytes, 0, result, 0, initialBytes.length);
333        System.arraycopy(bytes, 0, result, initialBytes.length, bytes.length);
334        return result;
335      }
336    }
337
338    @Override
339    public byte[] getSinkContents() throws IOException {
340      File file = getFile();
341      InputStream in = new FileInputStream(file);
342      ByteArrayOutputStream out = new ByteArrayOutputStream();
343      byte[] buffer = new byte[100];
344      int read;
345      while ((read = in.read(buffer)) != -1) {
346        out.write(buffer, 0, read);
347      }
348      return out.toByteArray();
349    }
350  }
351
352  private static class FileCharSourceFactory extends FileFactory implements CharSourceFactory {
353
354    @Override
355    public CharSource createSource(String string) throws IOException {
356      checkNotNull(string);
357      File file = createFile();
358      Writer writer = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8);
359      try {
360        writer.write(string);
361      } finally {
362        writer.close();
363      }
364      return Files.asCharSource(file, Charsets.UTF_8);
365    }
366
367    @Override
368    public String getExpected(String string) {
369      return checkNotNull(string);
370    }
371  }
372
373  private static class FileCharSinkFactory extends FileFactory implements CharSinkFactory {
374
375    private final String initialString;
376
377    private FileCharSinkFactory(@Nullable String initialString) {
378      this.initialString = initialString;
379    }
380
381    @Override
382    public CharSink createSink() throws IOException {
383      File file = createFile();
384      if (initialString != null) {
385        Writer writer = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8);
386        try {
387          writer.write(initialString);
388        } finally {
389          writer.close();
390        }
391        return Files.asCharSink(file, Charsets.UTF_8, FileWriteMode.APPEND);
392      }
393      return Files.asCharSink(file, Charsets.UTF_8);
394    }
395
396    @Override
397    public String getExpected(String string) {
398      checkNotNull(string);
399      return initialString == null
400          ? string
401          : initialString + string;
402    }
403
404    @Override
405    public String getSinkContents() throws IOException {
406      File file = getFile();
407      Reader reader = new InputStreamReader(new FileInputStream(file), Charsets.UTF_8);
408      StringBuilder builder = new StringBuilder();
409      CharBuffer buffer = CharBuffer.allocate(100);
410      while (reader.read(buffer) != -1) {
411        buffer.flip();
412        builder.append(buffer);
413        buffer.clear();
414      }
415      return builder.toString();
416    }
417  }
418
419  private static class UrlByteSourceFactory extends FileByteSourceFactory {
420
421    @Override
422    public ByteSource createSource(byte[] bytes) throws IOException {
423      super.createSource(bytes);
424      return Resources.asByteSource(getFile().toURI().toURL());
425    }
426  }
427
428  private static class UrlCharSourceFactory extends FileCharSourceFactory {
429
430    @Override
431    public CharSource createSource(String string) throws IOException {
432      super.createSource(string); // just ignore returned CharSource
433      return Resources.asCharSource(getFile().toURI().toURL(), Charsets.UTF_8);
434    }
435  }
436}
437