| /* | |
| * Copyright (c) 2020, 2022, 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. | |
| */ | |
| import java.lang.invoke.MethodHandle; | |
| import java.lang.invoke.MethodHandles; | |
| import java.lang.invoke.MethodType; | |
| import java.nio.Buffer; | |
| import java.nio.ByteBuffer; | |
| import java.nio.ByteOrder; | |
| import java.nio.CharBuffer; | |
| import java.nio.DoubleBuffer; | |
| import java.nio.FloatBuffer; | |
| import java.nio.IntBuffer; | |
| import java.nio.LongBuffer; | |
| import java.nio.ReadOnlyBufferException; | |
| import java.nio.ShortBuffer; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.HashMap; | |
| import java.util.Iterator; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Random; | |
| import org.testng.Assert; | |
| import org.testng.annotations.DataProvider; | |
| import org.testng.annotations.Test; | |
| /* | |
| * @test | |
| * @bug 8219014 8245121 | |
| * @summary Ensure that a bulk put of a buffer into another is correct. | |
| * @compile BulkPutBuffer.java | |
| * @run testng/othervm BulkPutBuffer | |
| */ | |
| public class BulkPutBuffer { | |
| static final long SEED = System.nanoTime(); | |
| static final MyRandom RND = new MyRandom(SEED); | |
| static final int ITERATIONS = 100; | |
| static final int MAX_CAPACITY = 1024; | |
| static class MyRandom extends Random { | |
| MyRandom(long seed) { | |
| super(seed); | |
| } | |
| public byte nextByte() { | |
| return (byte)next(8); | |
| } | |
| public char nextChar() { | |
| return (char)next(16); | |
| } | |
| public short nextShort() { | |
| return (short)next(16); | |
| } | |
| } | |
| enum BufferKind { | |
| HEAP, | |
| HEAP_VIEW, | |
| DIRECT, | |
| STRING; | |
| } | |
| static final Map<Class<?>,TypeAttr> typeToAttr; | |
| static record TypeAttr(Class<?> type, int bytes, String name) {} | |
| static { | |
| typeToAttr = Map.of( | |
| byte.class, new TypeAttr(ByteBuffer.class, Byte.BYTES, "Byte"), | |
| char.class, new TypeAttr(CharBuffer.class, Character.BYTES, "Char"), | |
| short.class, new TypeAttr(ShortBuffer.class, Short.BYTES, "Short"), | |
| int.class, new TypeAttr(IntBuffer.class, Integer.BYTES, "Int"), | |
| float.class, new TypeAttr(FloatBuffer.class, Float.BYTES, "Float"), | |
| long.class, new TypeAttr(LongBuffer.class, Long.BYTES, "Long"), | |
| double.class, new TypeAttr(DoubleBuffer.class, Double.BYTES, "Double") | |
| ); | |
| } | |
| static BufferKind[] getKinds(Class<?> elementType) { | |
| BufferKind[] kinds; | |
| if (elementType == byte.class) | |
| kinds = new BufferKind[] { | |
| BufferKind.DIRECT, | |
| BufferKind.HEAP | |
| }; | |
| else if (elementType == char.class) | |
| kinds = BufferKind.values(); | |
| else | |
| kinds = new BufferKind[] { | |
| BufferKind.DIRECT, | |
| BufferKind.HEAP, | |
| BufferKind.HEAP_VIEW | |
| }; | |
| return kinds; | |
| } | |
| static ByteOrder[] getOrders(BufferKind kind, Class<?> elementType) { | |
| switch (kind) { | |
| case HEAP: | |
| return new ByteOrder[] { ByteOrder.nativeOrder() }; | |
| default: | |
| if (elementType == byte.class) | |
| return new ByteOrder[] { ByteOrder.nativeOrder() }; | |
| else | |
| return new ByteOrder[] { ByteOrder.BIG_ENDIAN, | |
| ByteOrder.LITTLE_ENDIAN }; | |
| } | |
| } | |
| public static class BufferProxy { | |
| final Class<?> elementType; | |
| final BufferKind kind; | |
| final ByteOrder order; | |
| // Buffer methods | |
| MethodHandle alloc; | |
| MethodHandle allocBB; | |
| MethodHandle allocDirect; | |
| MethodHandle asReadOnlyBuffer; | |
| MethodHandle asTypeBuffer; | |
| MethodHandle putAbs; | |
| MethodHandle getAbs; | |
| MethodHandle putBufAbs; | |
| MethodHandle putBufRel; | |
| MethodHandle equals; | |
| // MyRandom method | |
| MethodHandle nextType; | |
| BufferProxy(Class<?> elementType, BufferKind kind, ByteOrder order) { | |
| this.elementType = elementType; | |
| this.kind = kind; | |
| this.order = order; | |
| Class<?> bufferType = typeToAttr.get(elementType).type; | |
| var lookup = MethodHandles.lookup(); | |
| try { | |
| String name = typeToAttr.get(elementType).name; | |
| alloc = lookup.findStatic(bufferType, "allocate", | |
| MethodType.methodType(bufferType, int.class)); | |
| allocBB = lookup.findStatic(ByteBuffer.class, "allocate", | |
| MethodType.methodType(ByteBuffer.class, int.class)); | |
| allocDirect = lookup.findStatic(ByteBuffer.class, "allocateDirect", | |
| MethodType.methodType(ByteBuffer.class, int.class)); | |
| asReadOnlyBuffer = lookup.findVirtual(bufferType, | |
| "asReadOnlyBuffer", MethodType.methodType(bufferType)); | |
| if (elementType != byte.class) { | |
| asTypeBuffer = lookup.findVirtual(ByteBuffer.class, | |
| "as" + name + "Buffer", MethodType.methodType(bufferType)); | |
| } | |
| putAbs = lookup.findVirtual(bufferType, "put", | |
| MethodType.methodType(bufferType, int.class, elementType)); | |
| getAbs = lookup.findVirtual(bufferType, "get", | |
| MethodType.methodType(elementType, int.class)); | |
| putBufAbs = lookup.findVirtual(bufferType, "put", | |
| MethodType.methodType(bufferType, int.class, bufferType, | |
| int.class, int.class)); | |
| putBufRel = lookup.findVirtual(bufferType, "put", | |
| MethodType.methodType(bufferType, bufferType)); | |
| equals = lookup.findVirtual(bufferType, "equals", | |
| MethodType.methodType(boolean.class, Object.class)); | |
| nextType = lookup.findVirtual(MyRandom.class, | |
| "next" + name, MethodType.methodType(elementType)); | |
| } catch (IllegalAccessException | NoSuchMethodException e) { | |
| throw new AssertionError(e); | |
| } | |
| } | |
| Buffer create(int capacity) throws Throwable { | |
| Class<?> bufferType = typeToAttr.get(elementType).type; | |
| try { | |
| if (bufferType == ByteBuffer.class || | |
| kind == BufferKind.DIRECT || kind == BufferKind.HEAP_VIEW) { | |
| int len = capacity*typeToAttr.get(elementType).bytes; | |
| ByteBuffer bb = (ByteBuffer)allocBB.invoke(len); | |
| byte[] bytes = new byte[len]; | |
| RND.nextBytes(bytes); | |
| bb.put(0, bytes); | |
| if (bufferType == ByteBuffer.class) { | |
| return (Buffer)bb; | |
| } else { | |
| bb.order(order); | |
| return (Buffer)asTypeBuffer.invoke(bb); | |
| } | |
| } else if (bufferType == CharBuffer.class && | |
| kind == BufferKind.STRING) { | |
| char[] array = new char[capacity]; | |
| for (int i = 0; i < capacity; i++) { | |
| array[i] = RND.nextChar(); | |
| } | |
| return CharBuffer.wrap(new String(array)); | |
| } else { | |
| Buffer buf = (Buffer)alloc.invoke(capacity); | |
| for (int i = 0; i < capacity; i++) { | |
| putAbs.invoke(buf, i, nextType.invoke(RND)); | |
| } | |
| return buf; | |
| } | |
| } catch (Exception e) { | |
| throw new AssertionError(e); | |
| } | |
| } | |
| void copy(Buffer src, int srcOff, Buffer dst, int dstOff, int length) | |
| throws Throwable { | |
| try { | |
| for (int i = 0; i < length; i++) { | |
| putAbs.invoke(dst, dstOff + i, getAbs.invoke(src, srcOff + i)); | |
| } | |
| } catch (ReadOnlyBufferException ro) { | |
| throw ro; | |
| } catch (Exception e) { | |
| throw new AssertionError(e); | |
| } | |
| } | |
| Buffer asReadOnlyBuffer(Buffer buf) throws Throwable { | |
| try { | |
| return (Buffer)asReadOnlyBuffer.invoke(buf); | |
| } catch (Exception e) { | |
| throw new AssertionError(e); | |
| } | |
| } | |
| void put(Buffer src, int srcOff, Buffer dst, int dstOff, int length) | |
| throws Throwable { | |
| try { | |
| putBufAbs.invoke(dst, dstOff, src, srcOff, length); | |
| } catch (ReadOnlyBufferException ro) { | |
| throw ro; | |
| } catch (Exception e) { | |
| throw new AssertionError(e); | |
| } | |
| } | |
| void put(Buffer src, Buffer dst) throws Throwable { | |
| try { | |
| putBufRel.invoke(dst, src); | |
| } catch (ReadOnlyBufferException ro) { | |
| throw ro; | |
| } catch (Exception e) { | |
| throw new AssertionError(e); | |
| } | |
| } | |
| boolean equals(Buffer src, Buffer dst) throws Throwable { | |
| try { | |
| return Boolean.class.cast(equals.invoke(dst, src)); | |
| } catch (Exception e) { | |
| throw new AssertionError(e); | |
| } | |
| } | |
| } | |
| static List<BufferProxy> getProxies(Class<?> type) { | |
| List proxies = new ArrayList(); | |
| for (BufferKind kind : getKinds(type)) { | |
| for (ByteOrder order : getOrders(kind, type)) { | |
| proxies.add(new BufferProxy(type, kind, order)); | |
| } | |
| } | |
| return proxies; | |
| } | |
| @DataProvider | |
| static Object[][] proxies() { | |
| ArrayList<Object[]> args = new ArrayList<>(); | |
| for (Class<?> type : typeToAttr.keySet()) { | |
| List<BufferProxy> proxies = getProxies(type); | |
| for (BufferProxy proxy : proxies) { | |
| args.add(new Object[] {proxy}); | |
| } | |
| } | |
| return args.toArray(Object[][]::new); | |
| } | |
| @DataProvider | |
| static Object[][] proxyPairs() { | |
| List<Object[]> args = new ArrayList<>(); | |
| for (Class<?> type : typeToAttr.keySet()) { | |
| List<BufferProxy> proxies = getProxies(type); | |
| for (BufferProxy proxy1 : proxies) { | |
| for (BufferProxy proxy2 : proxies) { | |
| args.add(new Object[] {proxy1, proxy2}); | |
| } | |
| } | |
| } | |
| return args.toArray(Object[][]::new); | |
| } | |
| private static void expectThrows(Class<?> exClass, Assert.ThrowingRunnable r) { | |
| try { | |
| r.run(); | |
| } catch(Throwable e) { | |
| if (e.getClass() != exClass && e.getCause().getClass() != exClass) { | |
| throw new RuntimeException("Expected " + exClass + | |
| "; got " + e.getCause().getClass(), e); | |
| } | |
| } | |
| } | |
| @Test(dataProvider = "proxies") | |
| public static void testExceptions(BufferProxy bp) throws Throwable { | |
| int cap = 27; | |
| Buffer buf = bp.create(cap); | |
| expectThrows(IndexOutOfBoundsException.class, | |
| () -> bp.put(buf, -1, buf, 0, 1)); | |
| expectThrows(IndexOutOfBoundsException.class, | |
| () -> bp.put(buf, 0, buf, -1, 1)); | |
| expectThrows(IndexOutOfBoundsException.class, | |
| () -> bp.put(buf, 1, buf, 0, cap)); | |
| expectThrows(IndexOutOfBoundsException.class, | |
| () -> bp.put(buf, 0, buf, 1, cap)); | |
| expectThrows(IndexOutOfBoundsException.class, | |
| () -> bp.put(buf, 0, buf, 0, cap + 1)); | |
| expectThrows(IndexOutOfBoundsException.class, | |
| () -> bp.put(buf, 0, buf, 0, Integer.MAX_VALUE)); | |
| Buffer rob = buf.isReadOnly() ? buf : bp.asReadOnlyBuffer(buf); | |
| expectThrows(ReadOnlyBufferException.class, | |
| () -> bp.put(buf, 0, rob, 0, cap)); | |
| } | |
| @Test(dataProvider = "proxies") | |
| public static void testSelf(BufferProxy bp) throws Throwable { | |
| for (int i = 0; i < ITERATIONS; i++) { | |
| int cap = RND.nextInt(MAX_CAPACITY); | |
| Buffer buf = bp.create(cap); | |
| int lowerOffset = RND.nextInt(1 + cap/10); | |
| int lowerLength = RND.nextInt(1 + cap/2); | |
| if (lowerLength < 2) | |
| continue; | |
| Buffer lower = buf.slice(lowerOffset, lowerLength); | |
| Buffer lowerCopy = bp.create(lowerLength); | |
| if (lowerCopy.isReadOnly()) { | |
| Assert.expectThrows(ReadOnlyBufferException.class, | |
| () -> bp.copy(lower, 0, lowerCopy, 0, lowerLength)); | |
| break; | |
| } | |
| bp.copy(lower, 0, lowerCopy, 0, lowerLength); | |
| int middleOffset = RND.nextInt(1 + cap/2); | |
| Buffer middle = buf.slice(middleOffset, lowerLength); | |
| Buffer middleCopy = bp.create(lowerLength); | |
| bp.copy(middle, 0, middleCopy, 0, lowerLength); | |
| bp.put(lower, middle); | |
| middle.flip(); | |
| Assert.assertTrue(bp.equals(lowerCopy, middle), | |
| String.format("%d %s %d %d %d %d%n", SEED, | |
| buf.getClass().getName(), cap, | |
| lowerOffset, lowerLength, middleOffset)); | |
| bp.copy(lowerCopy, 0, buf, lowerOffset, lowerLength); | |
| bp.copy(middleCopy, 0, buf, middleOffset, lowerLength); | |
| bp.put(buf, lowerOffset, buf, middleOffset, lowerLength); | |
| Assert.assertTrue(bp.equals(lowerCopy, middle), | |
| String.format("%d %s %d %d %d %d%n", SEED, | |
| buf.getClass().getName(), cap, | |
| lowerOffset, lowerLength, middleOffset)); | |
| } | |
| } | |
| @Test(dataProvider = "proxyPairs") | |
| public static void testPairs(BufferProxy bp, BufferProxy sbp) throws Throwable { | |
| for (int i = 0; i < ITERATIONS; i++) { | |
| int cap = Math.max(4, RND.nextInt(MAX_CAPACITY)); | |
| int cap2 = cap/2; | |
| Buffer buf = bp.create(cap); | |
| int pos = RND.nextInt(Math.max(1, cap2)); | |
| buf.position(pos); | |
| buf.mark(); | |
| int lim = pos + Math.max(1, cap - pos); | |
| buf.limit(lim); | |
| int scap = Math.max(buf.remaining(), RND.nextInt(1024)); | |
| Buffer src = sbp.create(scap); | |
| int diff = scap - buf.remaining(); | |
| int spos = diff > 0 ? RND.nextInt(diff) : 0; | |
| src.position(spos); | |
| src.mark(); | |
| int slim = spos + buf.remaining(); | |
| src.limit(slim); | |
| if (buf.isReadOnly()) { | |
| Assert.expectThrows(ReadOnlyBufferException.class, | |
| () -> bp.put(src, buf)); | |
| break; | |
| } | |
| Buffer backup = bp.create(slim - spos); | |
| bp.copy(buf, pos, backup, 0, backup.capacity()); | |
| bp.put(src, buf); | |
| buf.reset(); | |
| src.reset(); | |
| Assert.assertTrue(bp.equals(src, buf), | |
| String.format("%d %s %d %d %d %s %d %d %d%n", SEED, | |
| buf.getClass().getName(), cap, pos, lim, | |
| src.getClass().getName(), scap, spos, slim)); | |
| src.clear(); | |
| buf.clear(); | |
| bp.copy(backup, 0, buf, pos, backup.capacity()); | |
| bp.put(src, spos, buf, pos, backup.capacity()); | |
| src.position(spos); | |
| src.limit(slim); | |
| buf.position(pos); | |
| buf.limit(lim); | |
| Assert.assertTrue(bp.equals(src, buf), | |
| String.format("%d %s %d %d %d %s %d %d %d%n", SEED, | |
| buf.getClass().getName(), cap, pos, lim, | |
| src.getClass().getName(), scap, spos, slim)); | |
| } | |
| } | |
| } |