/**
 * {@link org.unitarou.yukinoshita.view.jface.board.IgoBoardPanel}と それに対するアノテーションからなるアウトライン用のパネルです。
 * <br>
 *
 * @author unitarou &lt;[email protected]&gt;
 */
public class BasicOutlinePanel implements IgoOutlinePanel {

  /** {@link BasicOutlinePanel}を返すファクトリーです。 */
  private static class FactoryImpl implements IgoOutlinePanelFactory<BasicOutlinePanel> {

    /* (non-Javadoc)
     * @see org.unitarou.yukinoshita.view.jface.board.IgoOutlinePanelFactory#create()
     */
    public BasicOutlinePanel create() {
      return new BasicOutlinePanel();
    }
  }

  /**
   * {@link BasicOutlinePanel}を作成するファクトリーを返します。
   *
   * @return {@link BasicOutlinePanel}を作成するファクトリー
   */
  public static IgoOutlinePanelFactory<BasicOutlinePanel> factory() {
    return new FactoryImpl();
  }

  /** このクラスのロガー */
  private static final Log log_s_ = LogFactory.getLog(BasicOutlinePanel.class);

  /** 「【{0}】{1}\n{2}」 */
  private static final Message FMT_ANNOTATION =
      new Message(BasicOutlinePanel.class, "fmtAnnotation"); // $NON-NLS-1$

  /** このパネルの最上位のフレームです。 */
  private Composite frame_;

  private Alignment alignment_;
  private Label annotationLabel_;
  private final DigestIgoBoardPanel digestIgoBoardPanel_;

  /** 盤面を表示するウィジットです。 */
  private Control boardControl_;

  /** 実際に描画対象となる{@link RootGameTree}です。 */
  private RootGameTree rootGameTree_;

  /** {@link #setFocus(boolean)}の値を保持し、 {@link #isFocused()}で返される値です。 */
  private boolean isFocused_;

  /** アノテーションを下部に配置し、背景色を未設定で初期化するコンストラクタです。 */
  public BasicOutlinePanel() {
    super();
    alignment_ = Alignment.BOTTOM;
    digestIgoBoardPanel_ = new DigestIgoBoardPanel(false, false);
    isFocused_ = false;
  }

  /**
   * アノテーションの場所を指定します。<br>
   * {@link #createContents(Composite)}より前に呼び出さないと効果がありません。
   *
   * @param alignment アノテーションの場所。NOT NULL
   * @throws org.unitarou.lang.NullArgumentException 引数(alignment)に<code>null</code>がある場合。
   */
  @Logging(level = LogLevel.WARN, contents = "createContents()後に呼ばれた場合")
  public final void setAlignment(Alignment alignment) {
    ArgumentChecker.throwIfNull(alignment);
    if (frame_ != null) {
      log_s_.log(WARN, "Widgit has already created(BUG)."); // $NON-NLS-1$
      return;
    }
    alignment_ = alignment;
  }

  /**
   * アノテーションの場所を返します。<br>
   *
   * @return アノテーションの場所 NOT NULL
   */
  public final Alignment getAlignment() {
    return alignment_;
  }

  /* (non-Javadoc)
   * @see org.unitarou.swt.WidgetContainer#createContents(org.eclipse.swt.widgets.Composite)
   */
  public Control createContents(Composite parent) {
    ArgumentChecker.throwIfNull(parent);

    GridLayout layout;
    frame_ = new Composite(parent, SWT.NONE);
    switch (alignment_) {
      case TOP:
        layout = new GridLayout(1, true);
        layout.marginWidth = 2;
        layout.marginHeight = 2;
        frame_.setLayout(layout);
        annotationLabel_ = new Label(frame_, SWT.WRAP);
        annotationLabel_.setLayoutData(new GridData(SWT.CENTER, SWT.BOTTOM, true, true));
        boardControl_ = digestIgoBoardPanel_.createContents(frame_);
        boardControl_.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, true));
        break;

