Exemplo n.º 1
0
/**
 * Handles queries for leaves. Queries of leaves are just relayed to an ultrapeer.
 *
 * @author Leo Nobach <*****@*****.**>
 * @version 05/06/2011
 */
public class QueryHandler {

  Leaf owner;
  private Gnutella06ConnectionManager<?> mgr;

  static Logger log = SimLogger.getLogger(QueryHandler.class);

  /**
   * Creates the query handler for the given leaf and its connection manager mgr.
   *
   * @param owner
   * @param mgr
   */
  public QueryHandler(Leaf owner, Gnutella06ConnectionManager<?> mgr) {
    this.owner = owner;
    this.mgr = mgr;
  }

  /**
   * Starts a query with the given query info by relaying it to an ultrapeer
   *
   * @param info
   */
  public void startQuery(IQueryInfo info, int hitsWanted) {
    Query q = new Query(info);
    owner.getLocalEventDispatcher().queryStarted(owner.getOwnContact(), q);
    Gnutella06OverlayContact peerForQuery = mgr.getRandomContact();
    if (peerForQuery != null) {
      new LeafQueryOperation(owner, q, hitsWanted, peerForQuery, this.new QueryCallback(q))
          .scheduleImmediately();
    } else {
      log.debug("Cannot start query because node " + owner.getOwnContact() + " has no ultrapeer.");
    }
  }

  /** Called when the leaf query was finished. */
  class QueryCallback implements OperationCallback<List<QueryHit>> {

    private Query q;

    public QueryCallback(Query q) {
      this.q = q;
    }

    @Override
    public void calledOperationFailed(Operation<List<QueryHit>> op) {
      owner
          .getLocalEventDispatcher()
          .queryFailed(owner.getOwnContact(), q, QueryHit.getTotalHits(op.getResult()));
    }

    @Override
    public void calledOperationSucceeded(Operation<List<QueryHit>> op) {
      owner
          .getLocalEventDispatcher()
          .querySucceeded(owner.getOwnContact(), q, QueryHit.getTotalHits(op.getResult()));
    }
  }
}
/**
 * @author <*****@*****.**>
 * @version 05/06/2011
 */
public class KademliaAddressResolutionImpl
    extends AbstractAddressResolution<KademliaOverlayID, SkyNetID, KademliaOverlayKey> {

  private static Logger log = SimLogger.getLogger(KademliaAddressResolutionImpl.class);

  private static KademliaAddressResolutionImpl ari;

  private final Config config;

  private KademliaAddressResolutionImpl(int size) {
    config = KademliaSetup.getConfig();
    byte[] bound = new byte[size + 1];
    bound[0] = 1;
    upperBound = new BigDecimal(new BigInteger(bound));
    log.debug("the upper bound of the addressSpace is " + upperBound.toPlainString());
  }

  public static KademliaAddressResolutionImpl getInstance(int size) {
    if (ari == null) {
      ari = new KademliaAddressResolutionImpl(size);
    }
    return ari;
  }

  @Override
  public KademliaOverlayID getOverlayID(SkyNetID skyNetID) {
    BigDecimal dec = skyNetID.getID().multiply(upperBound);
    BigInteger kademliaOverlayID = null;
    try {
      kademliaOverlayID = dec.toBigIntegerExact();
    } catch (Exception e) {
      log.fatal("Unable to create exact integer out of " + dec.toPlainString());
    }
    return new KademliaOverlayID(kademliaOverlayID.subtract(BigInteger.ONE), config);
  }

  @Override
  public KademliaOverlayKey getOverlayKey(SkyNetID skyNetKey) {
    BigInteger kademliaOverlayKey = skyNetKey.getID().multiply(upperBound).toBigIntegerExact();
    return new KademliaOverlayKey(kademliaOverlayKey.subtract(BigInteger.ONE), config);
  }

  @Override
  public SkyNetID getSkyNetID(KademliaOverlayID overlayID) {
    BigDecimal kademliaOverlayID = new BigDecimal(overlayID.getBigInt()).add(BigDecimal.ONE);
    BigDecimal skyNetID = kademliaOverlayID.divide(upperBound);
    return new SkyNetID(skyNetID);
  }
}
/**
 * Operation listener for the CAN
 *
 * @author Bjoern Dollak <*****@*****.**>
 * @version February 2010
 */
public class OperationListener implements OperationCallback<Object> {
  private CanNode master;
  private static Logger log = SimLogger.getLogger(CanNode.class);

  public OperationListener(CanNode master) {
    this.master = master;
    log.debug(Simulator.getSimulatedRealtime() + " new OperationListener");
  }

  public void calledOperationFailed(Operation<Object> op) {}

  public void calledOperationSucceeded(Operation<Object> op) {
    // TODO Auto-generated method stub

  }
}
Exemplo n.º 4
0
/**
 * This class is responsible for the communication with other peers. It has methods to start a
 * download, upload and it receives most types of the messages from other peers.
 *
 * @author Jan Stolzenburg
 */
