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