      case BOTTOM:
        layout = new GridLayout(1, true);
        layout.marginWidth = 2;
        layout.marginHeight = 2;
        frame_.setLayout(layout);
        boardControl_ = digestIgoBoardPanel_.createContents(frame_);
        boardControl_.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true, false));
        annotationLabel_ = new Label(frame_, SWT.WRAP);
        annotationLabel_.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, true));
        break;

      case LEFT:
        layout = new GridLayout(2, false);
        layout.marginWidth = 2;
        layout.marginHeight = 2;
        frame_.setLayout(layout);
        boardControl_ = digestIgoBoardPanel_.createContents(frame_);
        boardControl_.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, true));
        annotationLabel_ = new Label(frame_, SWT.WRAP);
        annotationLabel_.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, true, true));
        break;

      case RIGHT:
        layout = new GridLayout(2, false);
        layout.marginWidth = 2;
        layout.marginHeight = 2;
        frame_.setLayout(layout);
        annotationLabel_ = new Label(frame_, SWT.WRAP);
        annotationLabel_.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, true));
        boardControl_ = digestIgoBoardPanel_.createContents(frame_);
        boardControl_.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, true));
        break;

      default:
        assert false : "Unknown alignment type :" + alignment_; // $NON-NLS-1$
    }
    return frame_;
  }

  /* (non-Javadoc)
   * @see IgoOutlinePanel#getControl()
   */
  public Control getControl() {
    return frame_;
  }

  /* (non-Javadoc)
   * @see IgoOutlinePanel#getBoardControl()
   */
  public Control getBoardControl() {
    return boardControl_;
  }

  /* (non-Javadoc)
   * @see org.unitarou.swt.WidgetContainer#dispose()
   */
  public void close() {
    // do nothing
  }

  /* (non-Javadoc)
   * @see IgoOutlinePanel#setRootGameTree(RootGameTree)
   */
  public void setRootGameTree(RootGameTree rootGameTree) {
    ArgumentChecker.throwIfNull(rootGameTree);
    if (rootGameTree_ == rootGameTree) {
      return;
    }

    rootGameTree_ = rootGameTree;
    Result result = Result.parse(BasicFinder.findDatum(rootGameTree_.getSequence(), SgfId.RESULT));
    // 描画
    annotationLabel_.setText(
        FMT_ANNOTATION.get(
            rootGameTree.getGameType().displayName(),
            result.displayName(),
            new OutlineRgtLabelProvider().getLabel(rootGameTree_)));
    digestIgoBoardPanel_.updateImage(rootGameTree_);
  }

  /**
   * @inheritDoc このクラスではパネルを正方形に使います。
   * @see IgoOutlinePanel#setSizeHint(Point)
   */
  public void setSizeHint(Point boardSize) {
    ArgumentChecker.throwIfNull(boardSize);

    GridData gridData;
    gridData = (GridData) frame_.getLayoutData();
    gridData.widthHint = boardSize.x;

    int margin = ((GridLayout) frame_.getLayout()).marginWidth;
    gridData = (GridData) boardControl_.getLayoutData();
    gridData.widthHint = boardSize.x - frame_.getBorderWidth() * 2 - margin * 2;
    gridData.heightHint = boardSize.y - frame_.getBorderWidth() * 2 - margin * 2;

    gridData = (GridData) annotationLabel_.getLayoutData();
    gridData.widthHint = boardSize.x - frame_.getBorderWidth() * 2 - margin * 2;
  }

  /* (non-Javadoc)
   * @see IgoOutlinePanel#getRootGameTree()
   */
  public RootGameTree getRootGameTree() {
    return rootGameTree_;
  }

  /* (non-Javadoc)
   * @see IgoOutlinePanel#isFocused()
   */
  public boolean isFocused() {
    return isFocused_;
  }

  /* (non-Javadoc)
   * @see IgoOutlinePanel#setFocus(boolean)
   */
  public void setFocus(boolean focused) {
    isFocused_ = focused;
    Display display = frame_.getDisplay();
    frame_.setBackground(
        display.getSystemColor(focused ? SWT.COLOR_LIST_SELECTION : SWT.COLOR_WIDGET_BACKGROUND));
    annotationLabel_.setBackground(
        display.getSystemColor(focused ? SWT.COLOR_LIST_SELECTION : SWT.COLOR_WIDGET_BACKGROUND));
    annotationLabel_.setForeground(
        display.getSystemColor(
            focused ? SWT.COLOR_LIST_SELECTION_TEXT : SWT.COLOR_WIDGET_FOREGROUND));
  }
}
Beispiel #2
0
/**
 * 表示する際に意味のあるプロパティをまとめるクラスです。
 *
 * @author unitarou &lt;[email protected]&gt;
 */
public final class GiItem {

  /** このクラスのロガーです。 */
  private static final Log log_s_ = LogFactory.getLog(GiItem.class);

  /**
   * textが%から始まっているときはpropertiesのKeyとみなしてテキストを返します。 それ以外のときはそのまま返します。 Keyが見つからないときは警告を出して{@link
   * Strings#EMPTY}を返します。
   *
   * @param text
   * @param properties
   * @return 置き換えたあとの文字列
   */
  public static String convert(String text, Properties properties) {
    if (text.startsWith("%")) { // $NON-NLS-1$
      String key = text.substring(1);
      String ret = properties.getProperty(key);
      if (ret == null) {
        log_s_.log(WARN, "Can''t find the key={0}", key); // $NON-NLS-1$
        return Strings.EMPTY;
      }
      return ret;
    }
    return text;
  }

  private static final Messages messages_s_ = Messages.createByPackage(GiItem.class);

  private String labelText_;
  private String expression_;