public class BTPeerDistributeNode extends AbstractOverlayNode
    implements TransMessageListener, DistributionStrategy {

  /** It stores all documents we know. */
  private ContentStorage
      itsContentStorage; // TODO: Das bereits im Konstruktor setzen? Bisher wird es von der
                         // Application-Klasse gesetzt.

  /** This class makes the handling of operations easier. */
  //	private BTOperationManager itsOperationManager;

  /** My P2P address. */
  private BTContact itsOwnContact;

  /** This class is our interface to the network. We use it for sending and receiving messages. */
  private Map<OverlayKey, BTConnectionManager> itsConnectionManagers;

  /**
   * Here we store the message handler for every torrent. The message handler handles most types of
   * messages that we receive from other peers.
   */
  private Map<OverlayKey, BTMessageHandler>
      itsCurrentlyUploadedDocuments; // TODO: Beim beenden wieder hieraus entfernen.

  /** The upload operation for every torrent. */
  private Collection<BTOperationUpload<BTPeerDistributeNode>> itsUploadOperations;

  /** The download operation for every torrent. */
  private Collection<BTOperationDownload<BTPeerDistributeNode>> itsDownloadOperations;

  /** The statistic class stores, at which time we received how much data from every peer. */
  private BTInternStatistic itsStatistic;

  /** A random source. */
  private RandomGenerator itsRandomGenerator;

  /** The universal data store. We store different kind of information in it. */
  private BTDataStore itsDataBus;

  static final Logger log = SimLogger.getLogger(BTPeerDistributeNode.class);
  private UC3MLogBT logger = GNP_BTSimulation_Periodical.logger;

  public BTPeerDistributeNode(
      BTDataStore theDataBus,
      OverlayID theOverlayID,
      short thePeerDistributionPort,
      BTInternStatistic theStatistic,
      RandomGenerator theRandomGenerator) {
    super(theOverlayID, thePeerDistributionPort);
    this.itsDataBus = theDataBus;
    this.itsStatistic = theStatistic;
    this.itsRandomGenerator = theRandomGenerator;
    this.itsConnectionManagers = new HashMap<OverlayKey, BTConnectionManager>();
    this.itsCurrentlyUploadedDocuments = new HashMap<OverlayKey, BTMessageHandler>();
    this.itsDownloadOperations = new LinkedList<BTOperationDownload<BTPeerDistributeNode>>();
    this.itsUploadOperations = new LinkedList<BTOperationUpload<BTPeerDistributeNode>>();
  }

  /**
   * This method starts a download by creating and activating a download operation.
   *
   * @param theOverlayKey the key/hash of the document that we want to download.
   * @param theOtherPeers a list of peers start also participate in this torrent. We normaly get
   *     this list from the tracker.
   * @return the operation id of the started download operation.
   */
  public int downloadDocument(
      OverlayKey theOverlayKey, List<TransInfo> theOtherPeers, OperationCallback theCallback) {
    log.debug(
        "Time: "
            + Simulator.getCurrentTime()
            + "; Starting download at '"
            + this.itsOwnContact
            + "'.");
    BTDocument document;
    BTConnectionManager connectionManager;
    if (!this.itsContentStorage.containsDocument(theOverlayKey)) {
      document =
          new BTDocument(
              theOverlayKey,
              ((BTTorrent) this.itsDataBus.getPerTorrentData(theOverlayKey, "Torrent")).getSize());
      this.itsContentStorage.storeDocument(document);
    } else {
      document = (BTDocument) this.itsContentStorage.loadDocument(theOverlayKey);
    }
    if (!this.itsConnectionManagers.containsKey(theOverlayKey)) {
      connectionManager = new BTConnectionManager(this.itsOwnContact);
      this.itsConnectionManagers.put(theOverlayKey, connectionManager);
    } else {
      connectionManager = this.itsConnectionManagers.get(theOverlayKey);
    }
    BTOperationDownload<BTPeerDistributeNode> downloadOperation =
        new BTOperationDownload<BTPeerDistributeNode>(
            this.itsDataBus,
            document,
            this.itsOwnContact,
            this,
            this,
            connectionManager,
            this.itsStatistic,
            this.itsRandomGenerator);
    this.itsDownloadOperations.add(downloadOperation);
    downloadOperation.scheduleImmediately();
    return downloadOperation.getOperationID();
  }

  /**
   * This method starts a upload by creating and activating a download operation.
   *
   * @param theOverlayKey the key/hash of the document that we want to upload.
   * @return the operation id of the started upload operation.
   */
  public int uploadDocument(OverlayKey theOverlayKey, OperationCallback theCallback) {
    log.debug("Starting upload at '" + this.itsOwnContact + "'.");
    BTDocument document;
    BTConnectionManager connectionManager;
    if (!this.itsContentStorage.containsDocument(theOverlayKey)) {
      throw new RuntimeException("You tried to upload an unknown document.");
    } else {
      document = (BTDocument) this.itsContentStorage.loadDocument(theOverlayKey);
    }
    if (!this.itsConnectionManagers.containsKey(theOverlayKey)) {
      connectionManager = new BTConnectionManager(this.itsOwnContact);
      this.itsConnectionManagers.put(theOverlayKey, connectionManager);
    } else {
      connectionManager = this.itsConnectionManagers.get(theOverlayKey);
    }
    BTOperationUpload<BTPeerDistributeNode> uploadOperation =
        new BTOperationUpload<BTPeerDistributeNode>(
            this.itsDataBus,
            document,
            this.itsOwnContact,
            this,
            this,
            connectionManager,
            this.itsStatistic,
            this.itsRandomGenerator);
    this.itsUploadOperations.add(uploadOperation);
    uploadOperation.scheduleImmediately();
    if (!this.itsCurrentlyUploadedDocuments.containsKey(theOverlayKey)) {
      this.itsCurrentlyUploadedDocuments.put(
          theOverlayKey,
          new BTMessageHandler(
              this.itsDataBus,
              document,
              this.itsOwnContact,
              uploadOperation,
              this.getTransLayer(),
              connectionManager));
    }
    return uploadOperation.getOperationID();
  }

  public void setContentStorage(ContentStorage theContentStorage) {
    this.itsContentStorage = theContentStorage;
  }

  /**
   * Most types of P2P messages in BitTorrent are no direct replies. They are all received by this
   * method. It casts them to a subtype and forwards them to the message handler.
   *
   * @param theMessageEvent the message event that is received.
   */
  public void messageArrived(TransMsgEvent theMessageEvent) {

    Message theMessage = theMessageEvent.getPayload();
    if (!(theMessage instanceof BTMessage)) {
      String errorMessage = "Received a non-BitTorrent message: '" + theMessage.toString() + "'";
      log.error(errorMessage);
      throw new RuntimeException(errorMessage);
    }

    BTMessage theBTMessage = (BTMessage) theMessage;
    BTContact theOtherPeer =
        new BTContact(theBTMessage.getSender(), theMessageEvent.getSenderTransInfo());

    // A table lookup for the message type would be better than this case statement. But this is
    // much to much overhead in Java, as we don't have first class functions.
    // TODO: Aufr�umen, so dass man nicht jedes mal das gleiche macht. Am besten nur nachschauen von
    // wem die Nachricht ist, nachschlagen in welchem Torrent dieser Peer ist und fertig.
    switch (theBTMessage.getType()) {
      case REQUEST:
        {
          BTPeerMessageRequest theRequest = (BTPeerMessageRequest) theBTMessage;
          OverlayKey theDocument = theRequest.getRequest().getOverlayKey();
          if (!this.itsCurrentlyUploadedDocuments.containsKey(theDocument)) {
            // We just ignore this message.
            log.warn(
                "Received an unexpected request from '"
                    + theOtherPeer
                    + "' for '"
                    + theDocument
                    + "'!");
            return;
          }
          if (this.itsConnectionManagers.get(theDocument).getConnection(theOtherPeer) != null)
            this.itsConnectionManagers
                .get(theDocument)
                .getConnection(theOtherPeer)
                .receivedMessage(theRequest);
          this.itsCurrentlyUploadedDocuments
              .get(theDocument)
              .handleRequestMessage(theRequest, theOtherPeer, theMessageEvent);
          return;
        }
      case CANCEL:
        {
          BTPeerMessageCancel theCancelMessage = (BTPeerMessageCancel) theBTMessage;
          OverlayKey theDocument = theCancelMessage.getOverlayKey();
          if (!this.itsCurrentlyUploadedDocuments.containsKey(theCancelMessage.getOverlayKey())) {
            // We just ignore this message.
            log.warn(
                "Received an unexpected cancel message from '"
                    + theOtherPeer
                    + "' for '"
                    + theCancelMessage.getOverlayKey()
                    + "'!");
            return;
          }
          if (this.itsConnectionManagers.get(theDocument).getConnection(theOtherPeer) != null)
            this.itsConnectionManagers
                .get(theDocument)
                .getConnection(theOtherPeer)
                .receivedMessage(theCancelMessage);
          this.itsCurrentlyUploadedDocuments
              .get(theCancelMessage.getOverlayKey())
              .handleCancelMessage(theCancelMessage, theOtherPeer);
          return;
        }
      case HAVE:
        {
          BTPeerMessageHave theHaveMessage = (BTPeerMessageHave) theBTMessage;
          OverlayKey theDocument = theHaveMessage.getOverlayKey();
          if (!this.itsCurrentlyUploadedDocuments.containsKey(theHaveMessage.getOverlayKey())) {
            // We just ignore this message.
            log.warn(
                "Received an unexpected have message from '"
                    + theOtherPeer
                    + "' for '"
                    + theHaveMessage.getOverlayKey()
                    + "'!");
            return;
          }
          if (this.itsConnectionManagers.get(theDocument).getConnection(theOtherPeer) != null)
            this.itsConnectionManagers
                .get(theDocument)
                .getConnection(theOtherPeer)
                .receivedMessage(theHaveMessage);
          this.itsCurrentlyUploadedDocuments
              .get(theHaveMessage.getOverlayKey())
              .handleHaveMessage(theHaveMessage, theOtherPeer);
          return;
        }
      case HANDSHAKE:
        {
          BTPeerMessageHandshake theHandshake = (BTPeerMessageHandshake) theBTMessage;
          OverlayKey theDocument = theHandshake.getOverlayKey();
          if (!this.itsCurrentlyUploadedDocuments.containsKey(theHandshake.getOverlayKey())) {
            // We just ignore this message.
            log.debug(
                "Received an unexpected handshake at '"
                    + this.itsOwnContact.getOverlayID()
                    + "' from '"
                    + theOtherPeer.getOverlayID()
                    + "' for '"
                    + theHandshake.getOverlayKey()
                    + "'!");
            return;
          }
          if (this.itsConnectionManagers.get(theDocument).getConnection(theOtherPeer) != null)
            this.itsConnectionManagers
                .get(theDocument)
                .getConnection(theOtherPeer)
                .receivedMessage(theHandshake);
          this.itsCurrentlyUploadedDocuments
              .get(theHandshake.getOverlayKey())
              .handleHandshakeMessage(theHandshake, theOtherPeer);
          return;
        }
      case BITFIELD:
        {
          BTPeerMessageBitField theBitfield = (BTPeerMessageBitField) theBTMessage;
          OverlayKey theDocument = theBitfield.getOverlayKey();
          if (!this.itsCurrentlyUploadedDocuments.containsKey(theBitfield.getOverlayKey())) {
            // We just ignore this message.
            log.warn(
                "Received an unexpected bitfield from '"
                    + theOtherPeer
                    + "' for '"
                    + theBitfield.getOverlayKey()
                    + "'!");
            return;
          }
          if (this.itsConnectionManagers.get(theDocument).getConnection(theOtherPeer) != null)
            this.itsConnectionManagers
                .get(theDocument)
                .getConnection(theOtherPeer)
                .receivedMessage(theBitfield);
          this.itsCurrentlyUploadedDocuments
              .get(theBitfield.getOverlayKey())
              .handleBitfieldMessage(theBitfield, theOtherPeer);
          return;
        }
      case INTERESTED:
        {
          BTPeerMessageInterested theInterestMessage = (BTPeerMessageInterested) theBTMessage;
          OverlayKey theDocument = theInterestMessage.getOverlayKey();
          if (!this.itsCurrentlyUploadedDocuments.containsKey(theInterestMessage.getOverlayKey())) {
            // We just ignore this message.
            log.warn(
                "Received an unexpected interest message from '"
                    + theOtherPeer
                    + "' for '"
                    + theInterestMessage.getOverlayKey()
                    + "'!");
            return;
          }
          if (this.itsConnectionManagers.get(theDocument).getConnection(theOtherPeer) != null)
            this.itsConnectionManagers
                .get(theDocument)
                .getConnection(theOtherPeer)
                .receivedMessage(theInterestMessage);
          this.itsCurrentlyUploadedDocuments
              .get(theInterestMessage.getOverlayKey())
              .handleInterestMessage(theInterestMessage, theOtherPeer);
          return;
        }
      case UNINTERESTED:
        {
          BTPeerMessageUninterested theUninterestMessage = (BTPeerMessageUninterested) theBTMessage;
          OverlayKey theDocument = theUninterestMessage.getOverlayKey();
          if (!this.itsCurrentlyUploadedDocuments.containsKey(
              theUninterestMessage.getOverlayKey())) {
            // We just ignore this message.
            log.warn(
                "Received an unexpected uninterest message from '"
                    + theOtherPeer
                    + "' for '"
                    + theUninterestMessage.getOverlayKey()
                    + "'!");
            return;
          }
          if (this.itsConnectionManagers.get(theDocument).getConnection(theOtherPeer) != null)
            this.itsConnectionManagers
                .get(theDocument)
                .getConnection(theOtherPeer)
                .receivedMessage(theUninterestMessage);
          this.itsCurrentlyUploadedDocuments
              .get(theUninterestMessage.getOverlayKey())
              .handleUninterestMessage(theUninterestMessage, theOtherPeer);
          return;
        }
      case CHOKE:
        {
          BTPeerMessageChoke theChokeMessage = (BTPeerMessageChoke) theBTMessage;
          OverlayKey theDocument = theChokeMessage.getOverlayKey();
          if (!this.itsCurrentlyUploadedDocuments.containsKey(theChokeMessage.getOverlayKey())) {
            // We just ignore this message.
            log.warn(
                "Received an unexpected choke message from '"
                    + theOtherPeer
                    + "' for '"
                    + theChokeMessage.getOverlayKey()
                    + "'!");
            return;
          }
          if (this.itsConnectionManagers.get(theDocument).getConnection(theOtherPeer) != null)
            this.itsConnectionManagers
                .get(theDocument)
                .getConnection(theOtherPeer)
                .receivedMessage(theChokeMessage);
          this.itsCurrentlyUploadedDocuments
              .get(theChokeMessage.getOverlayKey())
              .handleChokeMessage(theChokeMessage, theOtherPeer);
          return;
        }
      case UNCHOKE:
        {
          BTPeerMessageUnchoke theUnchokeMessage = (BTPeerMessageUnchoke) theBTMessage;
          OverlayKey theDocument = theUnchokeMessage.getOverlayKey();
          if (!this.itsCurrentlyUploadedDocuments.containsKey(theUnchokeMessage.getOverlayKey())) {
            // We just ignore this message.
            log.warn(
                "Received an unexpected unchoke message from '"
                    + theOtherPeer
                    + "' for '"
                    + theUnchokeMessage.getOverlayKey()
                    + "'!");
            return;
          }
          if (this.itsConnectionManagers.get(theDocument).getConnection(theOtherPeer) != null)
            this.itsConnectionManagers
                .get(theDocument)
                .getConnection(theOtherPeer)
                .receivedMessage(theUnchokeMessage);
          this.itsCurrentlyUploadedDocuments
              .get(theUnchokeMessage.getOverlayKey())
              .handleUnchokeMessage(theUnchokeMessage, theOtherPeer);
          return;
        }
      case KEEPALIVE:
        {
          BTPeerMessageKeepAlive theKeepAliveMessage = (BTPeerMessageKeepAlive) theBTMessage;
          OverlayKey theDocument = theKeepAliveMessage.getOverlayKey();
          if (!this.itsCurrentlyUploadedDocuments.containsKey(
              theKeepAliveMessage.getOverlayKey())) {
            // We just ignore this message.
            log.warn(
                "Received an unexpected keep alive message from '"
                    + theOtherPeer
                    + "' for '"
                    + theKeepAliveMessage.getOverlayKey()
                    + "'!");
            return;
          }
          if (this.itsConnectionManagers.get(theDocument).getConnection(theOtherPeer) != null)
            this.itsConnectionManagers
                .get(theDocument)
                .getConnection(theOtherPeer)
                .receivedMessage(theKeepAliveMessage);
          this.itsCurrentlyUploadedDocuments
              .get(theKeepAliveMessage.getOverlayKey())
              .handleKeepAliveMessage(theOtherPeer);
          return;
        }
      case PIECE:
        {
          //				BTPeerMessagePiece thePieceMessage = (BTPeerMessagePiece) theBTMessage;
          /*
           * I wasn't able to find the reason for these failures:
           * It seams that the requests themselves get sometimes delayed and arrive much to late.
           * But this failure accures also, if the requests are not delayed.
           * There are not nearly-late piece messages: Either they arrive within some seconds or they are too late. (> 180 sec.)
           * But nothing in between...
           */
          //				log.warn("Received an piece-message that has timed out! It needed '" +
          // ((BTUtil.getTime() - thePieceMessage.getTimestamp()) / BTUtil.getSecond()) + "' seconds
          // till the answer of the request arrived.");
          //				log.warn("Received an piece-message that has timed out! Size: '" +
          // thePieceMessage.getMessageSize() + "'; it needed '" + ((BTUtil.getTime() -
          // thePieceMessage.getTimestamp()) / BTUtil.getSecond()) + "' seconds; it needed '" +
          // ((BTUtil.getTime() - thePieceMessage.getRequestTime()) / BTUtil.getSecond()) + "'
          // seconds till the answer of the request arrived. Piece: '" +
          // thePieceMessage.getPieceNumber() + "'; Block: '" + thePieceMessage.getBlockNumber() +
          // "';");
          return;
        }
      default:
        {
          String errorMessage =
              "Received an unknown BitTorrent message: '" + theBTMessage.toString() + "'";
          log.error(errorMessage);
          throw new RuntimeException(errorMessage);
        }
    }
  }

  @Override
  public TransLayer getTransLayer() {
    return this.getHost().getTransLayer();
  }

  public TransInfo getTransInfo() {
    return this.itsOwnContact.getTransInfo();
  }

  public void connect() {
    this.getTransLayer().addTransMsgListener(this, this.getPort());
    this.itsContentStorage = this.getHost().getStorage();
    this.itsOwnContact =
        new BTContact(this.getOverlayID(), this.getTransLayer().getLocalTransInfo(this.getPort()));
  }

  public Operation createOperation(String opName, String[] params, OperationCallback caller) {
    // TODO Auto-generated method stub
    throw new RuntimeException(
        "Method 'createOperation' in class 'BTPeerDistributeNode' not yet implemented!");
    // return null;
  }

  public void connectivityChanged(ConnectivityEvent ce) {
    // TODO Auto-generated method stub
    throw new RuntimeException(
        "Method 'connectivityChanged' in class 'BTPeerDistributeNode' not yet implemented!");
    //
  }

  @Override
  public void calledOperationFailed(Operation op) {
    if (op instanceof BTOperationDownload) {
      logger.process(
          this.getClass().toString(),
          new Object[] {op, new Long(Simulator.getCurrentTime()), new Boolean(false)});
    }
  }

  @Override
  public void calledOperationSucceeded(Operation opd) {
    if (opd instanceof BTOperationDownload) {
      BTOperationDownload op = (BTOperationDownload) opd;
      logger.process(
          this.getClass().toString(),
          new Object[] {op, new Long(op.getFinishedTime()), new Boolean(true)});
    }
  }
}
Exemplo n.º 5
0
/**
 * Concrete implementation of a simulator which can be used to run a simulation by calling the main
 * method in the SimulatorRunner class.
 *
 * @author Sebastian Kaune
 * @author Konstantin Pussep
 * @version 3.0, 11/29/2007
 */
