Skip to content

Instantly share code, notes, and snippets.

@seraphy
Last active March 31, 2020 07:06
Show Gist options
  • Save seraphy/bdd6f393095c3f23ab57d527332376cb to your computer and use it in GitHub Desktop.
Save seraphy/bdd6f393095c3f23ab57d527332376cb to your computer and use it in GitHub Desktop.
JavaFXのObservableListの要素をアンラップしたリストとしてアクセスするための読み込み専用の監視可能リスト
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.ObservableListBase;
import javafx.collections.WeakListChangeListener;
/**
* ObservableListの要素をアンラップしたObservableListに変換する読み込み専用のアダプタリスト。
* ソースのリストへの変更は、そのままアンラップされてリスナーに通知される。
* リストへのアクセスを行うと自動的にアンラップされた要素へのアクセスとなる。
* このリストへの読み書きは禁止される。
*
* @param <T> アンラップされた型
* @param <S> ソースリストの型
*/
public class UnwrappingObservableList<T, S> extends ObservableListBase<T> {
/**
* ソースリスト
*/
private ObservableList<S> backingList;
/**
* ソースリストの要素をアンラップする関数
*/
private Function<S, T> unwrapper;
/**
* ソースリストの変更を感知するリスナー
*/
private final ListChangeListener<S> listener;
private final WeakListChangeListener<S> weakListener;
/**
* アンラップするObservableListを構築します。
* @param unwrapper ソースリストの要素をアンラップする関数
*/
public UnwrappingObservableList(Function<S, T> unwrapper) {
this(null, unwrapper);
}
/**
* アンラップするObservableListを構築します
* @param backingList 元となるソースリスト
* @param unwrapper ソースリストの要素をアンラップする関数
*/
public UnwrappingObservableList(ObservableList<S> backingList, Function<S, T> unwrapper) {
this.backingList = backingList;
this.unwrapper = Objects.requireNonNull(unwrapper);
listener = c -> {
fireChange(new ListChangeListener.Change<T>(this) {
private int[] perm;
@Override
public boolean next() {
perm = null;
return c.next();
}
@Override
public void reset() {
c.reset();
}
@Override
public int getFrom() {
return c.getFrom();
}
@Override
public int getTo() {
return c.getTo();
}
@Override
public List<T> getRemoved() {
return c.getRemoved().stream().map(item -> unwrap(item)).collect(Collectors.toList());
}
@Override
public boolean wasUpdated() {
return c.wasUpdated(); // 既定実装はfalseを返すため、ソースを明示的に引き継ぐ必要がある
}
@Override
protected int[] getPermutation() {
if (perm == null) {
if (c.wasPermutated()) {
final int from = c.getFrom();
final int n = c.getTo() - from;
perm = new int[n];
for (int i = 0; i < n; i++) {
perm[i] = c.getPermutation(from + i);
}
} else {
perm = new int[0];
}
}
return perm;
}
@Override
public String toString() {
return c.toString();
}
});
};
this.weakListener = new WeakListChangeListener<>(listener);
if (this.backingList != null) {
this.backingList.addListener(weakListener);
}
}
/**
* バッキングリストを設定します。
* 現在設定されているリストは登録解除されます。
* @param backingList
*/
public void setBackingList(ObservableList<S> backingList) {
ObservableList<S> backingListOld = this.backingList;
this.backingList = Objects.requireNonNull(backingList);
this.backingList.addListener(weakListener);
if (backingListOld != null) {
backingListOld.removeListener(weakListener);
}
}
/**
* 現在設定されているバッキングリストを取得します。
* 設定されていない場合はnullを返します。
* @return
*/
public ObservableList<S> getBackingList() {
return backingList;
}
/**
* 変換関数
* @param item
* @return
*/
protected T unwrap(S item) {
return unwrapper.apply(item);
}
@Override
public T get(int index) {
if (backingList == null) {
throw new IndexOutOfBoundsException();
}
return unwrap(backingList.get(index));
}
@Override
public int size() {
if (backingList == null) {
return 0;
}
return backingList.size();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment