/* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* @test * @summary Stochastic test of charset-based streams */ import java.io.*; import java.util.*; import java.nio.*; import java.nio.channels.*; import java.nio.charset.*; public class BashStreams { static final PrintStream log = System.err; static class CharacterGenerator { private final Random rand; private final int max; private final int limit; private int count = 0; CharacterGenerator(long seed, String csn, int limit) { rand = new Random(seed); this.max = Character.MAX_CODE_POINT + 1; this.limit = limit; } private char[] saved = new char[10]; private int savedCount = 0; void push(char c) { saved[savedCount++] = c; count--; } int count() { return count; } boolean hasNext() { return count < limit; } char next() { if (count >= limit) throw new RuntimeException("EOF"); if (savedCount > 0) { savedCount--; count++; return saved[savedCount]; } int c; for (;;) { c = rand.nextInt(max); if ((Character.isBmpCodePoint(c) && (Character.isSurrogate((char) c) || (c == 0xfffe) || (c == 0xffff)))) continue; if (Character.isSupplementaryCodePoint(c) && (count == limit - 1)) continue; break; } count++; if (Character.isSupplementaryCodePoint(c)) { count++; push(Character.lowSurrogate(c)); return Character.highSurrogate(c); } return (char)c; } } static void mismatch(String csn, int count, char c, char d) { throw new RuntimeException(csn + ": Mismatch at count " + count + ": " + Integer.toHexString(c) + " != " + Integer.toHexString(d)); } static void mismatchedEOF(String csn, int count, int cgCount) { throw new RuntimeException(csn + ": Mismatched EOFs: " + count + " != " + cgCount); } static class Sink // One abomination... extends OutputStream implements WritableByteChannel { private final String csn; private final CharacterGenerator cg; private int count = 0; Sink(String csn, long seed) { this.csn = csn; this.cg = new CharacterGenerator(seed, csn, Integer.MAX_VALUE); } public void write(int b) throws IOException { write (new byte[] { (byte)b }, 0, 1); } private int check(byte[] ba, int off, int len) throws IOException { String s = new String(ba, off, len, csn); int n = s.length(); for (int i = 0; i < n; i++) { char c = s.charAt(i); char d = cg.next(); if (c != d) { if (c == '?') { if (Character.isHighSurrogate(d)) cg.next(); continue; } mismatch(csn, count + i, c, d); } } count += n; return len; } public void write(byte[] ba, int off, int len) throws IOException { check(ba, off, len); } public int write(ByteBuffer bb) throws IOException { int n = check(bb.array(), bb.arrayOffset() + bb.position(), bb.remaining()); bb.position(bb.position() + n); return n; } public void close() { count = -1; } public boolean isOpen() { return count >= 0; } } static void testWrite(String csn, int limit, long seed, Writer w) throws IOException { Random rand = new Random(seed); CharacterGenerator cg = new CharacterGenerator(seed, csn, Integer.MAX_VALUE); int count = 0; char[] ca = new char[16384]; int n = 0; while (count < limit) { n = rand.nextInt(ca.length); for (int i = 0; i < n; i++) ca[i] = cg.next(); w.write(ca, 0, n); count += n; } if (Character.isHighSurrogate(ca[n - 1])) w.write(cg.next()); w.close(); } static void testStreamWrite(String csn, int limit, long seed) throws IOException { log.println(" write stream"); testWrite(csn, limit, seed, new OutputStreamWriter(new Sink(csn, seed), csn)); } static void testChannelWrite(String csn, int limit, long seed) throws IOException { log.println(" write channel"); testWrite(csn, limit, seed, Channels.newWriter(new Sink(csn, seed), Charset.forName(csn) .newEncoder() .onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE), 8192)); } static class Source // ... and another extends InputStream implements ReadableByteChannel { private final String csn; private final CharsetEncoder enc; private final CharacterGenerator cg; private int count = 0; Source(String csn, long seed, int limit) { this.csn = csn.startsWith("\1") ? csn.substring(1) : csn; this.enc = Charset.forName(this.csn).newEncoder() .onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE); this.cg = new CharacterGenerator(seed, csn, limit); } public int read() throws IOException { byte[] b = new byte[1]; read(b); return b[0]; } private CharBuffer cb = CharBuffer.allocate(8192); private ByteBuffer bb = null; public int read(byte[] ba, int off, int len) throws IOException { if (!cg.hasNext()) return -1; int end = off + len; int i = off; while (i < end) { if ((bb == null) || !bb.hasRemaining()) { cb.clear(); while (cb.hasRemaining()) { if (!cg.hasNext()) break; char c = cg.next(); if (Character.isHighSurrogate(c) && cb.remaining() == 1) { cg.push(c); break; } cb.put(c); } cb.flip(); if (!cb.hasRemaining()) break; bb = enc.encode(cb); } int d = Math.min(bb.remaining(), end - i); bb.get(ba, i, d); i += d; } return i - off; } public int read(ByteBuffer bb) throws IOException { int n = read(bb.array(), bb.arrayOffset() + bb.position(), bb.remaining()); if (n >= 0) bb.position(bb.position() + n); return n; } public void close() { count = -1; } public boolean isOpen() { return count != -1; } } static void testRead(String csn, int limit, long seed, int max, Reader rd) throws IOException { Random rand = new Random(seed); CharacterGenerator cg = new CharacterGenerator(seed, csn, limit); int count = 0; char[] ca = new char[16384]; int n = 0; while (count < limit) { int rn = rand.nextInt(ca.length); n = rd.read(ca, 0, rn); if (n < 0) break; for (int i = 0; i < n; i++) { char c = ca[i]; if (!cg.hasNext()) mismatchedEOF(csn, count + i, cg.count()); char d = cg.next(); if (c == '?') { if (Character.isHighSurrogate(d)) { cg.next(); continue; } if (d > max) continue; } if (c != d) mismatch(csn, count + i, c, d); } count += n; } if (cg.hasNext()) mismatchedEOF(csn, count, cg.count()); rd.close(); } static void testStreamRead(String csn, int limit, long seed, int max) throws IOException { log.println(" read stream"); testRead(csn, limit, seed, max, new InputStreamReader(new Source(csn, seed, limit), csn)); } static void testChannelRead(String csn, int limit, long seed, int max) throws IOException { log.println(" read channel"); testRead(csn, limit, seed, max, Channels.newReader(new Source(csn, seed, limit), csn)); } static final int LIMIT = 1 << 21; static void test(String csn, int limit, long seed, int max) throws Exception { log.println(); log.println(csn); testStreamWrite(csn, limit, seed); testChannelWrite(csn, limit, seed); testStreamRead(csn, limit, seed, max); testChannelRead(csn, limit, seed, max); } public static void main(String[] args) throws Exception { if (System.getProperty("os.arch").equals("ia64")) { // This test requires 54 minutes on an Itanium-1 processor return; } int ai = 0, an = args.length; int limit; if (ai < an) limit = 1 << Integer.parseInt(args[ai++]); else limit = LIMIT; log.println("limit = " + limit); long seed; if (ai < an) seed = Long.parseLong(args[ai++]); else seed = System.currentTimeMillis(); log.println("seed = " + seed); test("UTF-8", limit, seed, Integer.MAX_VALUE); test("US-ASCII", limit, seed, 0x7f); test("ISO-8859-1", limit, seed, 0xff); } }