class MetastoragePageIOUtils {
    /**
     * @see MetastorageBPlusIO#getLink(long, int)
     */
    public static <IO extends BPlusIO<MetastorageRow> & MetastorageBPlusIO> long getLink(
        IO io,
        long pageAddr,
        int idx
    ) {
        assert idx < io.getCount(pageAddr) : idx;

        return PageUtils.getLong(pageAddr, io.offset(idx));
    }

    /**
     * @see MetastorageBPlusIO#getKeySize(long, int)
     */
    public static <IO extends BPlusIO<MetastorageRow> & MetastorageBPlusIO> short getKeySize(
        IO io,
        long pageAddr, int idx
    ) {
        return PageUtils.getShort(pageAddr, io.offset(idx) + 8);
    }

    /**
     * @see MetastorageBPlusIO#getKey(long, int, MetastorageRowStore)
     */
    public static <IO extends BPlusIO<MetastorageRow> & MetastorageBPlusIO> String getKey(
        IO io,
        long pageAddr,
        int idx,
        MetastorageRowStore rowStore
    ) throws IgniteCheckedException {
        int off = io.offset(idx);
        int len = PageUtils.getShort(pageAddr, off + 8);

        if (len > MetastorageTree.MAX_KEY_LEN) {
            long keyLink = PageUtils.getLong(pageAddr, off + 10);

            byte[] keyBytes = rowStore.readRow(keyLink);

            assert keyBytes != null : "[pageAddr=" + Long.toHexString(pageAddr) + ", idx=" + idx + ']';

            return new String(keyBytes);
        }
        else {
            byte[] keyBytes = PageUtils.getBytes(pageAddr, off + 10, len);

            return new String(keyBytes);
        }
    }

    /**
     * @see MetastorageBPlusIO#getDataRow(long, int, MetastorageRowStore)
     */
    public static <IO extends BPlusIO<MetastorageRow> & MetastorageBPlusIO> MetastorageDataRow getDataRow(
        IO io,
        long pageAddr,
        int idx,
        MetastorageRowStore rowStore
    ) throws IgniteCheckedException {
        long link = io.getLink(pageAddr, idx);

        int off = io.offset(idx);
        int len = PageUtils.getShort(pageAddr, off + 8);

        if (len > MetastorageTree.MAX_KEY_LEN) {
            long keyLink = PageUtils.getLong(pageAddr, off + 10);

            byte[] keyBytes = rowStore.readRow(keyLink);

            assert keyBytes != null : "[pageAddr=" + Long.toHexString(pageAddr) + ", idx=" + idx + ']';

            return new MetastorageDataRow(link, new String(keyBytes), keyLink);
        }
        else {
            byte[] keyBytes = PageUtils.getBytes(pageAddr, off + 10, len);

            return new MetastorageDataRow(link, new String(keyBytes), 0L);
        }
    }

    /**
     * @see BPlusIO#store(long, int, org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusIO, long, int)
     */
    public static <IO extends BPlusIO<MetastorageRow> & MetastorageBPlusIO> void store(
        IO dstIo,
        long dstPageAddr,
        int dstIdx,
        BPlusIO<MetastorageRow> srcIo,
        long srcPageAddr,
        int srcIdx
    ) {
        int srcOff = srcIo.offset(srcIdx);
        int dstOff = dstIo.offset(dstIdx);

        GridUnsafe.copyMemory(srcPageAddr + srcOff, dstPageAddr + dstOff, MetastorageTree.MAX_KEY_LEN + 10);
    }

    /**
     * @see BPlusIO#storeByOffset(long, int, Object)
     */
    public static <IO extends BPlusIO<MetastorageRow> & MetastorageBPlusIO> void storeByOffset(
        IO io,
        long pageAddr,
        int off,
        MetastorageRow row
    ) {<FILL_FUNCTION_BODY>}
}

        assert row.link() != 0;

        PageUtils.putLong(pageAddr, off, row.link());

        byte[] bytes = row.key().getBytes();
        assert bytes.length <= Short.MAX_VALUE;

        if (row.keyLink() != 0) {
            PageUtils.putShort(pageAddr, off + 8, (short)bytes.length);
            PageUtils.putLong(pageAddr, off + 10, row.keyLink());
        }
        else {
            assert bytes.length <= MetastorageTree.MAX_KEY_LEN;

            PageUtils.putShort(pageAddr, off + 8, (short)bytes.length);
            PageUtils.putBytes(pageAddr, off + 10, bytes);
        }
    
<no_super_class>