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 17 package com.google.common.io; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 import static com.google.common.io.SourceSinkFactory.ByteSinkFactory; 21 import static com.google.common.io.SourceSinkFactory.ByteSourceFactory; 22 import static com.google.common.io.SourceSinkFactory.CharSinkFactory; 23 import static com.google.common.io.SourceSinkFactory.CharSourceFactory; 24 import static java.nio.charset.StandardCharsets.UTF_8; 25 26 import java.io.ByteArrayOutputStream; 27 import java.io.File; 28 import java.io.FileInputStream; 29 import java.io.FileOutputStream; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.InputStreamReader; 33 import java.io.OutputStream; 34 import java.io.OutputStreamWriter; 35 import java.io.Reader; 36 import java.io.Writer; 37 import java.nio.CharBuffer; 38 import java.nio.file.Path; 39 import java.nio.file.StandardOpenOption; 40 import java.util.Arrays; 41 import java.util.logging.Level; 42 import java.util.logging.Logger; 43 import org.checkerframework.checker.nullness.qual.Nullable; 44 45 /** 46 * {@link SourceSinkFactory} implementations. 47 * 48 * @author Colin Decker 49 */ 50 public class SourceSinkFactories { 51 SourceSinkFactories()52 private SourceSinkFactories() {} 53 stringCharSourceFactory()54 public static CharSourceFactory stringCharSourceFactory() { 55 return new StringSourceFactory(); 56 } 57 byteArraySourceFactory()58 public static ByteSourceFactory byteArraySourceFactory() { 59 return new ByteArraySourceFactory(); 60 } 61 emptyByteSourceFactory()62 public static ByteSourceFactory emptyByteSourceFactory() { 63 return new EmptyByteSourceFactory(); 64 } 65 emptyCharSourceFactory()66 public static CharSourceFactory emptyCharSourceFactory() { 67 return new EmptyCharSourceFactory(); 68 } 69 fileByteSourceFactory()70 public static ByteSourceFactory fileByteSourceFactory() { 71 return new FileByteSourceFactory(); 72 } 73 fileByteSinkFactory()74 public static ByteSinkFactory fileByteSinkFactory() { 75 return new FileByteSinkFactory(null); 76 } 77 appendingFileByteSinkFactory()78 public static ByteSinkFactory appendingFileByteSinkFactory() { 79 String initialString = IoTestCase.ASCII + IoTestCase.I18N; 80 return new FileByteSinkFactory(initialString.getBytes(UTF_8)); 81 } 82 fileCharSourceFactory()83 public static CharSourceFactory fileCharSourceFactory() { 84 return new FileCharSourceFactory(); 85 } 86 fileCharSinkFactory()87 public static CharSinkFactory fileCharSinkFactory() { 88 return new FileCharSinkFactory(null); 89 } 90 appendingFileCharSinkFactory()91 public static CharSinkFactory appendingFileCharSinkFactory() { 92 String initialString = IoTestCase.ASCII + IoTestCase.I18N; 93 return new FileCharSinkFactory(initialString); 94 } 95 urlByteSourceFactory()96 public static ByteSourceFactory urlByteSourceFactory() { 97 return new UrlByteSourceFactory(); 98 } 99 urlCharSourceFactory()100 public static CharSourceFactory urlCharSourceFactory() { 101 return new UrlCharSourceFactory(); 102 } 103 104 @AndroidIncompatible pathByteSourceFactory()105 public static ByteSourceFactory pathByteSourceFactory() { 106 return new PathByteSourceFactory(); 107 } 108 109 @AndroidIncompatible pathByteSinkFactory()110 public static ByteSinkFactory pathByteSinkFactory() { 111 return new PathByteSinkFactory(null); 112 } 113 114 @AndroidIncompatible appendingPathByteSinkFactory()115 public static ByteSinkFactory appendingPathByteSinkFactory() { 116 String initialString = IoTestCase.ASCII + IoTestCase.I18N; 117 return new PathByteSinkFactory(initialString.getBytes(UTF_8)); 118 } 119 120 @AndroidIncompatible pathCharSourceFactory()121 public static CharSourceFactory pathCharSourceFactory() { 122 return new PathCharSourceFactory(); 123 } 124 125 @AndroidIncompatible pathCharSinkFactory()126 public static CharSinkFactory pathCharSinkFactory() { 127 return new PathCharSinkFactory(null); 128 } 129 130 @AndroidIncompatible appendingPathCharSinkFactory()131 public static CharSinkFactory appendingPathCharSinkFactory() { 132 String initialString = IoTestCase.ASCII + IoTestCase.I18N; 133 return new PathCharSinkFactory(initialString); 134 } 135 asByteSourceFactory(final CharSourceFactory factory)136 public static ByteSourceFactory asByteSourceFactory(final CharSourceFactory factory) { 137 checkNotNull(factory); 138 return new ByteSourceFactory() { 139 @Override 140 public ByteSource createSource(byte[] data) throws IOException { 141 return factory.createSource(new String(data, UTF_8)).asByteSource(UTF_8); 142 } 143 144 @Override 145 public byte[] getExpected(byte[] data) { 146 return factory.getExpected(new String(data, UTF_8)).getBytes(UTF_8); 147 } 148 149 @Override 150 public void tearDown() throws IOException { 151 factory.tearDown(); 152 } 153 }; 154 } 155 156 public static CharSourceFactory asCharSourceFactory(final ByteSourceFactory factory) { 157 checkNotNull(factory); 158 return new CharSourceFactory() { 159 @Override 160 public CharSource createSource(String string) throws IOException { 161 return factory.createSource(string.getBytes(UTF_8)).asCharSource(UTF_8); 162 } 163 164 @Override 165 public String getExpected(String data) { 166 return new String(factory.getExpected(data.getBytes(UTF_8)), UTF_8); 167 } 168 169 @Override 170 public void tearDown() throws IOException { 171 factory.tearDown(); 172 } 173 }; 174 } 175 176 public static CharSinkFactory asCharSinkFactory(final ByteSinkFactory factory) { 177 checkNotNull(factory); 178 return new CharSinkFactory() { 179 @Override 180 public CharSink createSink() throws IOException { 181 return factory.createSink().asCharSink(UTF_8); 182 } 183 184 @Override 185 public String getSinkContents() throws IOException { 186 return new String(factory.getSinkContents(), UTF_8); 187 } 188 189 @Override 190 public String getExpected(String data) { 191 /* 192 * Get what the byte sink factory would expect for no written bytes, then append expected 193 * string to that. 194 */ 195 byte[] factoryExpectedForNothing = factory.getExpected(new byte[0]); 196 return new String(factoryExpectedForNothing, UTF_8) + checkNotNull(data); 197 } 198 199 @Override 200 public void tearDown() throws IOException { 201 factory.tearDown(); 202 } 203 }; 204 } 205 206 public static ByteSourceFactory asSlicedByteSourceFactory( 207 final ByteSourceFactory factory, final long off, final long len) { 208 checkNotNull(factory); 209 return new ByteSourceFactory() { 210 @Override 211 public ByteSource createSource(byte[] bytes) throws IOException { 212 return factory.createSource(bytes).slice(off, len); 213 } 214 215 @Override 216 public byte[] getExpected(byte[] bytes) { 217 byte[] baseExpected = factory.getExpected(bytes); 218 int startOffset = (int) Math.min(off, baseExpected.length); 219 int actualLen = (int) Math.min(len, baseExpected.length - startOffset); 220 return Arrays.copyOfRange(baseExpected, startOffset, startOffset + actualLen); 221 } 222 223 @Override 224 public void tearDown() throws IOException { 225 factory.tearDown(); 226 } 227 }; 228 } 229 230 private static class StringSourceFactory implements CharSourceFactory { 231 232 @Override 233 public CharSource createSource(String data) throws IOException { 234 return CharSource.wrap(data); 235 } 236 237 @Override 238 public String getExpected(String data) { 239 return data; 240 } 241 242 @Override 243 public void tearDown() throws IOException {} 244 } 245 246 private static class ByteArraySourceFactory implements ByteSourceFactory { 247 248 @Override 249 public ByteSource createSource(byte[] bytes) throws IOException { 250 return ByteSource.wrap(bytes); 251 } 252 253 @Override 254 public byte[] getExpected(byte[] bytes) { 255 return bytes; 256 } 257 258 @Override 259 public void tearDown() throws IOException {} 260 } 261 262 private static class EmptyCharSourceFactory implements CharSourceFactory { 263 264 @Override 265 public CharSource createSource(String data) throws IOException { 266 return CharSource.empty(); 267 } 268 269 @Override 270 public String getExpected(String data) { 271 return ""; 272 } 273 274 @Override 275 public void tearDown() throws IOException {} 276 } 277 278 private static class EmptyByteSourceFactory implements ByteSourceFactory { 279 280 @Override 281 public ByteSource createSource(byte[] bytes) throws IOException { 282 return ByteSource.empty(); 283 } 284 285 @Override 286 public byte[] getExpected(byte[] bytes) { 287 return new byte[0]; 288 } 289 290 @Override 291 public void tearDown() throws IOException {} 292 } 293 294 private abstract static class FileFactory { 295 296 private static final Logger logger = Logger.getLogger(FileFactory.class.getName()); 297 298 private final ThreadLocal<File> fileThreadLocal = new ThreadLocal<>(); 299 300 protected File createFile() throws IOException { 301 File file = File.createTempFile("SinkSourceFile", "txt"); 302 fileThreadLocal.set(file); 303 return file; 304 } 305 306 protected File getFile() { 307 return fileThreadLocal.get(); 308 } 309 310 public final void tearDown() throws IOException { 311 if (!fileThreadLocal.get().delete()) { 312 logger.warning("Unable to delete file: " + fileThreadLocal.get()); 313 } 314 fileThreadLocal.remove(); 315 } 316 } 317 318 private static class FileByteSourceFactory extends FileFactory implements ByteSourceFactory { 319 320 @Override 321 public ByteSource createSource(byte[] bytes) throws IOException { 322 checkNotNull(bytes); 323 File file = createFile(); 324 OutputStream out = new FileOutputStream(file); 325 try { 326 out.write(bytes); 327 } finally { 328 out.close(); 329 } 330 return Files.asByteSource(file); 331 } 332 333 @Override 334 public byte[] getExpected(byte[] bytes) { 335 return checkNotNull(bytes); 336 } 337 } 338 339 private static class FileByteSinkFactory extends FileFactory implements ByteSinkFactory { 340 341 private final byte[] initialBytes; 342 343 private FileByteSinkFactory(byte @Nullable [] initialBytes) { 344 this.initialBytes = initialBytes; 345 } 346 347 @Override 348 public ByteSink createSink() throws IOException { 349 File file = createFile(); 350 if (initialBytes != null) { 351 FileOutputStream out = new FileOutputStream(file); 352 try { 353 out.write(initialBytes); 354 } finally { 355 out.close(); 356 } 357 return Files.asByteSink(file, FileWriteMode.APPEND); 358 } 359 return Files.asByteSink(file); 360 } 361 362 @Override 363 public byte[] getExpected(byte[] bytes) { 364 if (initialBytes == null) { 365 return checkNotNull(bytes); 366 } else { 367 byte[] result = new byte[initialBytes.length + bytes.length]; 368 System.arraycopy(initialBytes, 0, result, 0, initialBytes.length); 369 System.arraycopy(bytes, 0, result, initialBytes.length, bytes.length); 370 return result; 371 } 372 } 373 374 @Override 375 public byte[] getSinkContents() throws IOException { 376 File file = getFile(); 377 InputStream in = new FileInputStream(file); 378 ByteArrayOutputStream out = new ByteArrayOutputStream(); 379 byte[] buffer = new byte[100]; 380 int read; 381 while ((read = in.read(buffer)) != -1) { 382 out.write(buffer, 0, read); 383 } 384 return out.toByteArray(); 385 } 386 } 387 388 private static class FileCharSourceFactory extends FileFactory implements CharSourceFactory { 389 390 @Override 391 public CharSource createSource(String string) throws IOException { 392 checkNotNull(string); 393 File file = createFile(); 394 Writer writer = new OutputStreamWriter(new FileOutputStream(file), UTF_8); 395 try { 396 writer.write(string); 397 } finally { 398 writer.close(); 399 } 400 return Files.asCharSource(file, UTF_8); 401 } 402 403 @Override 404 public String getExpected(String string) { 405 return checkNotNull(string); 406 } 407 } 408 409 private static class FileCharSinkFactory extends FileFactory implements CharSinkFactory { 410 411 private final String initialString; 412 413 private FileCharSinkFactory(@Nullable String initialString) { 414 this.initialString = initialString; 415 } 416 417 @Override 418 public CharSink createSink() throws IOException { 419 File file = createFile(); 420 if (initialString != null) { 421 Writer writer = new OutputStreamWriter(new FileOutputStream(file), UTF_8); 422 try { 423 writer.write(initialString); 424 } finally { 425 writer.close(); 426 } 427 return Files.asCharSink(file, UTF_8, FileWriteMode.APPEND); 428 } 429 return Files.asCharSink(file, UTF_8); 430 } 431 432 @Override 433 public String getExpected(String string) { 434 checkNotNull(string); 435 return initialString == null ? string : initialString + string; 436 } 437 438 @Override 439 public String getSinkContents() throws IOException { 440 File file = getFile(); 441 Reader reader = new InputStreamReader(new FileInputStream(file), UTF_8); 442 StringBuilder builder = new StringBuilder(); 443 CharBuffer buffer = CharBuffer.allocate(100); 444 while (reader.read(buffer) != -1) { 445 Java8Compatibility.flip(buffer); 446 builder.append(buffer); 447 Java8Compatibility.clear(buffer); 448 } 449 return builder.toString(); 450 } 451 } 452 453 private static class UrlByteSourceFactory extends FileByteSourceFactory { 454 455 @SuppressWarnings("CheckReturnValue") // only using super.createSource to create a file 456 @Override 457 public ByteSource createSource(byte[] bytes) throws IOException { 458 super.createSource(bytes); 459 return Resources.asByteSource(getFile().toURI().toURL()); 460 } 461 } 462 463 private static class UrlCharSourceFactory extends FileCharSourceFactory { 464 465 @SuppressWarnings("CheckReturnValue") // only using super.createSource to create a file 466 @Override 467 public CharSource createSource(String string) throws IOException { 468 super.createSource(string); // just ignore returned CharSource 469 return Resources.asCharSource(getFile().toURI().toURL(), UTF_8); 470 } 471 } 472 473 @AndroidIncompatible 474 private abstract static class Jdk7FileFactory { 475 476 private static final Logger logger = Logger.getLogger(Jdk7FileFactory.class.getName()); 477 478 private final ThreadLocal<Path> fileThreadLocal = new ThreadLocal<>(); 479 480 protected Path createFile() throws IOException { 481 Path file = java.nio.file.Files.createTempFile("SinkSourceFile", "txt"); 482 fileThreadLocal.set(file); 483 return file; 484 } 485 486 protected Path getPath() { 487 return fileThreadLocal.get(); 488 } 489 490 public final void tearDown() throws IOException { 491 try { 492 java.nio.file.Files.delete(fileThreadLocal.get()); 493 } catch (IOException e) { 494 logger.log(Level.WARNING, "Unable to delete file: " + fileThreadLocal.get(), e); 495 } 496 fileThreadLocal.remove(); 497 } 498 } 499 500 @AndroidIncompatible 501 private static class PathByteSourceFactory extends Jdk7FileFactory implements ByteSourceFactory { 502 503 @Override 504 public ByteSource createSource(byte[] bytes) throws IOException { 505 checkNotNull(bytes); 506 Path file = createFile(); 507 508 java.nio.file.Files.write(file, bytes); 509 return MoreFiles.asByteSource(file); 510 } 511 512 @Override 513 public byte[] getExpected(byte[] bytes) { 514 return checkNotNull(bytes); 515 } 516 } 517 518 @AndroidIncompatible 519 private static class PathByteSinkFactory extends Jdk7FileFactory implements ByteSinkFactory { 520 521 private final byte[] initialBytes; 522 523 private PathByteSinkFactory(byte @Nullable [] initialBytes) { 524 this.initialBytes = initialBytes; 525 } 526 527 @Override 528 public ByteSink createSink() throws IOException { 529 Path file = createFile(); 530 if (initialBytes != null) { 531 java.nio.file.Files.write(file, initialBytes); 532 return MoreFiles.asByteSink(file, StandardOpenOption.APPEND); 533 } 534 return MoreFiles.asByteSink(file); 535 } 536 537 @Override 538 public byte[] getExpected(byte[] bytes) { 539 if (initialBytes == null) { 540 return checkNotNull(bytes); 541 } else { 542 byte[] result = new byte[initialBytes.length + bytes.length]; 543 System.arraycopy(initialBytes, 0, result, 0, initialBytes.length); 544 System.arraycopy(bytes, 0, result, initialBytes.length, bytes.length); 545 return result; 546 } 547 } 548 549 @Override 550 public byte[] getSinkContents() throws IOException { 551 Path file = getPath(); 552 return java.nio.file.Files.readAllBytes(file); 553 } 554 } 555 556 @AndroidIncompatible 557 private static class PathCharSourceFactory extends Jdk7FileFactory implements CharSourceFactory { 558 559 @Override 560 public CharSource createSource(String string) throws IOException { 561 checkNotNull(string); 562 Path file = createFile(); 563 try (Writer writer = java.nio.file.Files.newBufferedWriter(file, UTF_8)) { 564 writer.write(string); 565 } 566 return MoreFiles.asCharSource(file, UTF_8); 567 } 568 569 @Override 570 public String getExpected(String string) { 571 return checkNotNull(string); 572 } 573 } 574 575 @AndroidIncompatible 576 private static class PathCharSinkFactory extends Jdk7FileFactory implements CharSinkFactory { 577 578 private final String initialString; 579 580 private PathCharSinkFactory(@Nullable String initialString) { 581 this.initialString = initialString; 582 } 583 584 @Override 585 public CharSink createSink() throws IOException { 586 Path file = createFile(); 587 if (initialString != null) { 588 try (Writer writer = java.nio.file.Files.newBufferedWriter(file, UTF_8)) { 589 writer.write(initialString); 590 } 591 return MoreFiles.asCharSink(file, UTF_8, StandardOpenOption.APPEND); 592 } 593 return MoreFiles.asCharSink(file, UTF_8); 594 } 595 596 @Override 597 public String getExpected(String string) { 598 checkNotNull(string); 599 return initialString == null ? string : initialString + string; 600 } 601 602 @Override 603 public String getSinkContents() throws IOException { 604 Path file = getPath(); 605 try (Reader reader = java.nio.file.Files.newBufferedReader(file, UTF_8)) { 606 StringBuilder builder = new StringBuilder(); 607 for (int c = reader.read(); c != -1; c = reader.read()) { 608 builder.append((char) c); 609 } 610 return builder.toString(); 611 } 612 } 613 } 614 } 615