UnicodeReader.java 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. package de.jplag.csharp;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.io.InputStreamReader;
  5. import java.io.PushbackInputStream;
  6. import java.io.Reader;
  7. import java.nio.charset.Charset;
  8. import java.nio.charset.StandardCharsets;
  9. import java.util.Objects;
  10. /**
  11. * Generic unicode text reader, which will use BOM mark to identify the encoding to be used. If BOM is not found then
  12. * use a given default encoding. UTF-8 is used if: BOM mark is not found and defaultEnc is NULL. Usage pattern:
  13. *
  14. * <pre>
  15. * String defaultEnc = "UTF-16BE"; // or NULL to use system default
  16. * FileInputStream fis = new FileInputStream(file);
  17. * Reader in = new UnicodeReader(fis, defaultEnc);
  18. *
  19. * Original pseudocode : Thomas Weidenfeller
  20. * Implementation tweaked: Aki Nieminen
  21. *
  22. * http://www.unicode.org/unicode/faq/utf_bom.html
  23. * BOMs:
  24. * 00 00 FE FF = UTF-32, big-endian
  25. * FF FE 00 00 = UTF-32, little-endian
  26. * FE FF = UTF-16, big-endian
  27. * FF FE = UTF-16, little-endian
  28. * EF BB BF = UTF-8
  29. *
  30. * Win2k Notepad:
  31. * Unicode format = UTF-16LE
  32. * </pre>
  33. */
  34. public class UnicodeReader extends Reader {
  35. private final PushbackInputStream internalIn;
  36. private InputStreamReader internalIn2 = null;
  37. private final Charset defaultEnc;
  38. private static final int BOM_SIZE = 4;
  39. public UnicodeReader(InputStream in, Charset defaultEnc) {
  40. internalIn = new PushbackInputStream(in, BOM_SIZE);
  41. this.defaultEnc = defaultEnc;
  42. }
  43. public Charset getDefaultEncoding() {
  44. return defaultEnc;
  45. }
  46. public String getEncoding() {
  47. if (internalIn2 == null)
  48. return null;
  49. return internalIn2.getEncoding();
  50. }
  51. /**
  52. * Read-ahead four bytes and check for BOM marks. Extra bytes are unread back to the stream, only BOM bytes are skipped.
  53. */
  54. private void init() throws IOException {
  55. if (internalIn2 != null)
  56. return;
  57. Charset encoding;
  58. byte[] bom = new byte[BOM_SIZE];
  59. int n, unread;
  60. n = internalIn.read(bom, 0, bom.length);
  61. if ((bom[0] == (byte) 0xEF) && (bom[1] == (byte) 0xBB) && (bom[2] == (byte) 0xBF)) {
  62. encoding = StandardCharsets.UTF_8;
  63. unread = n - 3;
  64. } else if ((bom[0] == (byte) 0xFE) && (bom[1] == (byte) 0xFF)) {
  65. encoding = StandardCharsets.UTF_16BE;
  66. unread = n - 2;
  67. } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE)) {
  68. encoding = StandardCharsets.UTF_16LE;
  69. unread = n - 2;
  70. } else if ((bom[0] == (byte) 0x00) && (bom[1] == (byte) 0x00) && (bom[2] == (byte) 0xFE) && (bom[3] == (byte) 0xFF)) {
  71. encoding = Charset.forName("UTF-32BE");
  72. unread = n - 4;
  73. }
  74. // TODO WARNING: This line will evaluate always to false. Maybe that's an issue
  75. else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE) && (bom[2] == (byte) 0x00) && (bom[3] == (byte) 0x00)) {
  76. encoding = Charset.forName("UTF-32LE");
  77. unread = n - 4;
  78. } else {
  79. // Unicode BOM mark not found, unread all bytes
  80. encoding = defaultEnc;
  81. unread = n;
  82. }
  83. if (unread > 0)
  84. internalIn.unread(bom, (n - unread), unread);
  85. // Use given encoding
  86. internalIn2 = new InputStreamReader(internalIn, Objects.requireNonNullElse(encoding, StandardCharsets.UTF_8));
  87. }
  88. @Override
  89. public void close() throws IOException {
  90. init();
  91. internalIn2.close();
  92. }
  93. @Override
  94. public int read(char[] cbuf, int off, int len) throws IOException {
  95. init();
  96. return internalIn2.read(cbuf, off, len);
  97. }
  98. }