public class Simulator implements Configurable {

  private static final Logger log = SimLogger.getLogger(Simulator.class);

  /** These constant should be ALWAYS used for virtual time calculations. */
  //	public final static long MICROSECOND_UNIT = 1l;

  /** These constant should be ALWAYS used for virtual time calculations. */
  public static final long MILLISECOND_UNIT = 1000l; // * MICROSECOND_UNIT;

  /** These constant should be ALWAYS used for virtual time calculations. */
  public static final long SECOND_UNIT = 1000l * MILLISECOND_UNIT;

  /** These constant should be ALWAYS used for virtual time calculations. */
  public static final long MINUTE_UNIT = 60l * SECOND_UNIT;

  /** These constant should be ALWAYS used for virtual time calculations. */
  public static final long HOUR_UNIT = 60l * MINUTE_UNIT;

  /** Scenario holding all the information about the current simulation run. */
  private Scenario scenario;

  /** Singleton instance of default simulator. */
  private static Simulator singleton;

  /** Configurator instance is used to initialize the scenario. */
  private DefaultConfigurator defaultConfigurator;

  private boolean running;

  private static long seed;

  private static Scheduler scheduler;

  private static RandomGenerator randomGen = new JDKRandomGenerator();

  private static Monitor monitor;

  private static boolean finishedWithoutError = false;

  /** This class is singleton, so use getInstance() method to obtain a reference to it. */
  private Simulator() {
    singleton = this;
    scheduler = new Scheduler(true);
    monitor = new DefaultMonitor();
  }

  /**
   * Returns the single instance of the SimulationFramework
   *
   * @return the SimulationFramework
   */
  public static Simulator getInstance() {
    if (singleton == null) singleton = new Simulator();
    return singleton;
  }

  /**
   * Set the scenario (protocol stack, network topology etc.) which will be used to run the
   * simulation.
   *
   * @param scenario simulation scenario to be used
   */
  public void setScenario(Scenario scenario) {
    checkRunning();
    this.scenario = scenario;
  }

  /** This method will run the simulation using the previously set scenario data. */
  public void start() {
    checkRunning();
    log.info("Prepare Scenario ...");
    this.scenario.prepare();

    log.info("Running Scenario with seed=" + getSeed());
    long startTime = System.currentTimeMillis();

    log.info("Simulation started...");
    this.running = true;
    try {
      scheduler.start();
      finishedWithoutError = true;
    } catch (Exception e) {
      log.error("Simulator run stopped because of error", e);
      finishedWithoutError = false;
    } finally {
      this.running = false;
    }

    log.info("Simulation finished.");
    long runTime = System.currentTimeMillis() - startTime;
    long minutes = (long) Math.floor((runTime) / 60000);
    long secs = (runTime % 60000) / 1000;
    log.info("Realtime Duration of experiment (m:s) " + minutes + ":" + secs);
    log.info("Simulated time is " + getSimulatedRealtime());
  }