  /** 型は[{@link SgfId}, String] Propertyが存在しないときの表示項目を設定します。 */
  private final Map<SgfId, String> emptyMap_;

  /** 型は[{@link SgfId}, List[{@link GiEnum}]]です。 */
  private final Map<SgfId, List<GiEnum>> enumMap_;

  /** 実際の値を格納するLabelやTextが入ります。 */
  private GiFormula[] giFormulae_;

  /** */
  public GiItem() {
    super();
    labelText_ = Strings.EMPTY;
    expression_ = Strings.EMPTY;
    emptyMap_ = new TreeMap<SgfId, String>();
    enumMap_ = new TreeMap<SgfId, List<GiEnum>>();
    giFormulae_ = new GiFormula[0];
  }

  /**
   * {@link #setLabel(String)}と{@link #setExpression(String)}を 同時に設定するコンストラクタです。
   *
   * @param label NOT NULL
   * @param expression NOT NULL
   */
  public GiItem(String label, String expression) {
    super();
    ArgumentChecker.throwIfNull(label, expression);
    emptyMap_ = new TreeMap<SgfId, String>();
    enumMap_ = new TreeMap<SgfId, List<GiEnum>>();
    giFormulae_ = new GiFormula[0];
    labelText_ = label;
    expression_ = expression;
  }

  /**
   * labelに合わせて表示する項目を決定します。
   *
   * @param label NOT NULL
   * @throws org.unitarou.lang.NullArgumentException 引数の何れか一つでもnullの場合。
   */
  public void setLabel(String label) {
    ArgumentChecker.throwIfNull(label);
    labelText_ = label;
  }

  /** @return 一行のテキスト。NOT NULL */
  public CharSequence getText() {
    StringBuilder sb = new StringBuilder();
    sb.append(labelText_);
    for (GiFormula formula : giFormulae_) {
      sb.append(formula.getText());
    }
    return sb;
  }

  /**
   * expressionに合わせて表示する項目を決定します。<br>
   * expression はプロパティのIDとフリーテキストの混成表現で、 例えば次のような形式になります:<code>WN [WR]</code>。
   * この場合、最初に白の名前が表示され、ついでスペースとカギ括弧'['が 表示され、その後ろに白の段級位が表示され、最後にカギ括弧']'が表示されます。
   *
   * @param expression NOT NULL
   * @throws org.unitarou.lang.NullArgumentException 引数の何れか一つでもnullの場合。
   */
  public void setExpression(String expression) {
    ArgumentChecker.throwIfNull(expression);
    expression_ = expression;
  }

  /** @param empty */
  public void addEmpty(GiEmpty empty) {
    ArgumentChecker.throwIfNull(empty);
    emptyMap_.put(empty.getSgfId(), empty.getValue());
  }

  /** @param giEnum */
  public void addEnum(GiEnum giEnum) {
    ArgumentChecker.throwIfNull(giEnum);
    List<GiEnum> list = enumMap_.get(giEnum.getSgfId());
    if (list == null) {
      list = new ArrayList<GiEnum>();
      enumMap_.put(giEnum.getSgfId(), list);
    }
    list.add(giEnum);
  }

  /**
   * {@link #expression_}から{@link #giFormulae_}を構成します。 戦略<br>
   * ・一文字ずつ読み込んでアルファベットの大文字がきたら照合<br>
   *  ・ここでキューに文字がある場合はそれを組み合わせて照合する。 ・Propertyに存在しない場合はキューに入れる(キューの上限は1)
   * ・アルファベット大文字以外の文字がきたらキューをクリアーして表示文字に追加
   */
  public void setup() {
    List<GiFormula> giFormulaList = new ArrayList<GiFormula>();
    StringBuilder text = new StringBuilder(expression_.length());
    char queue = CharacterIterator.DONE;
    CharacterIterator ip = new StringCharacterIterator(expression_);
    for (char c = ip.first(); c != CharacterIterator.DONE; c = ip.next()) {
      if (!Strings.isUppercaseRomanAlphabet(c)) {
        if (queue != CharacterIterator.DONE) {
          text.append(queue);
          queue = CharacterIterator.DONE;
        }
        text.append(c);
        continue;
      }
      StringBuilder id = new StringBuilder(2); // IDのサイズは高々2
      if (queue != CharacterIterator.DONE) {
        id.append(queue);
      }
      id.append(c);
      SgfId sgfType = UEnum.find(SgfId.class, id.toString());
      if ((sgfType == null) || !PropertyType.GAME_INFO.equals(sgfType.propertyType())) {
        if (queue != CharacterIterator.DONE) {
          text.append(queue);
        }
        queue = c;
        continue;
      }
      if (text.length() != 0) {
        giFormulaList.add(new GiLabelFormula(text.toString()));
        text.delete(0, text.length());
      }
      GiTextFormula giTextFormula = new GiTextFormula(sgfType);
      String value = emptyMap_.get(sgfType);
      if (value != null) {
        giTextFormula.setEmpty(value);
      }
      List<GiEnum> giEnums = enumMap_.get(sgfType);
      if (giEnums != null) {
        for (GiEnum giEnum : giEnums) {
          String value2 = giEnum.getValue();
          if (value2.startsWith("%")) { // $NON-NLS-1$
            String key = value2.substring(1);
            value2 = messages_s_.get(key);
            giEnum.setValue(value2);
          }
          giTextFormula.addEnum(giEnum);
        }
      }
      giFormulaList.add(giTextFormula);
      queue = CharacterIterator.DONE;
    }
    if (text.length() != 0) {
      giFormulaList.add(new GiLabelFormula(text.toString()));
    }

    giFormulae_ = giFormulaList.toArray(new GiFormula[giFormulaList.size()]);
  }

