/*
* 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));
}
}
}
Messung V0.5 C=88 H=92 G=89
¤ Dauer der Verarbeitung: 0.7 Sekunden
¤
*© Formatika GbR, Deutschland