  /**
   * Configure simulation from an XML file.
   *
   * @param configFile XML file with the configuration data.
   * @param variables the variables which are specified in the XML file with the configuarion data.
   */
  public void configure(String configFile, Map<String, String> variables) {
    this.defaultConfigurator = new DefaultConfigurator(configFile);
    defaultConfigurator.setVariables(variables);
    this.defaultConfigurator.register(Configurator.CORE, this);
    Collection<Configurable> components = this.defaultConfigurator.configureAll();
    log.debug("components " + components);
    ScenarioFactory scenarioBuilder =
        (ScenarioFactory) this.defaultConfigurator.getConfigurable(Configurator.SCENARIO_TAG);
    if (scenarioBuilder == null)
      throw new ConfigurationException(
          "No scenario builder specified in the configuration file. Nothing to do.");
    Scenario scenario = scenarioBuilder.createScenario();
    setScenario(scenario);
  }

  /**
   * Returns the seed used within a simulation run.
   *
   * @return the predefined seed
   */
  public static long getSeed() {
    return seed;
  }

  /**
   * This method sets the seed of the global random generator which can be obtained using the static
   * getRandom()-method.
   *
   * @param seed the seed to configure the global random generator
   */
  public void setSeed(long seed) {
    checkRunning();
    this.seed = seed;
    randomGen.setSeed(seed);
  }

  /**
   * Returns the global monitor which can be used to delegate information/occured events to a
   * specific analyzer/s.
   *
   * @return the global monitor
   */
  public static Monitor getMonitor() {
    if (monitor == null) monitor = new DefaultMonitor();
    return monitor;
  }

  /** Assure that the set methods are not called after the simulation has started. */
  private void checkRunning() {
    if (this.running) throw new IllegalStateException("Simulator is already running.");
  }

  /**
   * This method provides the global random generator which has to be used within the simulation
   * framework to generate reproducable results depending on a predefined seed.
   *
   * @return the global random generator with a predefined seed.
   */
  public static RandomGenerator getRandom() {
    return randomGen;
  }

  /**
   * Sets the monitor which has to be configured by using the XML file with the configuration data.
   *
   * @param monitor monitor predefined in the XML file with the configuration data.
   */
  public void setMonitor(Monitor monitor) {
    checkRunning();
    this.monitor = monitor;
  }

  /**
   * Returns the current simulation unit value.
   *
   * @return the current simulation unit value
   */
  public static long getCurrentTime() {
    return scheduler.getCurrentTime();
  }

  /**
   * Returns the simulated realtime in the format (Hours:Minutes:Seconds).
   *
   * @return the simulated realtime
   */
  public static String getSimulatedRealtime() {
    return scheduler.getSimulatedRealtime();
  }

  /**
   * Inserts new event in queue
   *
   * @param content the content of the event
   * @param simulationTime time to schedule the event
   * @param handler handler which will receive this event
   * @param eventType specific simulation event
   */
  public static void scheduleEvent(
      Object content,
      long simulationTime,
      SimulationEventHandler handler,
      SimulationEvent.Type eventType) {
    scheduler.scheduleEvent(content, simulationTime, handler, eventType);
  }

  /**
   * Reset the simulator, so that it can be configured again for another simulation run without to
   * restart the Java Virtual Machine. This is especially usefull for JUnit tests.
   */
  void reset() {
    checkRunning();
    // TODO may be there should be default values for some of them
    monitor = new DefaultMonitor();
    scenario = null;
    scheduler = new Scheduler(true);
    seed = 0;
  }

  static Scheduler getScheduler() {
    return scheduler;
  }

  /**
   * Sets the end time at which the simulation framework will finish at the latest the simulation ,
   * irrespective if there are still unprocessed events in the event queue.
   *
   * @param endTime point in time at which the simular will finish at the latest
   */
  public void setFinishAt(long endTime) {
    checkRunning();
    this.scheduler.setFinishAt(endTime);
  }

  static boolean isFinishedWithoutError() {
    return finishedWithoutError;
  }

  /**
   * Can be used to format the absolute simulation time (current, past or future) into
   * human-readable format: (h:m:s:ms).
   *
   * @param time - absolute simulation time like the one obtained via getCurrentTime();
   * @return human-readable representation of the given simulation time
   */
  public static String getFormattedTime(long time) {
    return scheduler.getFormattedTime(time);
  }

  /**
   * Specifies how often the scheduler will printout the current simulation time.
   *
   * @param time
   */
  public void setStatusInterval(long time) {
    scheduler.setStatusInterval(time);
  }
}
Exemplo n.º 6
0
/**
 * This class is used to periodically start random lookup request
 *
 * @author Minh Hoang Nguyen <*****@*****.**>
 * @version 05/06/2011
 */
public class LookupGenerator implements Configurable {

  private static Logger log = SimLogger.getLogger(LookupGenerator.class);

  protected static ArrayList<Integer> lookupList = new ArrayList<Integer>();

  /**
   * [0] : sum of lookup [1] : failed/loss lookup [2] : receive reply lookup [3] : correct lookup
   */
  protected static int[] lookupCount = new int[4];

  public void startRandomLookup(ChordNode starter) {
    log.debug("lookup counter " + Arrays.toString(lookupCount));
    lookupCount[0]++;
    long randomLong = Simulator.getRandom().nextLong();
    String token = Long.toString(Math.abs(randomLong), 32);
    ChordID key = ChordIDFactory.getInstance().getChordID(token);
    int lookupID = starter.overlayNodeLookup(key, new MyCallback());

    log.debug("started lookup request key = " + key + " id = " + lookupID + " from = " + starter);
    lookupList.add(lookupID);
  }

  public void setStart(long start) {

    new LookupBegin().scheduleWithDelay(start);
  }

  class MyCallback implements OperationCallback<List<ChordContact>> {

    @Override
    public void calledOperationFailed(Operation op) {
      // do nothing
      lookupCount[1]++;
    }

    @Override
    public void calledOperationSucceeded(Operation op) {
      // do nothing
      lookupCount[2]++;
      LookupOperation lookupOp = (LookupOperation) op;
      Integer lookup = new Integer(lookupOp.getLookupId());
      lookupList.remove(lookup);
      log.trace("unfinished lookup " + Arrays.toString(lookupList.toArray()));
      ChordID target = lookupOp.getTarget();
      ChordID result = lookupOp.getResult().get(0).getOverlayID();
      if (ChordConfiguration.DO_CHORD_EVALUATION) analyzeLookupResult(target, result);
    }
  }

