class MessageFile implements Closeable {

    public long append(long timestamp, String routingKey, ReadableByteChannel payload, int payloadSize) throws IOException {
        int n = routingKey.length();
        if (n > 255) throw new IllegalArgumentException("Routing key length " + n + " > 255 characters");

        byte[] routingKeyBytes = routingKey.getBytes(UTF8);

        synchronized (channel) {
            if (length + MESSAGE_HEADER_SIZE + routingKeyBytes.length + payloadSize > maxFileSize) return -1;

            header.clear();
            channel.position(length);
            header.put(TYPE_MESSAGE);
            header.putLong(timestamp);
            header.putShort((short)routingKeyBytes.length);
            header.putInt(payloadSize);
            header.put(routingKeyBytes);
            header.flip();

            int id = length - FILE_HEADER_SIZE;
            channel.write(header);
            long sz = channel.transferFrom(payload, channel.position(), payloadSize);
            if (sz != payloadSize) {
                throw new IOException("Only read " + sz + " bytes from payload channel instead of " + payloadSize);
            }
            length = (int)channel.position() + payloadSize; // update after write so a partial write won't corrupt file

            // see if we need to start a new histogram bucket
            if (bucketIndex < 0 || ((id - bucketMessageId >= bytesPerBucket) && bucketIndex < MAX_BUCKETS - 1)) {
                if (bucketIndex >= 0) {
                    putBucketDataInFileHeader();
                    ++bucketIndex;
                } else {
                    bucketIndex = 0;
                }
                bucketMessageId = id;
                bucketTimestamp = timestamp;
                bucketCount = 1;
            } else {
                ++bucketCount;
            }

            mostRecentTimestamp = timestamp;
            return firstMessageId + id;
        }
    }

    @SuppressWarnings("StatementWithEmptyBody") public  MessageFile(File file, long firstMessageId);
    @SuppressWarnings("StatementWithEmptyBody") public  MessageFile(File file, long firstMessageId, int maxFileSize);

}