1 /* 2 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* @test 25 * @summary Stochastic test of charset-based streams 26 * @key randomness 27 */ 28 29 import java.io.*; 30 import java.util.*; 31 import java.nio.*; 32 import java.nio.channels.*; 33 import java.nio.charset.*; 34 35 36 public class BashStreams { 37 38 static final PrintStream log = System.err; 39 40 41 static class CharacterGenerator { 42 43 private final Random rand; 44 private final int max; 45 private final int limit; 46 private int count = 0; 47 48 CharacterGenerator(long seed, String csn, int limit) { 49 rand = new Random(seed); 50 this.max = Character.MAX_CODE_POINT + 1; 51 this.limit = limit; 52 } 53 54 private char[] saved = new char[10]; 55 private int savedCount = 0; 56 57 void push(char c) { 58 saved[savedCount++] = c; 59 count--; 60 } 61 62 int count() { 63 return count; 64 } 65 66 boolean hasNext() { 67 return count < limit; 68 } 69 70 char next() { 71 if (count >= limit) 72 throw new RuntimeException("EOF"); 73 if (savedCount > 0) { 74 savedCount--; 75 count++; 76 return saved[savedCount]; 77 } 78 int c; 79 for (;;) { 80 c = rand.nextInt(max); 81 if ((Character.isBmpCodePoint(c) 82 && (Character.isSurrogate((char) c) 83 || (c == 0xfffe) || (c == 0xffff)))) 84 continue; 85 if (Character.isSupplementaryCodePoint(c) 86 && (count == limit - 1)) 87 continue; 88 break; 89 } 90 count++; 91 if (Character.isSupplementaryCodePoint(c)) { 92 count++; 93 push(Character.lowSurrogate(c)); 94 return Character.highSurrogate(c); 95 } 96 return (char)c; 97 } 98 99 } 100 101 102 static void mismatch(String csn, int count, char c, char d) { 103 throw new RuntimeException(csn + ": Mismatch at count " 104 + count 105 + ": " + Integer.toHexString(c) 106 + " != " 107 + Integer.toHexString(d)); 108 } 109 110 static void mismatchedEOF(String csn, int count, int cgCount) { 111 throw new RuntimeException(csn + ": Mismatched EOFs: " 112 + count 113 + " != " 114 + cgCount); 115 } 116 117 118 static class Sink // One abomination... 119 extends OutputStream 120 implements WritableByteChannel 121 { 122 123 private final String csn; 124 private final CharacterGenerator cg; 125 private int count = 0; 126 127 Sink(String csn, long seed) { 128 this.csn = csn; 129 this.cg = new CharacterGenerator(seed, csn, Integer.MAX_VALUE); 130 } 131 132 public void write(int b) throws IOException { 133 write (new byte[] { (byte)b }, 0, 1); 134 } 135 136 private int check(byte[] ba, int off, int len) throws IOException { 137 String s = new String(ba, off, len, csn); 138 int n = s.length(); 139 for (int i = 0; i < n; i++) { 140 char c = s.charAt(i); 141 char d = cg.next(); 142 if (c != d) { 143 if (c == '?') { 144 if (Character.isHighSurrogate(d)) 145 cg.next(); 146 continue; 147 } 148 mismatch(csn, count + i, c, d); 149 } 150 } 151 count += n; 152 return len; 153 } 154 155 public void write(byte[] ba, int off, int len) throws IOException { 156 check(ba, off, len); 157 } 158 159 public int write(ByteBuffer bb) throws IOException { 160 int n = check(bb.array(), 161 bb.arrayOffset() + bb.position(), 162 bb.remaining()); 163 bb.position(bb.position() + n); 164 return n; 165 } 166 167 public void close() { 168 count = -1; 169 } 170 171 public boolean isOpen() { 172 return count >= 0; 173 } 174 175 } 176 177 static void testWrite(String csn, int limit, long seed, Writer w) 178 throws IOException 179 { 180 Random rand = new Random(seed); 181 CharacterGenerator cg = new CharacterGenerator(seed, csn, 182 Integer.MAX_VALUE); 183 int count = 0; 184 char[] ca = new char[16384]; 185 186 int n = 0; 187 while (count < limit) { 188 n = rand.nextInt(ca.length); 189 for (int i = 0; i < n; i++) 190 ca[i] = cg.next(); 191 w.write(ca, 0, n); 192 count += n; 193 } 194 if (Character.isHighSurrogate(ca[n - 1])) 195 w.write(cg.next()); 196 w.close(); 197 } 198 199 static void testStreamWrite(String csn, int limit, long seed) 200 throws IOException 201 { 202 log.println(" write stream"); 203 testWrite(csn, limit, seed, 204 new OutputStreamWriter(new Sink(csn, seed), csn)); 205 } 206 207 static void testChannelWrite(String csn, int limit, long seed) 208 throws IOException 209 { 210 log.println(" write channel"); 211 testWrite(csn, limit, seed, 212 Channels.newWriter(new Sink(csn, seed), 213 Charset.forName(csn) 214 .newEncoder() 215 .onMalformedInput(CodingErrorAction.REPLACE) 216 .onUnmappableCharacter(CodingErrorAction.REPLACE), 217 8192)); 218 } 219 220 221 static class Source // ... and another 222 extends InputStream 223 implements ReadableByteChannel 224 { 225 226 private final String csn; 227 private final CharsetEncoder enc; 228 private final CharacterGenerator cg; 229 private int count = 0; 230 231 Source(String csn, long seed, int limit) { 232 this.csn = csn.startsWith("\1") ? csn.substring(1) : csn; 233 this.enc = Charset.forName(this.csn).newEncoder() 234 .onMalformedInput(CodingErrorAction.REPLACE) 235 .onUnmappableCharacter(CodingErrorAction.REPLACE); 236 this.cg = new CharacterGenerator(seed, csn, limit); 237 } 238 239 public int read() throws IOException { 240 byte[] b = new byte[1]; 241 read(b); 242 return b[0]; 243 } 244 245 private CharBuffer cb = CharBuffer.allocate(8192); 246 private ByteBuffer bb = null; 247 248 public int read(byte[] ba, int off, int len) throws IOException { 249 if (!cg.hasNext()) 250 return -1; 251 int end = off + len; 252 int i = off; 253 while (i < end) { 254 if ((bb == null) || !bb.hasRemaining()) { 255 cb.clear(); 256 while (cb.hasRemaining()) { 257 if (!cg.hasNext()) 258 break; 259 char c = cg.next(); 260 if (Character.isHighSurrogate(c) 261 && cb.remaining() == 1) { 262 cg.push(c); 263 break; 264 } 265 cb.put(c); 266 } 267 cb.flip(); 268 if (!cb.hasRemaining()) 269 break; 270 bb = enc.encode(cb); 271 } 272 int d = Math.min(bb.remaining(), end - i); 273 bb.get(ba, i, d); 274 i += d; 275 } 276 return i - off; 277 } 278 279 public int read(ByteBuffer bb) throws IOException { 280 int n = read(bb.array(), 281 bb.arrayOffset() + bb.position(), 282 bb.remaining()); 283 if (n >= 0) 284 bb.position(bb.position() + n); 285 return n; 286 } 287 288 public void close() { 289 count = -1; 290 } 291 292 public boolean isOpen() { 293 return count != -1; 294 } 295 296 } 297 298 static void testRead(String csn, int limit, long seed, int max, 299 Reader rd) 300 throws IOException 301 { 302 Random rand = new Random(seed); 303 CharacterGenerator cg = new CharacterGenerator(seed, csn, limit); 304 int count = 0; 305 char[] ca = new char[16384]; 306 307 int n = 0; 308 while (count < limit) { 309 int rn = rand.nextInt(ca.length); 310 n = rd.read(ca, 0, rn); 311 if (n < 0) 312 break; 313 for (int i = 0; i < n; i++) { 314 char c = ca[i]; 315 if (!cg.hasNext()) 316 mismatchedEOF(csn, count + i, cg.count()); 317 char d = cg.next(); 318 if (c == '?') { 319 if (Character.isHighSurrogate(d)) { 320 cg.next(); 321 continue; 322 } 323 if (d > max) 324 continue; 325 } 326 if (c != d) 327 mismatch(csn, count + i, c, d); 328 } 329 count += n; 330 } 331 if (cg.hasNext()) 332 mismatchedEOF(csn, count, cg.count()); 333 rd.close(); 334 } 335 336 static void testStreamRead(String csn, int limit, long seed, int max) 337 throws IOException 338 { 339 log.println(" read stream"); 340 testRead(csn, limit, seed, max, 341 new InputStreamReader(new Source(csn, seed, limit), csn)); 342 } 343 344 static void testChannelRead(String csn, int limit, long seed, int max) 345 throws IOException 346 { 347 log.println(" read channel"); 348 testRead(csn, limit, seed, max, 349 Channels.newReader(new Source(csn, seed, limit), csn)); 350 } 351 352 353 static final int LIMIT = 1 << 21; 354 355 static void test(String csn, int limit, long seed, int max) 356 throws Exception 357 { 358 log.println(); 359 log.println(csn); 360 361 testStreamWrite(csn, limit, seed); 362 testChannelWrite(csn, limit, seed); 363 testStreamRead(csn, limit, seed, max); 364 testChannelRead(csn, limit, seed, max); 365 } 366 367 public static void main(String[] args) throws Exception { 368 369 if (System.getProperty("os.arch").equals("ia64")) { 370 // This test requires 54 minutes on an Itanium-1 processor 371 return; 372 } 373 374 int ai = 0, an = args.length; 375 376 int limit; 377 if (ai < an) 378 limit = 1 << Integer.parseInt(args[ai++]); 379 else 380 limit = LIMIT; 381 log.println("limit = " + limit); 382 383 long seed; 384 if (ai < an) 385 seed = Long.parseLong(args[ai++]); 386 else 387 seed = System.currentTimeMillis(); 388 log.println("seed = " + seed); 389 390 test("UTF-8", limit, seed, Integer.MAX_VALUE); 391 test("US-ASCII", limit, seed, 0x7f); 392 test("ISO-8859-1", limit, seed, 0xff); 393 394 } 395 396 }