  protected void analyzeLookupResult(ChordID target, ChordID result) {
    ChordNode responder =
        ChordOverlayUtil.getResponsibleNode(
            ChordBootstrapManager.getInstance(target).getAvailableNodes(), target.getValue());
    boolean valid = responder.getOverlayID().equals(result);
    if (!valid) {
      log.error(
          "incorrect lookup result"
              + " key = "
              + target
              + " correct responder "
              + responder
              + " found = "
              + result);
    } else {
      // +1 correct lookup
      lookupCount[3]++;
    }
  }

  public static boolean containLookupId(int lookupId) {

    return lookupList.contains(lookupId);
  }
}
/**
 * This operation sends a PingMsg to a neighbour and awaits a response. If no response arrives it is
 * tried CanConfig.numberPings times. If their is still no response, it tries to finds which peer
 * has the most common parents with the missing node. This peer gets a takeover message which starts
 * the TakeoverRabuild Operation. If the actual node is the has the most common vid parents, it
 * starts the TakeoverRebuildOperation itself. So it send a takeoverReorganizeMsg to every node
 * which has the same number of vid parents. If missing node and actual node are leafs of the tree,
 * the actual node just takes the area and adds it to its own.
 *
 * @author Bjoern Dollak <*****@*****.**>
 * @version February 2010
 */
public class TakeoverReplyOperation extends AbstractOperation<CanNode, Object> {

  private static final Logger log = SimLogger.getLogger(CanNode.class);

  private CanNode node;

  private CanOverlayContact neighbour;

  private boolean succ, delete;

  private int allreadyTried;

  private TakeoverOperation takeoverOperation;

  private boolean otherAnswer;

  /**
   * starts a TakeoverReplyOperation in this node for one of its neighbours
   *
   * @param component CanNode which should check its neighbours
   * @param neighbour neighbour to check
   * @param takeoverOperation this operation called the TekeoverReply Operation
   */
  public TakeoverReplyOperation(
      CanNode component, CanOverlayContact neighbour, TakeoverOperation takeoverOperation) {
    super(component);

    this.node = getComponent();
    this.neighbour = neighbour;
    this.takeoverOperation = takeoverOperation;
    this.allreadyTried = 0;
    this.succ = false;
    this.delete = false;
    otherAnswer = false;
  }

  @Override
  public void execute() {
    if (node.getPeerStatus() == PeerStatus.PRESENT
        && delete == false
        && node.neighboursContain(neighbour)) {

      if (allreadyTried < CanConfig.numberPings && succ == false) {
        PingMsg ping =
            new PingMsg(
                node.getLocalContact().getOverlayID(),
                neighbour.getOverlayID(),
                node.getLocalContact().clone());
        ping.setOperationId(this.getOperationID());
        node.getTransLayer()
            .send(ping, neighbour.getTransInfo(), node.getPort(), TransProtocol.UDP);
        allreadyTried++;
        this.operationFinished(true);
        this.scheduleWithDelay(CanConfig.timeout);
      } else if (allreadyTried >= CanConfig.numberPings && succ == false && otherAnswer == false) {
        this.operationFinished(true);
        this.scheduleWithDelay(CanConfig.numberPings * CanConfig.timeout + 2 * CanConfig.timeout);
      } else if (allreadyTried >= CanConfig.numberPings && succ == false) {
        log.warn(
            Simulator.getSimulatedRealtime()
                + " node left, own id: "
                + node.getLocalContact().getOverlayID().toString()
                + " "
                + node.getLocalContact().getArea().toString()
                + " missing node: "
                + neighbour.getOverlayID().toString()
                + " "
                + neighbour.getArea().toString()
                + " "
                + this.getOperationID());
        for (CanOverlayContact neighbour : node.getNeighbours()) {
          log.warn("own neighbours: " + neighbour.getOverlayID().toString());
        }

        CanOverlayContact closestVidNeighbour = null;
        if (neighbour
            .getArea()
            .getVid()
            .getVIDList()
            .get(neighbour.getArea().getVid().getVIDList().size() - 1)
            .equals("0"))
          closestVidNeighbour = node.getVidNeighboursOfCertainNeighbour(neighbour)[1];
        else closestVidNeighbour = node.getVidNeighboursOfCertainNeighbour(neighbour)[0];

        if (closestVidNeighbour != null) { // ////////////normaly used
          // without the case, not
          // well tested
          log.debug(
              "closest vid neighbour: "
                  + closestVidNeighbour.getOverlayID().toString()
                  + " in node "
                  + node.getLocalContact().getOverlayID().toString()
                  + " "
                  + neighbour.getArea().getVid().toString()
                  + " "
                  + node.getLocalContact().getArea().getVid().toString()
                  + " "
                  + neighbour
                      .getArea()
                      .getVid()
                      .closestNeighbour(node.getLocalContact().getArea().getVid()));

          // controll if one of the neighbours is the direct neighbour
          if (neighbour
              .getArea()
              .getVid()
              .closestNeighbour(node.getLocalContact().getArea().getVid())) {
            log.debug(
                "direct Vid neighbour is missing "
                    + " neighbour "
                    + neighbour.getArea().getVid().toString()
                    + " own "
                    + node.getLocalContact().getArea().getVid().toString()
                    + " "
                    + neighbour
                        .getArea()
                        .getVid()
                        .closestNeighbour(node.getLocalContact().getArea().getVid()));
            if (node.getMissingNode() == null) {
              node.setMissingNode(neighbour);
              TakeoverRebuildOperation takeoverRebuildOperation =
                  new TakeoverRebuildOperation(node);
              takeoverRebuildOperation.scheduleWithDelay(CanConfig.waitForTakeover);
              this.operationFinished(true);
            }
          }
          // own node is closest, but not brother
          else if (closestVidNeighbour
                  .getOverlayID()
                  .toString()
                  .equals(node.getLocalContact().getOverlayID().toString())
              && (node.getVIDNeighbours()[0]
                      .getOverlayID()
                      .toString()
                      .equals(neighbour.getOverlayID().toString())
                  || node.getVIDNeighbours()[1]
                      .getOverlayID()
                      .toString()
                      .equals(neighbour.getOverlayID().toString()))) {

            if (node.getMissingNode() == null) {
              node.setMissingNode(neighbour);

              CanOverlayContact n = null;
              if (node.getVIDNeighbours()[0]
                  .getArea()
                  .getVid()
                  .equals(neighbour.getArea().getVid())) n = node.getVIDNeighbours()[1];
              else n = node.getVIDNeighbours()[0];
              TakeoverReorganizeMsg leave =
                  new TakeoverReorganizeMsg(
                      (CanOverlayID) node.getOverlayID(),
                      n.getOverlayID(),
                      node.getLocalContact().clone(),
                      neighbour);
              node.getTransLayer().send(leave, n.getTransInfo(), node.getPort(), TransProtocol.TCP);

              TakeoverRebuildOperation takeoverRebuildOperation =
                  new TakeoverRebuildOperation(node);
              takeoverRebuildOperation.scheduleWithDelay(CanConfig.waitForTakeover);
              this.operationFinished(true);

            } else log.debug("node is allready set");
          } else { // send takeoverMsg
            CanOverlayContact receiver = null;
            if (closestVidNeighbour.getArea().getVid().numberCommon(neighbour.getArea().getVid())
                >= node.getLocalContact()
                    .getArea()
                    .getVid()
                    .numberCommon(neighbour.getArea().getVid())) receiver = closestVidNeighbour;
            else if (neighbour
                .getArea()
                .getVid()
                .getVIDList()
                .get(neighbour.getArea().getVid().getVIDList().size() - 1)
                .toString()
                .equals("0")) receiver = node.getVIDNeighbours()[0];
            else receiver = node.getVIDNeighbours()[1];

            log.debug(
                "send takeovermsg from "
                    + node.getLocalContact().getOverlayID().toString()
                    + " to "
                    + receiver.getOverlayID().toString());
            List<CanOverlayContact> neighboursOfMissing =
                node.getNeighboursOfCertainNeighbour(neighbour);
            CanOverlayContact[] vidNeighboursOfMissing =
                node.getVidNeighboursOfCertainNeighbour(neighbour);
            TakeoverMsg takeoverMsg =
                new TakeoverMsg(
                    node.getLocalContact().getOverlayID(),
                    receiver.getOverlayID(),
                    neighbour,
                    neighboursOfMissing,
                    vidNeighboursOfMissing);
            node.getTransLayer()
                .send(takeoverMsg, receiver.getTransInfo(), node.getPort(), TransProtocol.TCP);

            log.debug(
                "missing node detected and just removed, in node: "
                    + node.getLocalContact().getOverlayID().toString()
                    + " missing node: "
                    + neighbour.getOverlayID().toString());
            node.removeNeighbour(neighbour);
            this.takeoverOperation.takeoverReplyFinished(this.getOperationID());
          }
        } else {
          log.debug(
              "missing node detected and just removed, in node: "
                  + node.getLocalContact().getOverlayID().toString()
                  + " missing node: "
                  + neighbour.getOverlayID().toString());
          node.removeNeighbour(neighbour);
          this.takeoverOperation.takeoverReplyFinished(this.getOperationID());
        }
      } else {
        this.takeoverOperation.takeoverReplyFinished(this.getOperationID());
        this.operationFinished(true);
      }
    }
  }

