/** * {@link org.unitarou.yukinoshita.view.jface.board.IgoBoardPanel}と それに対するアノテーションからなるアウトライン用のパネルです。 * <br> * * @author unitarou <[email protected]> */ 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)); } }
/** * 表示する際に意味のあるプロパティをまとめるクラスです。 * * @author unitarou <[email protected]> */ 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 <[email protected]> */ 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; } }
/** * SGFのLBラベルの値をラップしたインスタンスです。 * * @author UNITAROU <[email protected]> */ 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 <[email protected]> */ 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; } } } }