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 }