/****************************************************************************** * Copyright (c) 2006 Remy Suen. All rights reserved. This program and the * accompanying materials are made available under the terms of the Eclipse * Public License v1.0, which accompanies this distribution and is available at * http://www.eclipse.org/legal/epl-v10.html, and also the MIT license, which * also accompanies this distribution. This dual licensing scheme allows a * developer to choose either license for use when developing applications with * this code. * * Contributors: * Remy Suen <remy.suen@gmail.com> - initial API and implementation ******************************************************************************/ package org.eclipse.bittorrent.internal.torrent; import java.util.Vector; import org.eclipse.bittorrent.internal.torrent.Block; /** * A <code>PieceState</code> is a visual representation of the amount of data * that has been written and completed for a {@link Piece} at a given time. No * bytes of data are actually stored within this or the {@link Block}s that it * contains. <code>PieceState</code>s are also used for recording the state * that a torrent is in so that resuming can begin quickly when the torrent has * restarted. */ public class PieceState { /** * A <code>Vector</code> that contains all of the {@link Block}s being * represented by this. */ private final Vector blocks; /** * The number of the {@link Piece} that this is representing. */ private final int number; /** * Create and return an array of length <code>pieces</code> containing * initialized <code>PieceState</code> references * * @param pieces * the length of the desired array * @return the created array with instantiated <code>PieceState</code> * objects */ public static PieceState[] createStates(int pieces) { PieceState[] statuses = new PieceState[pieces]; for (int i = 0; i < statuses.length; i++) { statuses[i] = new PieceState(i); } return statuses; } /** * Creates a new <code>PieceState</code> with the number of the * {@link Piece} that it corresponds to. * * @param number * the corresponding <code>Piece</code>'s number */ private PieceState(int number) { this.number = number; blocks = new Vector(); } /** * Checks every {@link Block} stored within {@link #blocks} to look for * matching pairs to merge them into one. */ private void updateState() { for (int i = 0; i < blocks.size(); i++) { Block block1 = (Block) blocks.get(i); for (int j = i + 1; j < blocks.size(); j++) { Block block2 = (Block) blocks.get(j); if (block1.isConnectedToStart(block2)) { block1.prepend(block2); blocks.remove(block2); i--; break; } else if (block1.isConnectedToEnd(block2)) { block1.append(block2); blocks.remove(block2); i--; break; } } } } /** * Adds a block with <code>index</code> as its starting position and a * length of <code>blockLength</code> to this or extend an existing block * by <code>blockLength</code> if this block is connected to said existing * block. * * @param index * the starting index of this new block * @param blockLength * the length of the new block */ synchronized void addDownloadedBlock(int index, int blockLength) { for (int i = 0; i < blocks.size(); i++) { Block b = (Block) blocks.get(i); if (b.isConnectedToStart(index, blockLength)) { b.prepend(blockLength); updateState(); return; } else if (b.isConnectedToEnd(index)) { b.append(blockLength); updateState(); return; } } blocks.add(new Block(index, blockLength)); } /** * Retrieves the <code>Vector</code> that holds the {@link Block} objects * contained within this. * * @return a <code>Vector</code> that contains <code>Block</code>s * corresponding to this state */ Vector getBlocks() { return blocks; } /** * Record the piece's state as being completed. All {@link Block}s will be * removed and only one will be added starting at index zero and the length * equal to <code>pieceLength</code>. * * @param pieceLength * the length of the piece that this <code>PieceState</code> * represents */ void setAsComplete(int pieceLength) { synchronized (blocks) { // remove all of the original blocks blocks.clear(); // simply add one block with a starting index of 0 with the // specified length blocks.add(new Block(0, pieceLength)); } } /** * Resets all state information corresponding so that it is as if no data * has been downloaded thus far. */ void reset() { blocks.clear(); } /** * Parses a string to set the blocks' starting indices and lengths for this * state. The string should be of the form <code>n:a-b:c-d:e-f</code> * wherein <code>n</code> is this piece's number, and the letters within * <code>a-b</code>, <code>c-d</code>, and <code>e-f</code> would * correspond to a certain block's starting index and its length, in that * order. * * @param information * the string to parse */ public void parse(String information) { String[] split = information.split(":", 2); if (split.length != 1) { split = split[1].split(":"); for (int i = 0; i < split.length; i++) { String[] blockInfo = split[i].split("-"); int index = Integer.parseInt(blockInfo[0]); int blockLength = Integer.parseInt(blockInfo[1]) - index; addDownloadedBlock(index, blockLength); } } } /** * Returns a string representation of this <code>PieceState</code> which * is formatted in the same manner of what would be parsed in the argument * to the {@link #parse(String)} method. A <code>PieceState</code> with a * number of 5, and one single block starting at index 0 with a length of * 16384 would return the string literal <code>5:0-16384</code>. * * @return the string representation of this <code>PieceState</code> in a * format equal to what is parsed by <code>parse(String)</code> */ public String toString() { StringBuffer buffer = new StringBuffer(Integer.toString(number)); synchronized (buffer) { buffer.append(":"); for (int i = 0; i < blocks.size(); i++) { buffer.append(blocks.get(i)); buffer.append(":"); } buffer.deleteCharAt(buffer.length() - 1); } return buffer.toString(); } }