  /**
   * 引数でインスタンスの表示を更新します。
   *
   * @param node
   */
  public void currentChanged(Node node) {
    ArgumentChecker.throwIfNull(node);
    for (int i = 0; i < giFormulae_.length; ++i) {
      giFormulae_[i].currentChanged(node);
    }
  }

  /** @param properties */
  public void setLocalizedLabel(Properties properties) {
    ArgumentChecker.throwIfNull(properties);

    labelText_ = convert(labelText_, properties);

    for (int i = 0; i < giFormulae_.length; ++i) {
      giFormulae_[i].setLabel(properties);
    }
    for (SgfId key : emptyMap_.keySet()) {
      String value = emptyMap_.get(key);
      emptyMap_.put(key, convert(value, properties));
    }
  }
}
/**
 * 用いた{@link org.unitarou.linecodec.LineCodec}と 変換した値セットでStringにコードし、デコードするCodecです。
 * コード化されたStringは以下の形式になります。<br>
 * (CODECのFQCN) + '@' + ({@link org.unitarou.linecodec.LineCodec#encode(Object)})
 *
 * @author UNITAROU &lt;[email protected]&gt;
 */
public final class LineCodecServer {
  /** このクラスのロガー。 */
  private static final Log log_s_ = LogFactory.getLog(LineCodecServer.class);

  /** 唯一のインスタンス。 */
  private static final LineCodecServer instance_s_ = new LineCodecServer();

  /**
   * 2.2.7までのCODECのデフォルトパッケージです。<br>
   * preferenceファイルにこのパッケージがあった場合は、{@link #NEW_CODEC_PACKAGES}に変換されます。 2.3.Xが定着した段階で削除します。
   */
  private static final String OLD_CODEC_PACKAGE = "org.unitarou.jface.pref"; // $NON-NLS-1$

  /** 2.2.7 -> 2.3.X用の変換テーブルです。 2.3.Xが定着した段階で削除します。 */
  private static final String[] NEW_CODEC_PACKAGES = {
    "org.unitarou.linecodec",
    "org.unitarou.jface.linecodec", //$NON-NLS-1$ //$NON-NLS-2$
    "org.unitarou.sgf.util",
    "org.unitarou.util"
  }; //$NON-NLS-1$ //$NON-NLS-2$

  /** @return 唯一のインスタンス。 */
  public static LineCodecServer instance() {
    return instance_s_;
  }

  /** {@link LineCodec}クラスと値を分けるセパレータ('@')です。 */
  public static final char SEPARATOR = '@';

  private final Map<Class<? extends LineCodec>, LineCodec> codecMap_;
  /** */
  private LineCodecServer() {
    super();
    codecMap_ = new LinkedHashMap<Class<? extends LineCodec>, LineCodec>();
    codecMap_.put(BooleanCodec.class, new BooleanCodec());
    codecMap_.put(IntegerCodec.class, new IntegerCodec());
    codecMap_.put(IntArrayCodec.class, new IntArrayCodec());
    codecMap_.put(EnumCodec.class, new EnumCodec());
    codecMap_.put(ProviderCodec.class, new ProviderCodec());
    codecMap_.put(FileCodec.class, new FileCodec());
    codecMap_.put(FileArrayCodec.class, new FileArrayCodec());
    codecMap_.put(CharsetArrayCodec.class, new CharsetArrayCodec());
    codecMap_.put(RgbCodec.class, new RgbCodec());
    codecMap_.put(EnumSetCodec.class, new EnumSetCodec());
    codecMap_.put(ProblemPropertyCodec.class, new ProblemPropertyCodec());
    codecMap_.put(FontDataCodec.class, new FontDataCodec());
  }

  /**
   * @param value
   * @return エンコード可能なオブジェクトの場合にtrueを返す。
   */
  public boolean isEncodable(Object value) {
    for (LineCodec codec : codecMap_.values()) {
      if (codec.isEncodable(value)) {
        return true;
      }
    }
    return false;
  }

