1 /*
   2  * Copyright (c) 2020, 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 import java.lang.invoke.MethodHandle;
  25 import java.lang.invoke.MethodHandles;
  26 import java.lang.invoke.MethodType;
  27 import java.nio.Buffer;
  28 import java.nio.ByteBuffer;
  29 import java.nio.CharBuffer;
  30 import java.nio.ShortBuffer;
  31 import java.nio.IntBuffer;
  32 import java.nio.LongBuffer;
  33 import java.nio.FloatBuffer;
  34 import java.nio.DoubleBuffer;
  35 import java.nio.ByteOrder;
  36 import java.util.ArrayList;
  37 import java.util.Arrays;
  38 import java.util.HashMap;
  39 import java.util.Iterator;
  40 import java.util.List;
  41 import java.util.Map;
  42 import java.util.Random;
  43 
  44 import org.testng.Assert;
  45 import org.testng.annotations.DataProvider;
  46 import org.testng.annotations.Test;
  47 
  48 
  49 /*
  50  * @test
  51  * @bug 8245121
  52  * @summary Ensure that a bulk put of a buffer into another is correct.
  53  * @compile --enable-preview -source ${jdk.version} BulkPutBuffer.java
  54  * @run testng/othervm --enable-preview BulkPutBuffer
  55  */
  56 public class BulkPutBuffer {
  57     static final long SEED = System.nanoTime();
  58     static final Random RND = new MyRandom(SEED);
  59 
  60     static final int ITERATIONS = 100;
  61     static final int MAX_CAPACITY = 1024;
  62 
  63     static class MyRandom extends Random {
  64         MyRandom(long seed) {
  65             super(seed);
  66         }
  67 
  68         public byte nextByte() {
  69             return (byte)next(8);
  70         }
  71 
  72         public char nextChar() {
  73             return (char)next(16);
  74         }
  75 
  76         public short nextShort() {
  77             return (short)next(16);
  78         }
  79     }
  80 
  81     enum BufferKind {
  82         HEAP,
  83         HEAP_VIEW,
  84         DIRECT;
  85     }
  86 
  87     static final Map<Class<?>,TypeAttr> typeToAttr;
  88 
  89     static record TypeAttr(Class<?> type, int bytes, String name) {}
  90 
  91     static {
  92         typeToAttr = Map.of(
  93             byte.class, new TypeAttr(ByteBuffer.class, Byte.BYTES, "Byte"),
  94             char.class, new TypeAttr(CharBuffer.class, Character.BYTES, "Char"),
  95             short.class, new TypeAttr(ShortBuffer.class, Short.BYTES, "Short"),
  96             int.class, new TypeAttr(IntBuffer.class, Integer.BYTES, "Int"),
  97             float.class, new TypeAttr(FloatBuffer.class, Float.BYTES, "Float"),
  98             long.class, new TypeAttr(LongBuffer.class, Long.BYTES, "Long"),
  99             double.class, new TypeAttr(DoubleBuffer.class, Double.BYTES, "Double")
 100         );
 101     }
 102 
 103     static BufferKind[] getKinds(Class<?> elementType) {
 104         return elementType == byte.class ?
 105             new BufferKind[] { BufferKind.DIRECT, BufferKind.HEAP } :
 106             BufferKind.values();
 107     }
 108 
 109     static ByteOrder[] getOrders(BufferKind kind, Class<?> elementType) {
 110         switch (kind) {
 111             case HEAP:
 112                 return new ByteOrder[] { ByteOrder.nativeOrder() };
 113             default:
 114                 if (elementType == byte.class)
 115                     return new ByteOrder[] { ByteOrder.nativeOrder() };
 116                 else
 117                     return new ByteOrder[] { ByteOrder.BIG_ENDIAN,
 118                         ByteOrder.LITTLE_ENDIAN };
 119         }
 120     }
 121 
 122     public static class BufferProxy {
 123         final Class<?> elementType;
 124         final BufferKind kind;
 125         final ByteOrder order;
 126 
 127         // Buffer methods
 128         MethodHandle alloc;
 129         MethodHandle allocBB;
 130         MethodHandle allocDirect;
 131         MethodHandle asTypeBuffer;
 132         MethodHandle putAbs;
 133         MethodHandle getAbs;
 134         MethodHandle putBufRel;
 135         MethodHandle equals;
 136 
 137         // MyRandom method
 138         MethodHandle nextType;
 139 
 140         BufferProxy(Class<?> elementType, BufferKind kind, ByteOrder order) {
 141             this.elementType = elementType;
 142             this.kind = kind;
 143             this.order = order;
 144 
 145             Class<?> bufferType = typeToAttr.get(elementType).type;
 146 
 147             var lookup = MethodHandles.lookup();
 148             try {
 149                 String name = typeToAttr.get(elementType).name;
 150 
 151                 alloc = lookup.findStatic(bufferType, "allocate",
 152                     MethodType.methodType(bufferType, int.class));
 153                 allocBB = lookup.findStatic(ByteBuffer.class, "allocate",
 154                     MethodType.methodType(ByteBuffer.class, int.class));
 155                 allocDirect = lookup.findStatic(ByteBuffer.class, "allocateDirect",
 156                     MethodType.methodType(ByteBuffer.class, int.class));
 157                 if (elementType != byte.class) {
 158                     asTypeBuffer = lookup.findVirtual(ByteBuffer.class,
 159                         "as" + name + "Buffer", MethodType.methodType(bufferType));
 160                 }
 161 
 162                 putAbs = lookup.findVirtual(bufferType, "put",
 163                     MethodType.methodType(bufferType, int.class, elementType));
 164                 getAbs = lookup.findVirtual(bufferType, "get",
 165                     MethodType.methodType(elementType, int.class));
 166                 putBufRel = lookup.findVirtual(bufferType, "put",
 167                     MethodType.methodType(bufferType, bufferType));
 168                 equals = lookup.findVirtual(bufferType, "equals",
 169                     MethodType.methodType(boolean.class, Object.class));
 170 
 171                 nextType = lookup.findVirtual(MyRandom.class,
 172                      "next" + name, MethodType.methodType(elementType));
 173             } catch (IllegalAccessException | NoSuchMethodException e) {
 174                 throw new AssertionError(e);
 175             }
 176         }
 177 
 178         Buffer create(int capacity) throws Throwable {
 179 
 180             Class<?> bufferType = typeToAttr.get(elementType).type;
 181 
 182             try {
 183                 if (bufferType == ByteBuffer.class ||
 184                     kind == BufferKind.DIRECT || kind == BufferKind.HEAP_VIEW) {
 185                     int len = capacity*typeToAttr.get(elementType).bytes;
 186                     ByteBuffer bb = (ByteBuffer)allocBB.invoke(len);
 187                     byte[] bytes = new byte[len];
 188                     RND.nextBytes(bytes);
 189                     bb.put(0, bytes);
 190                     if (bufferType == ByteBuffer.class) {
 191                         return (Buffer)bb;
 192                     } else {
 193                         bb.order(order);
 194                         return (Buffer)asTypeBuffer.invoke(bb);
 195                     }
 196                 } else {
 197                     Buffer buf = (Buffer)alloc.invoke(capacity);
 198                     for (int i = 0; i < capacity; i++) {
 199                         putAbs.invoke(buf, i, nextType.invoke(RND));
 200                     }
 201                     return buf;
 202                 }
 203             } catch (Exception e) {
 204                 throw new AssertionError(e);
 205             }
 206         }
 207 
 208         void copy(Buffer src, int srcOff, Buffer dst, int dstOff, int length)
 209             throws Throwable {
 210             try {
 211                 for (int i = 0; i < length; i++) {
 212                     putAbs.invoke(dst, dstOff + i, getAbs.invoke(src, srcOff + i));
 213                 }
 214             } catch (Exception e) {
 215                 throw new AssertionError(e);
 216             }
 217         }
 218 
 219         void put(Buffer src, Buffer dst) throws Throwable {
 220             try {
 221                 putBufRel.invoke(dst, src);
 222             } catch (Exception e) {
 223                 throw new AssertionError(e);
 224             }
 225         }
 226 
 227         boolean equals(Buffer src, Buffer dst) throws Throwable {
 228             try {
 229                 return Boolean.class.cast(equals.invoke(dst, src));
 230             } catch (Exception e) {
 231                 throw new AssertionError(e);
 232             }
 233         }
 234     }
 235 
 236     static List<BufferProxy> getProxies(Class<?> type) {
 237         List proxies = new ArrayList();
 238         for (BufferKind kind : getKinds(type)) {
 239             for (ByteOrder order : getOrders(kind, type)) {
 240                 proxies.add(new BufferProxy(type, kind, order));
 241             }
 242         }
 243         return proxies;
 244     }
 245 
 246     @DataProvider
 247     static Object[][] proxies() {
 248         ArrayList<Object[]> args = new ArrayList<>();
 249         for (Class<?> type : typeToAttr.keySet()) {
 250             List<BufferProxy> proxies = getProxies(type);
 251             for (BufferProxy proxy : proxies) {
 252                 args.add(new Object[] {proxy});
 253             }
 254         }
 255         return args.toArray(Object[][]::new);
 256 
 257     }
 258 
 259     @DataProvider
 260     static Object[][] proxyPairs() {
 261         List<Object[]> args = new ArrayList<>();
 262         for (Class<?> type : typeToAttr.keySet()) {
 263             List<BufferProxy> proxies = getProxies(type);
 264             for (BufferProxy proxy1 : proxies) {
 265                 for (BufferProxy proxy2 : proxies) {
 266                     args.add(new Object[] {proxy1, proxy2});
 267                 }
 268             }
 269         }
 270         return args.toArray(Object[][]::new);
 271     }
 272 
 273     @Test(dataProvider = "proxies")
 274     public static void testSelf(BufferProxy bp) throws Throwable {
 275         for (int i = 0; i < ITERATIONS; i++) {
 276             int cap = RND.nextInt(MAX_CAPACITY);
 277             Buffer buf = bp.create(cap);
 278 
 279             int lowerOffset = RND.nextInt(1 + cap/10);
 280             int lowerLength = RND.nextInt(1 + cap/2);
 281             if (lowerLength < 2)
 282                 continue;
 283             Buffer lower = buf.slice(lowerOffset, lowerLength);
 284 
 285             Buffer lowerCopy = bp.create(lowerLength);
 286             bp.copy(lower, 0, lowerCopy, 0, lowerLength);
 287 
 288             int middleOffset = RND.nextInt(1 + cap/2);
 289             Buffer middle = buf.slice(middleOffset, lowerLength);
 290 
 291             bp.put(lower, middle);
 292             middle.flip();
 293 
 294             Assert.assertTrue(bp.equals(lowerCopy, middle),
 295                 String.format("%d %s %d %d %d %d%n", SEED,
 296                     buf.getClass().getName(), cap,
 297                     lowerOffset, lowerLength, middleOffset));
 298         }
 299     }
 300 
 301     @Test(dataProvider = "proxyPairs")
 302     public static void testPairs(BufferProxy bp, BufferProxy sbp) throws Throwable {
 303         for (int i = 0; i < ITERATIONS; i++) {
 304             int cap = Math.max(4, RND.nextInt(MAX_CAPACITY));
 305             int cap2 = cap/2;
 306             Buffer buf = bp.create(cap);
 307 
 308             int pos = RND.nextInt(Math.max(1, cap2));
 309             buf.position(pos);
 310             buf.mark();
 311             int lim = pos + Math.max(1, cap - pos);
 312             buf.limit(lim);
 313 
 314             int scap = Math.max(buf.remaining(), RND.nextInt(1024));
 315             Buffer src = sbp.create(scap);
 316 
 317             int diff = scap - buf.remaining();
 318             int spos = diff > 0 ? RND.nextInt(diff) : 0;
 319             src.position(spos);
 320             src.mark();
 321             int slim = spos + buf.remaining();
 322             src.limit(slim);
 323 
 324             bp.put(src, buf);
 325 
 326             buf.reset();
 327             src.reset();
 328 
 329             Assert.assertTrue(bp.equals(src, buf),
 330                 String.format("%d %s %d %d %d %s %d %d %d%n", SEED,
 331                     buf.getClass().getName(), cap, pos, lim,
 332                     src.getClass().getName(), scap, spos, slim));
 333         }
 334     }
 335 }