/**
   * データ量を取得
   *
   * @param topic
   * @param category
   * @return
   */
  public int calcMessageDataSize(String topic, String category) {
    // メモリ上に存在している場合は,コメントをメモリ上から取得
    SortedSet<CommentElement> comments = localTopic.get(topic, category);
    // StringBuffer commentBuf = new StringBuffer("<topic>\n");

    if (comments == null) {
      // コメントをファイルから持ってくる
      try {
        loadTopicFromFile(topic, category);
        comments = localTopic.get(topic, category);
      } catch (CommentNotFoundException e) {
        // コメントが見つからなかった,無視
      }
    }
    return localTopic.calcMessageDataSize(topic, category);
  }
  /**
   * 指定したトピックのコメントを取得
   *
   * @param topic
   * @param category
   * @param fromNo
   * @return
   * @throws CommentNotFoundException
   */
  public String getComment(String topic, String category, int fromNo)
      throws CommentNotFoundException {
    // メモリ上に存在している場合は,コメントをメモリ上から取得
    SortedSet<CommentElement> comments = localTopic.get(topic, category, fromNo);
    // StringBuffer commentBuf = new StringBuffer("<topic>\n");
    StringBuffer commentBuf = new StringBuffer("");

    if (comments == null) {
      // コメントをファイルから持ってくる
      loadTopicFromFile(topic, category);
      comments = localTopic.get(topic, category, fromNo);
    }
    // コメントを取得
    for (CommentElement buf : comments) {
      commentBuf.append(commentElementToXML(buf) + "\n");
    }
    // 現在表示中のトピックを更新
    currentTopic = new Pair<String, String>(topic, category);

    return commentBuf.toString();
  }
  /**
   * Reduce the load on the current hub so that it reaches the target load. We reduce load by
   * releasing topics using the {@link TopicManager} passed to the constructor. We use {@link
   * TopicManager#releaseTopics(int, org.apache.hedwig.util.Callback, Object)} to actually release
   * topics.
   *
   * @param targetLoad
   * @param callback a Callback<Long> that indicates how many topics we tried to release.
   * @param ctx
   */
  public void reduceLoadTo(HubLoad targetLoad, final Callback<Long> callback, final Object ctx) {
    int targetTopics = (int) targetLoad.toHubLoadData().getNumTopics();
    int numTopicsToRelease = (int) numTopics - targetTopics;

    // The number of topics we own is less than the target topic size. We don't release
    // any topics in this case.
    if (numTopicsToRelease <= 0) {
      callback.operationFinished(ctx, 0L);
      return;
    }
    // Call releaseTopics() on the topic manager to do this. We let the manager handle the release
    // policy.
    tm.releaseTopics(numTopicsToRelease, callback, ctx);
  }
 /** 現在表示中のトピックを除いてメモリ上から削除する また,削除したトピックはファイルへと保存する */
 public void garbageCollection() {
   // メモリ上に保持されているトピックをファイルへ移動する
   saveTopicToFile();
   // トピック一覧を取得
   Set<Pair<String, String>> topics = localTopic.getTopics();
   if (topics == null) {
     return;
   }
   // 現在表示しているトピック以外を削除
   for (Pair<String, String> topic : topics) {
     if (topic.equals(getCurrentTopic())) {
       continue;
     }
     remove(topic.getFirst(), topic.getSecond());
   }
 }
  /**
   * ファイルからトピックを読み込む
   *
   * @param topic
   * @param category
   * @throws CommentNotFoundException
   */
  public void loadTopicFromFile(String topic, String category) throws CommentNotFoundException {
    try {
      File f = new File("./" + logDirectory + "/" + category + "/" + topic + ".dat");
      byte[] b = new byte[(int) f.length()];
      FileInputStream fi = new FileInputStream(f);

      fi.read(b);
      // 読み取ったデータ
      String data = new String(b, this.charsetName);
      fi.close();

      List<CommentElement> topics = CommentElementXMLParser.XMLToCommentElement(data);
      if (topics != null) {
        for (CommentElement t : topics) {
          // データをメモリ上に読み込む
          localTopic.add(topic, category, t);
          // System.out.println("debug :" + t.getMessage() + " - " + t.getDate());
        }
      }
    } catch (Exception e) {
      // データを読み込めなかった場合
      throw new CommentNotFoundException("トピック:" + topic + "は見つかりませんでした.");
    }
  }
  /**
   * メモリ上に保持しているトピックをファイルへ保存する
   *
   * @param topic
   * @param category
   * @return
   */
  public boolean saveTopicToFile() {
    Set<Pair<String, String>> topics = localTopic.getTopics();

    // データが一つも存在しない
    if (topics == null) {
      return false;
    }
    // ログディレクトリを作成
    try {
      File logDir = new File("./" + logDirectory);
      if (!logDir.exists()) {
        // ディレクトリが存在しないので作成する
        if (logDir.mkdir() == false) {
          throw new IOException(logDirectory + "ディレクトリを作成できませんでした.");
        }
      }

      // メモリ上に存在するすべてのトピックをファイルへ保存する
      for (Pair<String, String> topic : topics) {
        // ファイル名 ./カテゴリ名/トピック名.dat
        String filename =
            "./" + logDirectory + "/" + topic.getSecond() + "/" + topic.getFirst() + ".dat";
        // トピック保存ディレクトリを作成
        // ディレクトリ名はトピックのカテゴリ名
        File dir = new File("./" + logDirectory + "/" + topic.getSecond());
        if (!dir.exists()) {
          // ディレクトリが存在しないので作成する
          if (dir.mkdir() == false) {
            throw new IOException("ディレクトリを作成できませんでした.");
          }
        }

        FileOutputStream fos = new FileOutputStream(filename);
        OutputStreamWriter osw = new OutputStreamWriter(fos, charsetName);
        BufferedWriter bw = new BufferedWriter(osw);

        // ファイル書き込みデータ
        StringBuffer writeData =
            new StringBuffer("<?xml version=\"1.0\" encoding=\"" + charsetName + "\"?>\n");
        writeData.append("<topic>\n");

        SortedSet<CommentElement> comments = localTopic.get(topic.getFirst(), topic.getSecond());
        // トピックの所属するすべてのコメントデータを書き込む
        for (CommentElement comment : comments) {
          writeData.append(commentElementToXML(comment) + "\n");
        }
        writeData.append("</topic>");
        // ファイル書き込み
        bw.write(writeData.toString());
        // ファイルを閉じる
        bw.close();
        osw.close();
        fos.close();
      }
    } catch (Exception e) {
      System.err.println(e);
      e.printStackTrace();
    }

    return false;
  }
 /**
  * 指定したトピックをメモリ上から削除する
  *
  * @param topic
  * @param category
  */
 public void remove(String topic, String category) {
   localTopic.remove(topic, category);
 }
 /**
  * @param tm The topic manager used to handle load shedding
  * @param tolerancePercentage The tolerance percentage for shedding load
  * @param maxLoadToShed The maximum amoung of load to shed in one call.
  */
 public TopicBasedLoadShedder(
     TopicManager tm, double tolerancePercentage, PubSubProtocol.HubLoadData maxLoadToShed) {
   // Make sure that all functions in this class have a consistent view
   // of the load. So, we use the same topic list throughout.
   this(tm, tm.getNumTopics(), tolerancePercentage, maxLoadToShed);
 }