  /**
   * stringにCODECクラスが記載されていない場合のデコーダーです。<br>
   * lastAttrが{@link LineCodec#isEncodable(Object)}で trueを返すCODESでデコードします。
   *
   * @param lastAttr
   * @param string
   * @return デコードされた結果。デコードに失敗した場合は<code>null</code>
   */
  public Object decode(Object lastAttr, String string) {
    ArgumentChecker.throwIfNull(lastAttr, string);
    for (LineCodec codec : codecMap_.values()) {
      if (codec.isEncodable(lastAttr)) {
        return codec.decode(string);
      }
    }
    return null;
  }

  /**
   * prefValueを (CODECのFQCN) + '@' + ({@link org.unitarou.linecodec.LineCodec#encode(Object)})
   * とみなして、CODECからObjectをデコードして返します。<br>
   * CODECのクラス名が取得できなかったり、 取得できてもそのインスタンスが取得できない場合は<code>null</code>を返します。
   *
   * @param prefValue
   * @return デコードされた結果。デコードに失敗した場合は<code>null</code>
   * @throws org.unitarou.lang.NullArgumentException
   */
  @Logging(level = WARN, stackTrace = false, contents = "CODECのロードに失敗した場合")
  public Object decode(String prefValue) {
    ArgumentChecker.throwIfNull(prefValue);

    int pos = prefValue.indexOf(SEPARATOR);
    if (pos == -1) {
      log_s_.log(DEBUG, "[IGNORE] Unknown type value: {0}", prefValue); // $NON-NLS-1$
      return null;
    }
    String className = prefValue.substring(0, pos);
    pos = pos + 1;
    String value = (pos < prefValue.length()) ? prefValue.substring(pos) : Strings.EMPTY;

    try {
      Class<?> clazz = getClass(className);
      LineCodec codec = codecMap_.get(clazz);
      if (codec == null) {
        codec = (LineCodec) clazz.newInstance();
      }
      return codec.decode(value);

    } catch (Exception e) {
      log_s_.log(WARN, "[IGNORE] {0}@{1}", prefValue, e.getLocalizedMessage()); // $NON-NLS-1$
    }
    return null;
  }

  /**
   * 2.2.7->2.3.X以降用にクラスがない場合にパッケージを変換して対応します。 2.3.Xが定着した段階で削除します。
   *
   * @param className
   * @return NOT NULL
   * @throws ClassNotFoundException
   */
  private Class<?> getClass(String className) throws ClassNotFoundException {
    try {
      return Class.forName(className);

    } catch (ClassNotFoundException e) {
      if (!className.startsWith(OLD_CODEC_PACKAGE)) {
        throw e;
      }
      for (String newPackage : NEW_CODEC_PACKAGES) {
        String replacedClassName = className.replace(OLD_CODEC_PACKAGE, newPackage);
        try {
          return Class.forName(replacedClassName);

        } catch (ClassNotFoundException ignore) {
          // DO NOTHING
        }
      }
      throw e;
    }
  }

  /**
   * 引数をコーデックのクラス名つきでエンコードします。
   *
   * @param value NOT NULL
   * @return エンコードに失敗した場合、NULL。
   */
  public String encode(Object value) {
    ArgumentChecker.throwIfNull(value);

    for (LineCodec codec : codecMap_.values()) {
      if (codec.isEncodable(value)) {
        StringBuilder sb = new StringBuilder();
        sb.append(codec.getClass().getName()).append(SEPARATOR).append(codec.encode(value));
        return sb.toString();
      }
    }
    return null;
  }

  /**
   * {@link #encode(Object)}と異なりcodecのクラス名をつけません。
   *
   * @param value NOT NULL
   * @return エンコードに失敗した場合、NULL。
   */
  public String encodeNoCodec(Object value) {
    ArgumentChecker.throwIfNull(value);
    for (LineCodec codec : codecMap_.values()) {
      if (codec.isEncodable(value)) {
        String ret = codec.encode(value);
        if (codec instanceof EnumCodec) {
          ret = ret.substring(ret.indexOf(SEPARATOR) + 1);
        }
        return ret;
      }
    }
    return null;
  }
}
Beispiel #4
0
/**
 * SGFのLBラベルの値をラップしたインスタンスです。
 *
 * @author UNITAROU &lt;[email protected]&gt;
 */
public class Label implements TypedString<Label> {
  private static final Log log_s_ = LogFactory.getLog(Label.class);
  private static final Pattern condition_s_ =
      Pattern.compile("([a-zA-Z]{2})\\s*:(.*)"); // $NON-NLS-1$
  private static final int POSITION_POS = 1; // 正規表現のグループID
  private static final int LABEL_POS = 2; // 正規表現のグループID

