簡単な入れ子構造を扱う #20

いけなくない。
locationsそのものを公開することが問題のポイントなので、
Itemが利用しているTreeSetを要素数0にできないものに置き換えるか、
Itemが外に提供するセットを要素数0にできないものに置き換えればいい。
ここでは後者の方法をとってみる。
変更はItemだけで済む。

Item.java
package index;

import java.util.Set;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.TreeSet;

class Item<N, L extends Comparable<L>> {
    private final N name;
    private final TreeSet<L> locations = new TreeSet<L>();

    Item(N name, L location) {
        if (name == null) throw new IllegalArgumentException("null name");
        if (location == null) throw new IllegalArgumentException("null location");
        this.name = name;
        locations.add(location);
    }

    boolean add(L location) {
        return locations.add(location);
    }

    Set<L> get() {
        return new LocationSet();
    }

    private final class LocationSet extends AbstractSet<L> {
        @Override public boolean add(L e) { return locations.add(e); }

        @Override public boolean addAll(Collection<? extends L> c) { return locations.addAll(c); }

        @Override public void clear() { throw new UnsupportedOperationException(); }

        @Override public Iterator<L> iterator() {
            return new Iterator<L>() {
                private Iterator<L> iterator = locations.iterator();

                public boolean hasNext() {
                    return iterator.hasNext();
                }

                public L next() {
                    return iterator.next();
                }

                public void remove() {
                    if (size() == 1) throw new UnsupportedOperationException();
                    iterator.remove();
                }
            };
        }

        @Override public boolean remove(Object o) {
            if (size() == 1 && locations.contains(o)) throw new UnsupportedOperationException();
            return locations.remove(o);
        }

        @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); }

        @Override public int size() { return locations.size(); }
    }
}

Itemが提供するLocationSetはAbstractSetを継承している。
基本的にこのセットへの操作はlocationsに委譲しているが削除関連だけ特別になっている。
clear()やremoveAll()はいきなり例外を投げる。
remove()は要素数が0になる削除なら例外を出す。
iterator()はremove操作で要素数を確認するよう実装したIteratorを返す。
この反復子自身がlocationsの反復子へ機能を委譲するものであり、
AbstractSetやAbstractCollectionが多くの操作をこの反復子を利用して実現するため、
委譲のためのメソッドを明示的に全ては書かなくても済んでいる。
ただ、AbstractSetがサポートしていない要素追加関連のメソッドは明示的に委譲しなければならない。
また、size()は実装されていないため委譲メソッドを書いている。