package com.limegroup.gnutella.tigertree.dime;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.limegroup.gnutella.dime.DIMEGenerator;
import com.limegroup.gnutella.dime.DIMERecord;
import com.limegroup.gnutella.tigertree.ThexWriter;
import com.limegroup.gnutella.tigertree.HashTree;
import com.limegroup.gnutella.tigertree.HashTreeNodeManager;
import com.limegroup.gnutella.tigertree.HashTreeUtils;
import com.limegroup.gnutella.tigertree.HashTreeWriteHandler;
import com.limegroup.gnutella.util.UUID;
/**
* @author Gregorio Roper
*
* Class handling all the reading and writing of HashTrees to the network
*/
class TigerDimeWriteHandler implements HashTreeWriteHandler {
private static final Log LOG = LogFactory.getLog(TigerDimeWriteHandler.class);
private static final String OUTPUT_TYPE = "application/dime";
private static final String XML_TYPE = "text/xml";
private static final byte[] TREE_TYPE_BYTES =
getBytes(TigerDimeUtils.SERIALIZED_TREE_TYPE);
private static final byte[] XML_TYPE_BYTES =
getBytes(XML_TYPE);
private static final String XML_TREE_DESC_START =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<!DOCTYPE hashtree " + TigerDimeUtils.SYSTEM_STRING + " \"" + TigerDimeUtils.DTD_SYSTEM_ID + "\">"
+ "<hashtree>";
private static final String XML_TREE_DESC_END = "</hashtree>";
/**
* Returns the bytes of a string in UTF-8 format, or in the default
* format if UTF-8 failed for whatever reason.
*/
private static byte[] getBytes(String string) {
try {
return string.getBytes("UTF-8");
} catch(UnsupportedEncodingException uee) {
LOG.debug(string, uee);
return string.getBytes();
}
}
///////////////////////// WRITING ///////////////////////
/**
* The generator containing the DIME message to send.
*/
private final DIMEGenerator GENERATOR;
/**
* Constructs a new handler for sending
* @param tree
* the <tt>HashTree</tt> to construct this message from
*/
public TigerDimeWriteHandler(HashTree tree, HashTreeNodeManager hashTreeNodeManager) {
LOG.trace("creating HashTreeHandler for sending");
UUID uri = UUID.nextUUID();
GENERATOR = new DIMEGenerator();
GENERATOR.add(new XMLRecord(tree, uri));
GENERATOR.add(new TreeRecord(tree, uri, hashTreeNodeManager));
}
/**
* method for writing a HashTree to an OutputStream
*
* @param os
* the <tt>OutputStream</tt> to write to.
* @throws IOException
* if there was a problem writing to os.
*/
public void write(OutputStream os) throws IOException {
GENERATOR.write(os);
}
/**
* Determines the length of the written data.
*/
public int getOutputLength() {
return GENERATOR.getLength();
}
/**
* Determines the mime type of the output.
*/
public String getOutputType() {
return OUTPUT_TYPE;
}
public ThexWriter createAsyncWriter() {
return new AsyncTigerTreeWriter(GENERATOR.createAsyncWriter());
}
/**
* A simple XML DIMERecord.
*/
private static class XMLRecord extends DIMERecord {
XMLRecord(HashTree tree, UUID uri) {
super(DIMERecord.TYPE_MEDIA_TYPE, null, null,
XML_TYPE_BYTES, getXML(tree, uri));
}
/**
* Constructs the XML bytes.
*/
private static byte[] getXML(HashTree tree, UUID uri) {
String xml =
XML_TREE_DESC_START
+ "<file size='"
+ tree.getFileSize()
+ "' segmentsize='"
+ HashTreeUtils.BLOCK_SIZE
+ "'/>"
+ "<digest algorithm='"
+ TigerDimeUtils.DIGEST
+ "' outputsize='"
+ TigerDimeUtils.HASH_SIZE
+ "'/>"
+ "<serializedtree depth='"
+ tree.getDepth()
+ "' type='"
+ TigerDimeUtils.SERIALIZED_TREE_TYPE
+ "' uri='uuid:"
+ uri
+ "'/>"
+ XML_TREE_DESC_END;
return getBytes(xml);
}
}
/**
* Private DIMERecord for a Tree.
*/
private static class TreeRecord extends DIMERecord {
/**
* The tree of this record.
*/
private final HashTree tigerTree;
/**
* The length of the tree.
*/
private final int length;
/** The manager to retrieve nodes from. */
private final HashTreeNodeManager hashTreeNodeManager;
TreeRecord(HashTree tree, UUID uri, HashTreeNodeManager hashTreeNodeManager) {
super(DIMERecord.TYPE_ABSOLUTE_URI, null,
getBytes("uuid:" + uri),
TREE_TYPE_BYTES, null);
tigerTree = tree;
length = tigerTree.getNodeCount() * TigerDimeUtils.HASH_SIZE;
this.hashTreeNodeManager = hashTreeNodeManager;
}
/**
* Writes the tree's data to the specified output stream.
*/
@Override
public void writeData(OutputStream out) throws IOException {
for(List<byte[]> list : hashTreeNodeManager.getAllNodes(tigerTree)) {
for(byte[] b : list)
out.write(b);
}
writePadding(getDataLength(), out);
}
/**
* Determines the length of the data.
*/
@Override
public int getDataLength() {
return length;
}
}
}