  /**
   * valueを単に前半の座標部分と後半のメッセージ部分に分けた配列を返します。 {@link SgfSize}が取得せずに生値を取得したい場合に利用します。
   *
   * @param value NOT NULL
   * @return 要素2のString配列、第一要素[0]は座標部分、第二要素[1]はラベル部分
   * @throws TypeParseException valueがパースできなかった場合
   */
  public static String[] parseString(String value) throws TypeParseException {
    ArgumentChecker.throwIfNull(value);
    Matcher matched = condition_s_.matcher(value);
    if (!matched.matches()) {
      throw new TypeParseException("Bad argument value=\'" + value + '\''); // $NON-NLS-1$
    }
    return new String[] {matched.group(POSITION_POS), matched.group(LABEL_POS)};
  }
  /**
   * @param size
   * @param value
   * @return NOT NULL
   * @throws TypeParseException valueがパースできなかった場合
   * @throws org.unitarou.lang.NullArgumentException 引数がnullの場合
   */
  public static Label parse(SgfSize size, String value) throws TypeParseException {
    ArgumentChecker.throwIfNull(size, value);

    String[] parsed = parseString(value);
    return new Label(SgfPoint.parseMove(size, parsed[0]), SimpleText.parse(parsed[1]));
  }

  /**
   * {@link TypeParseException}を送出しない代わりに、 パースに失敗した場合はnullを返します。
   *
   * @param size
   * @param value
   * @return NULLを返しうる。
   */
  public static Label parseQuietly(SgfSize size, String value) {
    try {
      return parse(size, value);
    } catch (TypeParseException e) {
      log_s_.log(DEBUG, e, "Bad value for parse: {0}", value); // $NON-NLS-1$
      return null;

    } catch (IllegalArgumentException e) {
      log_s_.log(DEBUG, e, "Bad value for parse: {0}", value); // $NON-NLS-1$
      return null;
    }
  }

  private final SgfPoint point_;
  private final SimpleText label_;

  /**
   * @param point NOT NULL
   * @param simpleText NOT NULL
   * @throws org.unitarou.lang.NullArgumentException 引数がnullの場合
   */
  public Label(SgfPoint point, SimpleText simpleText) {
    super();
    ArgumentChecker.throwIfNull(point, simpleText);
    point_ = point;
    label_ = simpleText;
  }

  /**
   * {@link SgfId#LABEL}のみ受け付けます。
   *
   * @see org.unitarou.sgf.type.TypedString#acceptable(org.unitarou.sgf.SgfId)
   */
  public boolean acceptable(SgfId sgfId) {
    ArgumentChecker.throwIfNull(sgfId);
    return SgfId.LABEL.equals(sgfId);
  }

  /* (non-Javadoc)
   * @see org.unitarou.sgf.type.TypedString#getString()
   */
  public String getString() {
    StringBuilder sb = new StringBuilder();
    sb.append(point_.getString());
    sb.append(Sgfs.COMPOSE_SEPARATOR);
    sb.append(label_.getString());
    return sb.toString();
  }

  /** @return NOT NULL */
  public SgfPoint getPoint() {
    return point_;
  }

  /** @return NOT NULL */
  public SimpleText getLabel() {
    return label_;
  }

  /**
   * @param value
   * @return 有効な値であればtrue
   */
  public boolean isValid(String value) {
    return (value != null) ? condition_s_.matcher(value).matches() : false;
  }

  /* (non-Javadoc)
   * @see java.lang.Comparable#compareTo(java.lang.Object)
   */
  public int compareTo(Label obj) {
    return point_.compareTo(obj.point_); // 同じ場所の場合は上書きを前提
  }

  /* (non-Javadoc)
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(Object obj) {
    if ((obj == null) || (!(obj instanceof Label))) {
      return false;

    } else if (this == obj) {
      return true;

    } else {
      Label o = (Label) obj;
      return point_.equals(o.point_);
    }
  }

  /* (non-Javadoc)
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode() {
    return point_.hashCode();
  }
}
/**
 * {@link org.unitarou.sgf.Collection}を編集するために ラップしたエディタークラスです。 TODO
 * GameFrameContollerからしか作成されていないので、 インターフェイスを作って問題集とそれ以外に分離する。 その際に、{@link
 * org.unitarou.yukinoshita.view.CollectionController#indexFilter_}を こちらに写す。
 *
 * @author unitarou &lt;[email protected]&gt;
 */
public class CollectionProxy {
  private static final Log log_s_ = LogFactory.getLog(CollectionProxy.class);

  /** ラップしているCollectionインスタンスです。 */
  private final Collection collection_;

  /** ラップしている{@link #collection_}へのリスナーです。 */
  private final CollectionListener listener_;

  /**
   * {@link RootGameTree}にそれぞれ対応するコントローラーを保持します。 コントローラーは{@link #initializeGame(int,
   * boolean)}で遅延初期化されます。
   */
  private final List<RgtProxy> games_;

  /** 現在アクティブになっている{@link RootGameTree}の {@link Collection}内でのIndexです。 */
  private int activeRgtIndex_;

