/* * Copyright (C) 2012-2015 DataStax Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.datastax.driver.core; import com.datastax.driver.core.policies.RoundRobinPolicy; import com.datastax.driver.core.policies.WhiteListPolicy; import com.google.common.base.Predicate; import com.google.common.base.Throwables; import com.google.common.collect.Sets; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Uninterruptibles; import com.sun.management.OperatingSystemMXBean; import io.netty.channel.EventLoopGroup; import org.scassandra.Scassandra; import org.scassandra.ScassandraFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.lang.management.ManagementFactory; import java.math.BigDecimal; import java.math.BigInteger; import java.net.*; import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import static com.datastax.driver.core.ConditionChecker.check; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; /** * A number of static fields/methods handy for tests. */ public abstract class TestUtils { public static final String IP_PREFIX; static { String ip_prefix = System.getProperty("ipprefix"); if (ip_prefix == null || ip_prefix.isEmpty()) { ip_prefix = "127.0.1."; } IP_PREFIX = ip_prefix; } private static final Logger logger = LoggerFactory.getLogger(TestUtils.class); public static final String CREATE_KEYSPACE_SIMPLE_FORMAT = "CREATE KEYSPACE %s WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor' : %d }"; public static final String CREATE_KEYSPACE_GENERIC_FORMAT = "CREATE KEYSPACE %s WITH replication = { 'class' : '%s', %s }"; public static final String SELECT_ALL_FORMAT = "SELECT * FROM %s"; public static final int TEST_BASE_NODE_WAIT = SystemProperties.getInt("com.datastax.driver.TEST_BASE_NODE_WAIT", 60); public static void setValue(SettableByIndexData<?> data, int i, DataType type, Object value) { switch (type.getName()) { case ASCII: data.setString(i, (String) value); break; case BIGINT: data.setLong(i, (Long) value); break; case BLOB: data.setBytes(i, (ByteBuffer) value); break; case BOOLEAN: data.setBool(i, (Boolean) value); break; case COUNTER: // Just a no-op, we shouldn't handle counters the same way than other types break; case DECIMAL: data.setDecimal(i, (BigDecimal) value); break; case DOUBLE: data.setDouble(i, (Double) value); break; case FLOAT: data.setFloat(i, (Float) value); break; case INET: data.setInet(i, (InetAddress) value); break; case TINYINT: data.setByte(i, (Byte) value); break; case SMALLINT: data.setShort(i, (Short) value); break; case INT: data.setInt(i, (Integer) value); break; case TEXT: data.setString(i, (String) value); break; case TIMESTAMP: data.setTimestamp(i, (Date) value); break; case DATE: data.setDate(i, (LocalDate) value); break; case TIME: data.setTime(i, (Long) value); break; case UUID: data.setUUID(i, (UUID) value); break; case VARCHAR: data.setString(i, (String) value); break; case VARINT: data.setVarint(i, (BigInteger) value); break; case TIMEUUID: data.setUUID(i, (UUID) value); break; case LIST: data.setList(i, (List<?>) value); break; case SET: data.setSet(i, (Set<?>) value); break; case MAP: data.setMap(i, (Map<?, ?>) value); break; default: throw new RuntimeException("Missing handling of " + type); } } @SuppressWarnings({"unchecked", "rawtypes"}) public static void setValue(SettableByNameData<?> data, String name, DataType type, Object value) { switch (type.getName()) { case ASCII: data.setString(name, (String) value); break; case BIGINT: data.setLong(name, (Long) value); break; case BLOB: data.setBytes(name, (ByteBuffer) value); break; case BOOLEAN: data.setBool(name, (Boolean) value); break; case COUNTER: // Just a no-op, we shouldn't handle counters the same way than other types break; case DECIMAL: data.setDecimal(name, (BigDecimal) value); break; case DOUBLE: data.setDouble(name, (Double) value); break; case FLOAT: data.setFloat(name, (Float) value); break; case INET: data.setInet(name, (InetAddress) value); break; case TINYINT: data.setByte(name, (Byte) value); break; case SMALLINT: data.setShort(name, (Short) value); break; case INT: data.setInt(name, (Integer) value); break; case TEXT: data.setString(name, (String) value); break; case TIMESTAMP: data.setTimestamp(name, (Date) value); break; case DATE: data.setDate(name, (LocalDate) value); break; case TIME: data.setTime(name, (Long) value); break; case UUID: data.setUUID(name, (UUID) value); break; case VARCHAR: data.setString(name, (String) value); break; case VARINT: data.setVarint(name, (BigInteger) value); break; case TIMEUUID: data.setUUID(name, (UUID) value); break; case LIST: data.setList(name, (List) value); break; case SET: data.setSet(name, (Set) value); break; case MAP: data.setMap(name, (Map) value); break; default: throw new RuntimeException("Missing handling of " + type); } } public static Object getValue(GettableByIndexData data, int i, DataType type, CodecRegistry codecRegistry) { switch (type.getName()) { case ASCII: return data.getString(i); case BIGINT: return data.getLong(i); case BLOB: return data.getBytes(i); case BOOLEAN: return data.getBool(i); case COUNTER: return data.getLong(i); case DECIMAL: return data.getDecimal(i); case DOUBLE: return data.getDouble(i); case FLOAT: return data.getFloat(i); case INET: return data.getInet(i); case TINYINT: return data.getByte(i); case SMALLINT: return data.getShort(i); case INT: return data.getInt(i); case TEXT: return data.getString(i); case TIMESTAMP: return data.getTimestamp(i); case DATE: return data.getDate(i); case TIME: return data.getTime(i); case UUID: return data.getUUID(i); case VARCHAR: return data.getString(i); case VARINT: return data.getVarint(i); case TIMEUUID: return data.getUUID(i); case LIST: Class<?> listEltClass = codecRegistry.codecFor(type.getTypeArguments().get(0)).getJavaType().getRawType(); return data.getList(i, listEltClass); case SET: Class<?> setEltClass = codecRegistry.codecFor(type.getTypeArguments().get(0)).getJavaType().getRawType(); return data.getSet(i, setEltClass); case MAP: Class<?> keyClass = codecRegistry.codecFor(type.getTypeArguments().get(0)).getJavaType().getRawType(); Class<?> valueClass = codecRegistry.codecFor(type.getTypeArguments().get(1)).getJavaType().getRawType(); return data.getMap(i, keyClass, valueClass); } throw new RuntimeException("Missing handling of " + type); } public static Object getValue(GettableByNameData data, String name, DataType type, CodecRegistry codecRegistry) { switch (type.getName()) { case ASCII: return data.getString(name); case BIGINT: return data.getLong(name); case BLOB: return data.getBytes(name); case BOOLEAN: return data.getBool(name); case COUNTER: return data.getLong(name); case DECIMAL: return data.getDecimal(name); case DOUBLE: return data.getDouble(name); case FLOAT: return data.getFloat(name); case INET: return data.getInet(name); case TINYINT: return data.getByte(name); case SMALLINT: return data.getShort(name); case INT: return data.getInt(name); case TEXT: return data.getString(name); case TIMESTAMP: return data.getTimestamp(name); case DATE: return data.getDate(name); case TIME: return data.getTime(name); case UUID: return data.getUUID(name); case VARCHAR: return data.getString(name); case VARINT: return data.getVarint(name); case TIMEUUID: return data.getUUID(name); case LIST: Class<?> listEltClass = codecRegistry.codecFor(type.getTypeArguments().get(0)).getJavaType().getRawType(); return data.getList(name, listEltClass); case SET: Class<?> setEltClass = codecRegistry.codecFor(type.getTypeArguments().get(0)).getJavaType().getRawType(); return data.getSet(name, setEltClass); case MAP: Class<?> keyClass = codecRegistry.codecFor(type.getTypeArguments().get(0)).getJavaType().getRawType(); Class<?> valueClass = codecRegistry.codecFor(type.getTypeArguments().get(1)).getJavaType().getRawType(); return data.getMap(name, keyClass, valueClass); } throw new RuntimeException("Missing handling of " + type); } // Always return the "same" value for each type @SuppressWarnings("serial") public static Object getFixedValue(final DataType type) { try { switch (type.getName()) { case ASCII: return "An ascii string"; case BIGINT: return 42L; case BLOB: return ByteBuffer.wrap(new byte[]{(byte) 4, (byte) 12, (byte) 1}); case BOOLEAN: return true; case COUNTER: throw new UnsupportedOperationException("Cannot 'getSomeValue' for counters"); case DURATION: return Duration.from("1h20m3s"); case DECIMAL: return new BigDecimal("3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"); case DOUBLE: return 3.142519; case FLOAT: return 3.142519f; case INET: return InetAddress.getByAddress(new byte[]{(byte) 127, (byte) 0, (byte) 0, (byte) 1}); case TINYINT: return (byte) 25; case SMALLINT: return (short) 26; case INT: return 24; case TEXT: return "A text string"; case TIMESTAMP: return new Date(1352288289L); case DATE: return LocalDate.fromDaysSinceEpoch(0); case TIME: return 54012123450000L; case UUID: return UUID.fromString("087E9967-CCDC-4A9B-9036-05930140A41B"); case VARCHAR: return "A varchar string"; case VARINT: return new BigInteger("123456789012345678901234567890"); case TIMEUUID: return UUID.fromString("FE2B4360-28C6-11E2-81C1-0800200C9A66"); case LIST: return new ArrayList<Object>() {{ add(getFixedValue(type.getTypeArguments().get(0))); }}; case SET: return new HashSet<Object>() {{ add(getFixedValue(type.getTypeArguments().get(0))); }}; case MAP: return new HashMap<Object, Object>() {{ put(getFixedValue(type.getTypeArguments().get(0)), getFixedValue(type.getTypeArguments().get(1))); }}; } } catch (Exception e) { throw new RuntimeException(e); } throw new RuntimeException("Missing handling of " + type); } // Always return the "same" value for each type @SuppressWarnings("serial") public static Object getFixedValue2(final DataType type) { try { switch (type.getName()) { case ASCII: return "A different ascii string"; case BIGINT: return Long.MAX_VALUE; case BLOB: ByteBuffer bb = ByteBuffer.allocate(64); bb.putInt(0xCAFE); bb.putShort((short) 3); bb.putShort((short) 45); return bb; case BOOLEAN: return false; case COUNTER: throw new UnsupportedOperationException("Cannot 'getSomeValue' for counters"); case DECIMAL: return new BigDecimal("12.3E+7"); case DOUBLE: return Double.POSITIVE_INFINITY; case FLOAT: return Float.POSITIVE_INFINITY; case INET: return InetAddress.getByName("123.123.123.123"); case TINYINT: return Byte.MAX_VALUE; case SMALLINT: return Short.MAX_VALUE; case INT: return Integer.MAX_VALUE; case TEXT: return "résumé"; case TIMESTAMP: return new Date(872835240000L); case DATE: return LocalDate.fromDaysSinceEpoch(0); case TIME: return 54012123450000L; case UUID: return UUID.fromString("067e6162-3b6f-4ae2-a171-2470b63dff00"); case VARCHAR: return "A different varchar résumé"; case VARINT: return new BigInteger(Integer.toString(Integer.MAX_VALUE) + "000"); case TIMEUUID: return UUID.fromString("FE2B4360-28C6-11E2-81C1-0800200C9A66"); case LIST: return new ArrayList<Object>() {{ add(getFixedValue2(type.getTypeArguments().get(0))); }}; case SET: return new HashSet<Object>() {{ add(getFixedValue2(type.getTypeArguments().get(0))); }}; case MAP: return new HashMap<Object, Object>() {{ put(getFixedValue2(type.getTypeArguments().get(0)), getFixedValue2(type.getTypeArguments().get(1))); }}; } } catch (Exception e) { throw new RuntimeException(e); } throw new RuntimeException("Missing handling of " + type); } /** * Returns a set of all primitive types supported by the given protocolVersion. * <p> * Primitive types are defined as the types that don't have type arguments * (that is excluding lists, sets, and maps, tuples and udts). * </p> * * @param protocolVersion protocol version to get types for. * @return returns a set of all the primitive types for the given protocolVersion. */ static Set<DataType> allPrimitiveTypes(final ProtocolVersion protocolVersion) { return Sets.filter(DataType.allPrimitiveTypes(), new Predicate<DataType>() { @Override public boolean apply(DataType dataType) { return protocolVersion.compareTo(dataType.getName().minProtocolVersion) >= 0; } }); } // Wait for a node to be up and running // This is used because there is some delay between when a node has been // added through ccm and when it's actually available for querying public static void waitForUp(String node, Cluster cluster) { waitFor(node, cluster, TEST_BASE_NODE_WAIT, false); } public static void waitForUp(String node, Cluster cluster, int timeoutSeconds) { waitFor(node, cluster, timeoutSeconds, false); } public static void waitForDown(String node, Cluster cluster) { waitFor(node, cluster, TEST_BASE_NODE_WAIT * 3, true); } public static void waitForDown(String node, Cluster cluster, int timeoutSeconds) { waitFor(node, cluster, timeoutSeconds, true); } private static void waitFor(String node, Cluster cluster, int timeoutSeconds, boolean waitForDown) { if (waitForDown) logger.debug("Waiting for node to leave: {}", node); else logger.debug("Waiting for upcoming node: {}", node); // In the case where the we've killed the last node in the cluster, if we haven't // tried doing an actual query, the driver won't realize that last node is dead until // keep alive kicks in, but that's a fairly long time. So we cheat and trigger a force // the detection by forcing a request. if (waitForDown) Futures.getUnchecked(cluster.manager.submitSchemaRefresh(null, null, null, null)); if (waitForDown) { check() .every(1, SECONDS) .before(timeoutSeconds, SECONDS) .that(new HostIsDown(cluster, node)) .becomesTrue(); } else { check() .every(1, SECONDS) .before(timeoutSeconds, SECONDS) .that(new HostIsUp(cluster, node)) .becomesTrue(); } } private static class HostIsDown implements Callable<Boolean> { private final Cluster cluster; private final String ip; public HostIsDown(Cluster cluster, String ip) { this.cluster = cluster; this.ip = ip; } @Override public Boolean call() throws Exception { final Host host = findHost(cluster, ip); return host == null || !host.isUp(); } } private static class HostIsUp implements Callable<Boolean> { private final Cluster cluster; private final String ip; public HostIsUp(Cluster cluster, String ip) { this.cluster = cluster; this.ip = ip; } @Override public Boolean call() throws Exception { final Host host = findHost(cluster, ip); return host != null && host.isUp(); } } /** * Returns the IP of the {@code nth} host in the cluster (counting from 1, i.e., * {@code ipOfNode(1)} returns the IP of the first node. * <p/> * In multi-DC setups, nodes are numbered in ascending order of their datacenter number. * E.g. with 2 DCs and 3 nodes in each DC, the first node in DC 2 is number 4. * * @return the IP of the {@code nth} host in the cluster. */ public static String ipOfNode(int n) { return IP_PREFIX + n; } /** * Returns the address of the {@code nth} host in the cluster (counting from 1, i.e., * {@code ipOfNode(1)} returns the address of the first node. * <p/> * In multi-DC setups, nodes are numbered in ascending order of their datacenter number. * E.g. with 2 DCs and 3 nodes in each DC, the first node in DC 2 is number 4. * * @return the IP of the {@code nth} host in the cluster. */ public static InetAddress addressOfNode(int i) { try { return InetAddress.getByName(IP_PREFIX + i); } catch (UnknownHostException e) { // should never happen throw Throwables.propagate(e); } } public static Host findOrWaitForHost(Cluster cluster, int node, long duration, TimeUnit unit) { return findOrWaitForHost(cluster, ipOfNode(node), duration, unit); } public static Host findOrWaitForHost(final Cluster cluster, final String address, long duration, TimeUnit unit) { Host host = findHost(cluster, address); if (host == null) { final CountDownLatch addSignal = new CountDownLatch(1); Host.StateListener addListener = new StateListenerBase() { @Override public void onAdd(Host host) { if (host.getAddress().getHostAddress().equals(address)) { // for a new node, because of this we also listen for add events. addSignal.countDown(); } } }; cluster.register(addListener); try { // Wait until an add event occurs or we timeout. if (addSignal.await(duration, unit)) { host = findHost(cluster, address); } } catch (InterruptedException e) { return null; } finally { cluster.unregister(addListener); } } return host; } public static Host findHost(Cluster cluster, int hostNumber) { return findHost(cluster, ipOfNode(hostNumber)); } public static Host findHost(Cluster cluster, String address) { // Note: we can't rely on ProtocolOptions.getPort() to build an InetSocketAddress and call metadata.getHost, // because the port doesn't have the correct value if addContactPointsWithPorts was used to create the Cluster // (JAVA-860 will solve that) for (Host host : cluster.getMetadata().allHosts()) { if (host.getAddress().getHostAddress().equals(address)) return host; } return null; } public static Host findOrWaitForControlConnection(final Cluster cluster, long duration, TimeUnit unit) { ControlConnection controlConnection = cluster.manager.controlConnection; long durationNs = TimeUnit.NANOSECONDS.convert(duration, unit); long start = System.nanoTime(); while (System.nanoTime() - start < durationNs) { if (controlConnection.isOpen()) { return controlConnection.connectedHost(); } Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); } return null; } public static HostConnectionPool poolOf(Session session, int hostNumber) { SessionManager sessionManager = (SessionManager) session; return sessionManager.pools.get(findHost(session.getCluster(), hostNumber)); } public static int numberOfLocalCoreConnections(Cluster cluster) { Configuration configuration = cluster.getConfiguration(); return configuration.getPoolingOptions().getCoreConnectionsPerHost(HostDistance.LOCAL); } /** * @return A Scassandra instance with an arbitrarily chosen binary port from 8042-8142 and admin port from * 8052-8152. */ public static Scassandra createScassandraServer() { int binaryPort = findAvailablePort(); int adminPort = findAvailablePort(); return ScassandraFactory.createServer(ipOfNode(1), binaryPort, ipOfNode(1), adminPort); } private static final ConcurrentMap<String, AtomicInteger> IDENTIFIERS = new ConcurrentHashMap<String, AtomicInteger>(); /** * Generates a unique CQL identifier with the given prefix. * * @param prefix The prefix for the identifier * @return a unique CQL identifier. */ public static String generateIdentifier(String prefix) { AtomicInteger seq = new AtomicInteger(0); AtomicInteger previous = IDENTIFIERS.putIfAbsent(prefix, seq); if (previous != null) seq = previous; return prefix + seq.incrementAndGet(); } /** * Finds an available port in the ephemeral range. * This is loosely inspired by Apache MINA's AvailablePortFinder. * * @return A local port that is currently unused. */ public synchronized static int findAvailablePort() throws RuntimeException { ServerSocket ss = null; try { // let the system pick an ephemeral port ss = new ServerSocket(0); ss.setReuseAddress(true); return ss.getLocalPort(); } catch (IOException e) { throw Throwables.propagate(e); } finally { if (ss != null) { try { ss.close(); } catch (IOException e) { Throwables.propagate(e); } } } } private static final Predicate<InetSocketAddress> PORT_IS_UP = new Predicate<InetSocketAddress>() { @Override public boolean apply(InetSocketAddress address) { return pingPort(address.getAddress(), address.getPort()); } }; public static void waitUntilPortIsUp(InetSocketAddress address) { check().before(5, MINUTES).that(address, PORT_IS_UP).becomesTrue(); } public static void waitUntilPortIsDown(InetSocketAddress address) { check().before(5, MINUTES).that(address, PORT_IS_UP).becomesFalse(); } public static boolean pingPort(InetAddress address, int port) { logger.debug("Trying {}:{}...", address, port); boolean connectionSuccessful = false; Socket socket = null; try { socket = new Socket(address, port); connectionSuccessful = true; logger.debug("Successfully connected"); } catch (IOException e) { logger.debug("Connection failed"); } finally { if (socket != null) try { socket.close(); } catch (IOException e) { logger.warn("Error closing socket to " + address, e); } } return connectionSuccessful; } /** * @return a {@link Cluster} instance that connects only to the control host of the given cluster. */ public static Cluster buildControlCluster(Cluster cluster, CCMAccess ccm) { Host controlHost = cluster.manager.controlConnection.connectedHost(); List<InetSocketAddress> singleAddress = Collections.singletonList(controlHost.getSocketAddress()); return Cluster.builder() .addContactPoints(controlHost.getSocketAddress().getAddress()) .withPort(ccm.getBinaryPort()) .withLoadBalancingPolicy(new WhiteListPolicy(new RoundRobinPolicy(), singleAddress)) .build(); } /** * @return a {@link QueryOptions} that disables debouncing by setting intervals to 0ms. */ public static QueryOptions nonDebouncingQueryOptions() { return new QueryOptions().setRefreshNodeIntervalMillis(0) .setRefreshNodeListIntervalMillis(0) .setRefreshSchemaIntervalMillis(0); } /** * A custom {@link NettyOptions} that shuts down the {@link EventLoopGroup} after * no quiet time. This is useful for tests that consistently close clusters as * otherwise there is a 2 second delay (from JAVA-914). */ public static NettyOptions nonQuietClusterCloseOptions = new NettyOptions() { @Override public void onClusterClose(EventLoopGroup eventLoopGroup) { eventLoopGroup.shutdownGracefully(0, 15, SECONDS).syncUninterruptibly(); } }; /** * Executes a task and catches any exception. * * @param task The task to execute. * @param logException Whether to log the exception, if any. */ public static void executeNoFail(Runnable task, boolean logException) { try { task.run(); } catch (Exception e) { if (logException) logger.error(e.getMessage(), e); } } /** * Executes a task and catches any exception. * * @param task The task to execute. * @param logException Whether to log the exception, if any. */ public static void executeNoFail(Callable<?> task, boolean logException) { try { task.call(); } catch (Exception e) { if (logException) logger.error(e.getMessage(), e); } } /** * Returns the system's free memory in megabytes. * <p/> * This includes the free physical memory + the free swap memory. */ public static long getFreeMemoryMB() { OperatingSystemMXBean bean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); return (bean.getFreePhysicalMemorySize() + bean.getFreeSwapSpaceSize()) / 1024 / 1024; } /** * Helper for generating a DynamicCompositeType {@link ByteBuffer} from the given parameters. * <p/> * Any of params given as an Integer will be considered with a field name of 'i', any as String will * be considered with a field name of 's'. * * @param params params to serialize. * @return bytes representing a DynamicCompositeType. */ public static ByteBuffer serializeForDynamicCompositeType(Object... params) { List<ByteBuffer> l = new ArrayList<ByteBuffer>(); int size = 0; for (Object p : params) { if (p instanceof Integer) { ByteBuffer elt = ByteBuffer.allocate(2 + 2 + 4 + 1); elt.putShort((short) (0x8000 | 'i')); elt.putShort((short) 4); elt.putInt((Integer) p); elt.put((byte) 0); elt.flip(); size += elt.remaining(); l.add(elt); } else if (p instanceof String) { ByteBuffer bytes = ByteBuffer.wrap(((String) p).getBytes()); ByteBuffer elt = ByteBuffer.allocate(2 + 2 + bytes.remaining() + 1); elt.putShort((short) (0x8000 | 's')); elt.putShort((short) bytes.remaining()); elt.put(bytes); elt.put((byte) 0); elt.flip(); size += elt.remaining(); l.add(elt); } else { throw new RuntimeException(); } } ByteBuffer res = ByteBuffer.allocate(size); for (ByteBuffer bb : l) res.put(bb); res.flip(); return res; } /** * Helper for generating a Composite {@link ByteBuffer} from the given parameters. * <p/> * Expects Integer and String types for parameters. * * @param params params to serialize. * @return bytes representing a CompositeType */ public static ByteBuffer serializeForCompositeType(Object... params) { List<ByteBuffer> l = new ArrayList<ByteBuffer>(); int size = 0; for (Object p : params) { if (p instanceof Integer) { ByteBuffer elt = ByteBuffer.allocate(2 + 4 + 1); elt.putShort((short) 4); elt.putInt((Integer) p); elt.put((byte) 0); elt.flip(); size += elt.remaining(); l.add(elt); } else if (p instanceof String) { ByteBuffer bytes = ByteBuffer.wrap(((String) p).getBytes()); ByteBuffer elt = ByteBuffer.allocate(2 + bytes.remaining() + 1); elt.putShort((short) bytes.remaining()); elt.put(bytes); elt.put((byte) 0); elt.flip(); size += elt.remaining(); l.add(elt); } else { throw new RuntimeException(); } } ByteBuffer res = ByteBuffer.allocate(size); for (ByteBuffer bb : l) res.put(bb); res.flip(); return res; } }