  public Object getResult() {
    return null;
  }

  public void found(PongMsg msg) {
    succ = true;
  }

  public void deleteOperation() {
    delete = true;
  }

  public boolean getSucc() {
    return succ;
  }

  public void anotherAnswer() {
    otherAnswer = true;
  }

  public void updateNode(CanNode node) {
    this.node = node;
  }
}
Exemplo n.º 8
0
/**
 * @author <*****@*****.**>
 * @version 05/06/2011
 */
public class Quadrant {
  private static final Logger log = SimLogger.getLogger(Quadrant.class);

  private HashMap<MobileNetID, MobileNetLayer> NodeList;

  /** Constructor for a new quadrant */
  public Quadrant() {
    this.NodeList = new HashMap<MobileNetID, MobileNetLayer>();
  }

  /**
   * Adds a node to this quadrant
   *
   * @param id : MobileNetId
   * @param p
   * @return True: if added successfully.
   */
  public void addNode(MobileNetID id, MobileNetLayer p) {
    NodeList.put(id, p);
  }

  /**
   * Delete this node from this quadrant
   *
   * @param id
   * @return Boolean
   */
  public boolean deleteNode(MobileNetID id) {
    MobileNetLayer temp = NodeList.remove(id);
    if (temp == null) return false;
    else return true;
  }

  public void deleteNode(LinkedList<MobileNetLayer> list) {
    for (Iterator<MobileNetLayer> iterator = list.iterator(); iterator.hasNext(); ) {
      MobileNetLayer t1 = iterator.next();
      NodeList.remove(t1.getNetID());
    }
  }

  /** @return How many nodes are present in this quadrant */
  public int getNodeCount() {
    return NodeList.size();
  }

  /**
   * Execute the method "moveNode" for all nodes which are present in this network. If the node has
   * moved out of this quadrant, add it to a list and return it to the manager.
   *
   * @param last_changed : Time to calculate the distance for. Current time minus the last update
   *     time
   * @return LinkedList: List with all the nodes, which have been moved out of the array and were
   *     already deleted.
   */
  public LinkedList<MobileNetLayer> updateMovement() {
    LinkedList<MobileNetLayer> l = new LinkedList<MobileNetLayer>();
    for (Map.Entry<MobileNetID, MobileNetLayer> e : NodeList.entrySet()) {
      MobileNetLayer elem = e.getValue();
      MobileNode n = (MobileNode) e.getValue().getNetPosition();
      double x = n.getXPos();
      double y = n.getYPos();
      n.updateMovement();
      // If the node moved out of my quadrant, add it to the returned List
      // and remove it from my list
      if ((x != n.getXPos()) || (y != n.getYPos())) {
        l.add(elem);
      }
    }
    this.deleteNode(l);
    return l;
  }

  public void outputNodes() {
    for (Map.Entry<MobileNetID, MobileNetLayer> e : NodeList.entrySet()) {
      MobileNode n = (MobileNode) e.getValue().getNetPosition();
      double x = n.getXPos();
      double y = n.getYPos();
      if (x == 0.8151173344625606D) log.debug(e.getKey() + ";" + x + ";" + y);
    }
  }

  public HashMap<MobileNetID, MobileNetLayer> getNodes() {
    return NodeList;
  }

  public boolean contains(NetID netID) {
    return NodeList.containsKey(netID);
  }
}
/**
 * Implementing a centralized DHT overlay, whose organization of the centralized index is similar to
 * the distributed index of Chord
 *
 * @author Dominik Stingl <*****@*****.**>
 * @version 1.0, 08.12.2008
 */