  /**
   * {@link RootGameTree}の順番や一部の追加、削除を行った時に trueにセットされるフラグです。{@link #clearChangedFlags()}でfalseに
   * セットされます。
   */
  private boolean isRgtEdited_;

  /**
   * このクラスのプライムコンストラクタです。
   *
   * @param collection
   * @param setChangedFlag
   * @throws org.unitarou.lang.NullArgumentException collectionがnullの場合。
   */
  public CollectionProxy(Collection collection, boolean setChangedFlag) {
    super();
    ArgumentChecker.throwIfNull(collection);
    collection_ = collection;
    listener_ = new CollectionListenerImpl();
    collection_.addListener(listener_);
    games_ = new ArrayList<RgtProxy>(collection_.size());
    for (int i = 0; i < collection_.size(); ++i) {
      games_.add(null);
    }
    activeRgtIndex_ = 0;

    if (setChangedFlag) { // 最初から編集済み扱いにする時のみ、全てを読み出す。
      for (int i = 0; i < collection_.size(); ++i) {
        initializeGame(i, true);
      }
    }
    isRgtEdited_ = false;
  }

  /** */
  public void dispose() {
    games_.clear();
    collection_.removeListener(listener_);
    // コンテキストを全て破棄する。
    for (RootGameTree rgt : collection_) {
      Yukinoshita.context(rgt).clearCurrent();
    }
    Yukinoshita.context(collection_).clearCurrent();
  }

  /* (non-Javadoc)
   * @see java.lang.Object#finalize()
   */
  @Override
  protected void finalize() throws Throwable {
    try {
      if (!games_.isEmpty()) {
        log_s_.log(WARN, "{0} is not disposed.", this); // $NON-NLS-1$
        dispose();
      }
    } finally {
      super.finalize();
    }
  }

  /**
   * gameIndexで指定したゲームをアクティブにします。
   *
   * @param activeRgtIndex アクティブにする{@link RgtProxy}のインデックス(0オリジン)
   * @throws IllegalArgumentException gameIndexがコレクションの範囲を超えた場合
   */
  public void setActive(int activeRgtIndex) {
    if ((activeRgtIndex < 0) || (collection_.size() <= activeRgtIndex)) {
      throw new IllegalArgumentException(
          "Parameter gameIndex is out of bounds. Game size is "
              + collection_.size() // $NON-NLS-1$
              + ". Parameter gameIndex is "
              + activeRgtIndex); //$NON-NLS-1$
    }
    activeRgtIndex_ = activeRgtIndex;
  }

  /**
   * ラップしている{@link Collection}を返します。 問題集などは内部で展開されるため、コンストラクタで渡した インスタンスとは異なるインスタンスを返すことがあります。
   *
   * @return ラップしている{@link Collection}
   */
  public Collection getCollection() {
    return collection_;
  }

  /**
   * 現在アクティブ(選択)されている{@link RgtProxy}のインデックス(0オリジン)を返します。
   *
   * @return 0オリジンのインデックス
   */
  public int getActiveIndex() {
    return activeRgtIndex_;
  }

  /**
   * 現在アクティブ(選択されている){@link RgtProxy}を返します。 遅延初期化をおおなっており、このメソッドの呼び出し時にもしなければ {@link
   * RgtProxy}が生成されます。 決して<code>null</code>は返しません。
   *
   * @return 現在アクティブ(選択されている){@link RgtProxy}。NOT NULL
   */
  public RgtProxy getActive() {
    return get(activeRgtIndex_);
  }

  /**
   * indexに該当する{@link RgtProxy}を初期化します。
   *
   * @param index
   * @param setChangedFlag
   * @return indexに該当する{@link RgtProxy}
   */
  private RgtProxy initializeGame(int index, boolean setChangedFlag) {
    RootGameTree rootGameTree = collection_.get(index);
    RgtProxy ret = new RgtProxy(rootGameTree, setChangedFlag);
    games_.set(index, ret);
    return ret;
  }

  /**
   * {@link Collection}内の{@link RootGameTree}の順序の入れ替えや、 インスタンスの追加削除が合った場合にtrueを返します。<br>
   * <b>注意:</b>{@link #getChangedFlags()}は各{@link RootGameTree}内部が 編集されたかどうかを示すフラグで、このメソッドは{@link
   * Collection}内の {@link RootGameTree}の追加削除があったかを示すフラグです。<br>
   * {@link #getChangedFlags()}が全て<code>false</code>でも、 このメソッドがtrueを返すことがありますし、逆もまたありえます。
   *
   * @return コレクションの順序が入れ替えられた場合にtrue
   */
  public boolean isRgtEdited() {
    return isRgtEdited_;
  }

