package org.yi.happy.archive.block;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;

import org.yi.happy.archive.ByteString;
import org.yi.happy.archive.Bytes;

/**
 * The part that is common for all the block types.
 */
public abstract class AbstractBlock implements Block {
    private byte[] ENDL = new byte[] { '\r', '\n' };
    private byte[] SEPARATOR = new byte[] { ':', ' ' };

    /**
     * get the block representation as bytes, assuming all the meta entries are
     * valid and the body is not null.
     */
    @Override
    public byte[] asBytes() {
	/*
	 * This can probably be done more efficiently, and maybe even more
	 * simply, without the stream.
	 */
	try {
	    ByteArrayOutputStream out = new ByteArrayOutputStream();

	    for (Map.Entry<String, String> i : getMeta().entrySet()) {
		out.write(ByteString.toUtf8(i.getKey()));
		out.write(SEPARATOR);
		out.write(ByteString.toUtf8(i.getValue()));
		out.write(ENDL);
	    }

	    out.write(ENDL);

	    out.write(getBody().toByteArray());

	    return out.toByteArray();
	} catch (IOException e) {
	    throw new Error(e);
	}
    }

    @Override
    public abstract Map<String, String> getMeta();

    @Override
    public abstract Bytes getBody();
}

package org.yi.happy.archive.block;

import java.util.Map;

import org.yi.happy.archive.ByteString;
import org.yi.happy.archive.Bytes;

/**
 * The part that is common for all the block types.
 */
public abstract class AbstractBlock implements Block {
    private byte[] ENDL = new byte[] { '\r', '\n' };
    private byte[] SEPARATOR = new byte[] { ':', ' ' };

    /**
     * get the block representation as bytes, assuming all the meta entries are
     * valid and the body is not null.
     */
    @Override
    public byte[] asBytes() {
	Map<String, String> m = getMeta();
	Bytes b = getBody();

	/*
	 * calculate the size and gather the header bytes.
	 */
	byte[][] p = new byte[m.size() * 2][];
	/**
	 * index into the part list
	 */
	int i = 0;
	/**
	 * number of bytes.
	 */
	int n = 0;
	for (Map.Entry<String, String> j : m.entrySet()) {
	    p[i] = ByteString.toUtf8(j.getKey());
	    n += p[i].length;
	    n += SEPARATOR.length;
	    i++;
	    p[i] = ByteString.toUtf8(j.getValue());
	    n += p[i].length;
	    n += ENDL.length;
	    i++;
	}
	n += ENDL.length;
	n += b.getSize();

	/*
	 * fill in the output.
	 */
	byte[] out = new byte[n];
	n = 0;
	for (i = 0; i < p.length; i += 2) {
	    System.arraycopy(p[i], 0, out, n, p[i].length);
	    n += p[i].length;
	    System.arraycopy(SEPARATOR, 0, out, n, SEPARATOR.length);
	    n += SEPARATOR.length;
	    System.arraycopy(p[i + 1], 0, out, n, p[i + 1].length);
	    n += p[i + 1].length;
	    System.arraycopy(ENDL, 0, out, n, ENDL.length);
	    n += ENDL.length;
	}
	System.arraycopy(ENDL, 0, out, n, ENDL.length);
	n += ENDL.length;
	b.getBytes(0, out, n, b.getSize());
	return out;
    }

    @Override
    public abstract Map<String, String> getMeta();

    @Override
    public abstract Bytes getBody();
}

write AbstractBlock.toByteArray() without using a stream to reduce copying, and make it look simpler.