public class ClientLeaveOperation extends AbstractOperation<NapsterClientNode, Object>
    implements TransMessageCallback {

  private static Logger log = SimLogger.getLogger(ClientLeaveOperation.class);

  private ClientLeaveRequestMsg request = null;

  private ClientLeaveReplyMsg reply;

  private int msgID = -2;

  public ClientLeaveOperation(NapsterClientNode component, OperationCallback<Object> callback) {
    super(component, callback);
    NapsterOverlayID ownOID = getComponent().getOwnOverlayID();

    if (getComponent().getServerOverlayContact() != null
        && getComponent().getServerOverlayContact().getOverlayID() != null) {
      NapsterOverlayID serverOID = getComponent().getServerOverlayContact().getOverlayID();
      request = new ClientLeaveRequestMsg(ownOID, serverOID, null, getOperationID());
    }
  }

  @Override
  protected void execute() {
    if (request == null) return;

    TransInfo serverTransInfo = getComponent().getServerOverlayContact().getTransInfo();
    msgID =
        getComponent()
            .getTransLayer()
            .sendAndWait(
                request,
                serverTransInfo,
                getComponent().getPort(),
                TransProtocol.TCP,
                this,
                10 * Simulator.SECOND_UNIT);
    log.info("[Client] Initiating transMessage with id " + msgID + "-->opID " + getOperationID());
  }

  @Override
  public Object getResult() {
    // No result needed
    return null;
  }

  public void messageTimeoutOccured(int commId) {
    log.info("[Client] Message-timeout of transMessage with ID = " + commId + " occured");
    operationFinished(false);
  }

  public void receive(Message msg, TransInfo senderInfo, int commId) {
    reply = (ClientLeaveReplyMsg) msg;
    if (request.getOpID() == reply.getOpID()) {
      log.info("[Client] transMessage with ID = " + commId + " is received");
      operationFinished(true);
    } else {
      log.error("The opID send does not equal the opID received");
    }
  }
}
/**
 * This builder will parse an XML subtree and create hosts as specified there. It expects a tree
 * which looks as follows: <code>
 * &lt;HostBuilder&gt;
 * 	  &lt;Host groupID="..."&gt;...
 *   &lt;Group size="..." groupID="..."&gt;...
 * &lt;HostBuilder/&gt;
 * </code> The exact values for XML tags are specified as constants in this class (see below).
 *
 * @author Konstantin Pussep <*****@*****.**>
 * @author Sebastian Kaune
 * @version 3.0, 29.11.2007
 */
public class DefaultHostBuilder implements HostBuilder, Composable, Builder {
  /** XML attribute with this name specifies the size of the group. */
  public static final String GROUP_SIZE_TAG = "size";

  /** XML element with this name specifies a group of hosts. */
  public static final String GROUP_TAG = "Group";

  /**
   * XML element with this name specifies a single host and behaves equivalent to an element with
   * the name = GROUP_TAG value and group size of 1.
   */
  public static final String HOST_TAG = "Host";

  /**
   * XML attribute with this name specifies the id of the group, which is used to refer to this
   * group lateron, e.g. when you specify scenario actions.
   */
  public static final String GROUP_ID_TAG = "groupID";

  private static final Logger log = SimLogger.getLogger(DefaultHostBuilder.class);

  /** Groups of hosts indexed by group ids. */
  protected Map<String, List<Host>> groups;

  protected int experimentSize;

  /** Flat list of all hosts. */
  protected final List<Host> hosts = new LinkedList<Host>();

  /**
   * Will be called by the configurator.
   *
   * @param size total number of hosts in the simulator TODO we could remove this or force its
   *     correctness...
   */
  public void setExperimentSize(int size) {
    groups = new HashMap<String, List<Host>>(size);
    this.experimentSize = size;
  }

  public void compose(Configurator config) {
    // unused
  }

  /*
   * (non-Javadoc)
   *
   * @see de.tud.kom.p2psim.api.scenario.HostBuilder#getAllHostsWithGroupIDs()
   */
  public Map<String, List<Host>> getAllHostsWithGroupIDs() {
    Map<String, List<Host>> hosts = new HashMap<String, List<Host>>(groups);
    return hosts;
  }

  /*
   * (non-Javadoc)
   *
   * @see de.tud.kom.p2psim.api.scenario.HostBuilder#getAllHosts()
   */
  public List<Host> getAllHosts() {
    return hosts;
  }

  /*
   * (non-Javadoc)
   *
   * @see
   * de.tud.kom.p2psim.api.scenario.HostBuilder#getHosts(java.lang.String)
   */
  public List<Host> getHosts(String groupId) {
    return groups.get(groupId);
  }

  /*
   * (non-Javadoc)
   *
   * @see de.tud.kom.p2psim.api.scenario.HostBuilder#parse(org.dom4j.Element,
   * de.tud.kom.p2psim.api.scenario.Configurator)
   */
  public void parse(Element elem, Configurator config) {
    DefaultConfigurator defaultConfigurator = (DefaultConfigurator) config;

    // create groups
    for (Iterator iter = elem.elementIterator(); iter.hasNext(); ) {
      Element groupElem = (Element) iter.next();
      String groupID = groupElem.attributeValue(GROUP_ID_TAG);
      if (groupID == null) {
        throw new IllegalArgumentException(
            "Id of host/group " + groupElem.asXML() + " must not be null");
      }

      // either a group of hosts or a single host (=group with size 1)
      int groupSize;
      if (groupElem.getName().equals(HOST_TAG)) {
        groupSize = 1;
      } else if (groupElem.getName().equals(GROUP_TAG)) {
        String attributeValue = config.parseValue(groupElem.attributeValue(GROUP_SIZE_TAG));
        groupSize = Integer.parseInt(attributeValue);
      } else {
        throw new IllegalArgumentException("Unexpected tag " + groupElem.getName());
      }
      List<Host> group = new ArrayList<Host>(groupSize);

      // create hosts and instances of specified components for each host
      for (int i = 0; i < groupSize; i++) {

        DefaultHost host = new DefaultHost();

        // initialize properties
        DefaultHostProperties hostProperties = new DefaultHostProperties();
        host.setProperties(hostProperties);
        // minimal information for host properties is the group id
        hostProperties.setGroupID(groupID);

        // initialize layers and properties
        for (Iterator layers = groupElem.elementIterator(); layers.hasNext(); ) {
          Element layerElem = (Element) layers.next();
          if (layerElem.getName().equals(Configurator.HOST_PROPERTIES_TAG)) {
            defaultConfigurator.configureAttributes(hostProperties, layerElem);
          } else {
            // layer component factory
            ComponentFactory layer =
                (ComponentFactory) defaultConfigurator.configureComponent(layerElem);
            Component comp = layer.createComponent(host);

            setComponent(host, comp);
          }
        }
        group.add(host);
      }
      log.debug("Created a group with " + group.size() + " hosts");
      hosts.addAll(group);
      groups.put(groupID, group);
    }
    log.info("CREATED " + hosts.size() + " hosts");
    if (hosts.size() != experimentSize) {
      log.warn(
          "Only "
              + hosts.size()
              + " hosts were specified, though the experiment size was set to "
              + experimentSize);
    }
  }

  protected void setComponent(DefaultHost host, Component comp) {
    // TODO setComponent method in the host?
    if (comp instanceof NetLayer) {
      host.setNetwork((NetLayer) comp);
    } else if (comp instanceof TransLayer) {
      host.setTransport((TransLayer) comp);
    } else if (comp instanceof OverlayNode) {
      host.setOverlayNode((OverlayNode) comp);
    } else if (comp instanceof Application) {
      host.setApplication((Application) comp);
    } else if (comp instanceof ContentStorage) {
      host.setContentStorage((DefaultContentStorage) comp);
    }
  }
}