  /**
   * どのゲームが編集されているかを示す配列を返します。<br>
   * 戻り値は長さが{@link Collection#size()}で各要素がtrueの場合は そのRootGameTreeから始まるゲームが編集されていることを示します。
   *
   * @return {@link RootGameTree}が編集されている場合にtrueを返す配列。
   */
  public boolean[] getChangedFlags() {
    boolean[] ret = new boolean[collection_.size()];
    for (int i = 0; i < ret.length; ++i) {
      RgtProxy gm = games_.get(i);
      ret[i] = (gm != null) && gm.isChanged();
    }
    return ret;
  }

  /** 編集フラグをクリアーします。 ファイルとして保存された後に呼び出されます。 */
  public void clearChangedFlags() {
    for (RgtProxy mediator : games_) {
      if (mediator != null) {
        mediator.clearChanged();
      }
    }
    isRgtEdited_ = false;
  }

  /**
   * GameMediatorの総数を返します。 <code>getCollection().size()</code>と等価です。
   *
   * @return GameMediatorの総数
   */
  public int size() {
    return collection_.size();
  }

  /**
   * rgtをこのCollectionに組み込んでそのコントローラである {@link RgtProxy}を返します。
   *
   * @param rgt
   * @return 引数をラップした{@link RgtProxy}。NOT NULL
   */
  public RgtProxy createGame(RootGameTree rgt) {
    ArgumentChecker.throwIfNull(rgt);
    collection_.addLast(rgt);
    RgtProxy gm = initializeGame(collection_.size() - 1, true);
    return gm;
  }

  /**
   * このCollectionがDrill形式の場合にtrueを返します。
   *
   * @return Drill形式の場合にtrue
   */
  public boolean isDrill() {
    return get(0).getGameType().equals(GameType.DRILL);
  }

  /**
   * indexで指定されたGameMediatorを必要であれば作成して返します。
   *
   * @param index
   * @return indexで指定されたGameMediator
   * @throws IllegalArgumentException indexがコレクションの範囲外の場合
   */
  private RgtProxy get(int index) {
    if ((index < 0) || (collection_.size() <= index)) {
      throw new IllegalArgumentException("Bad index:" + index); // $NON-NLS-1$
    }
    RgtProxy ret = games_.get(index);
    if (ret == null) {
      ret = initializeGame(index, false);
    }
    return ret;
  }

  /**
   * indexで指定した{@link RgtProxy}とそれに対応する{@link Collection}を削除します。
   *
   * @param index
   * @throws IllegalArgumentException indexがコレクションの範囲外の場合
   */
  public void remove(int index) {
    if ((index < 0) || (collection_.size() <= index)) {
      throw new IllegalArgumentException("Bad index:" + index); // $NON-NLS-1$
    }
    collection_.remove(index);
    isRgtEdited_ = true;
  }

  /**
   * index1とindex2の{@link RgtProxy}を入れ替えます。<br>
   * なお、index1とindex2が等しい場合は何もしません。
   *
   * @param index1
   * @param index2
   * @throws IllegalArgumentException index1およびindex2の値がコレクションの範囲を超えた場合
   */
  public void swap(int index1, int index2) {
    if (index1 < 0 || collection_.size() <= index1 || index2 < 0 || collection_.size() <= index2) {
      throw new IllegalArgumentException(
          "Parameter is out of range. index1:"
              + index1 //$NON-NLS-1$
              + ", index2:"
              + index2); //$NON-NLS-1$
    }
    if (index1 == index2) {
      return;
    }
    collection_.swap(index1, index2);
    RgtProxy gm1 = games_.get(index1);
    games_.set(index1, games_.get(index2));
    games_.set(index2, gm1);

    if (activeRgtIndex_ == index1) {
      activeRgtIndex_ = index2;
    } else if (activeRgtIndex_ == index2) {
      activeRgtIndex_ = index1;
    }
    isRgtEdited_ = true;
  }

  /*
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    return ToStringBuilder.reflectionToString(this);
  }

  /** このクラスが保持する{@link Collection}用のリスナーです。 */
  private class CollectionListenerImpl implements CollectionListener {
    /*
     * @see org.unitarou.sgf.CollectionListener#rootGameTreeAppended(org.unitarou.sgf.CollectionEvent)
     */
    public void rootGameTreeAppended(CollectionEvent event) {
      games_.add(null);
    }

    /* (non-Javadoc)
     * @see org.unitarou.sgf.CollectionListener#rootGameTreeRemoved(org.unitarou.sgf.CollectionEvent)
     */
    public void rootGameTreeRemoved(CollectionEvent event) {
      RootGameTree target = event.getRootGameTree();
      for (int i = 0; i < games_.size(); ++i) {
        RgtProxy gm = games_.get(i);
        if (gm != null && gm.getRootGameTree() == target) {
          games_.remove(i);
          break;
        }
      }
      if (activeRgtIndex_ == collection_.size()) {
        activeRgtIndex_ = collection_.size() - 1;
      }
    }
  }
}