Ernio
Ernio

Reputation: 978

Sorting by enum on adding to Set

I need to create priority set/array that bases on:

public interface IListener
{
    public Priority getPriority();

    public enum Priority
    {
        HIGHEST,
        HIGH,
        NORMAL,
        LOW,
        LOWEST;
    }
}

IListeners are stored in:

HashMap<Class<? extends IListener>, Set<IListener>> listeners = new HashMap<>();

I am looking to make method that will always add IListener in 1st place after its Priority group. Example: Given Set contains some IListeners with this order.

{ HIGHEST, HIGHEST, HIGH, HIGH, LOW, LOW, LOW, LOWEST }

Adding listener with Priority == HIGH would result in:

{ HIGHEST, HIGHEST, HIGH, HIGH, HIGH, LOW, LOW, LOW, LOWEST }

Bold one being newly added.

I know I could just iterate and add at 1st "free slot", but question is rather - does Java provide some good-looking (maybe better?) solutions? Might be just for future reference.

Upvotes: 3

Views: 1162

Answers (3)

Little Santi
Little Santi

Reputation: 8803

Keep it easy:

You can combine several kinds of collections:

  • A LinkedHashSet allows you to store items by ordering them by insertion order (and with no repeated items).
  • A TreeMap allows you to map keys and values ordering them according to the keys.

Thus, you can declare this combination:

TreeMap<IListener.Priority, LinkedHashSet<IListener>> listenersByPriority=new TreeMap<IListener.Priority, LinkedHashSet<IListener>>(new PriorityComparator());

... and encapsulate it in a proper abstraction to manage it:

public class ListenerManager
{
    private final TreeMap<IListener.Priority, LinkedHashSet<IListener>> listenersByPriority=new TreeMap<IListener.Priority, LinkedHashSet<IListener>>();

    private int size;

    public void addListener(IListener listener)
    {
        synchronized (listenersByPriority)
        {
            LinkedHashSet<IListener> list=listenersByPriority.get(listener.getPriority());
            if (list == null)
            {
                list=new LinkedHashSet<IListener>();
                listenersByPriority.put(listener.getPriority(), list);
            }
            list.add(listener);
            size++;
        }
    }

    public Iterator<IListener> iterator()
    {
        synchronized (listenersByPriority)
        {
            List<IListener> output=new ArrayList<IListener>(size);
            for (LinkedHashSet<IListener> list : listenersByPriority.values())
            {
                for (IListener listener : list)
                {
                    output.add(listener);
                }
            }
            return output.iterator();
        }
    }
}

When declaring a TreeMap, it is usually necessary an specific implementation of Comparator coupled to the key class, but it is not necessary in this case, because enums are already comparable by its ordinal. (thanks to Paul Boddington). And the ordinal of each enum item is the position it is placed in the declaration.

Upvotes: 1

Tagir Valeev
Tagir Valeev

Reputation: 100219

An alternative approach is to use TreeSet with custom comparator and automatically assign autogenerated ids to the elements added to the set, so the latter elements always get bigger id which can be used in comparison:

public class IListenerSet extends AbstractSet<IListener> {
    private long maxId = 0;
    private final Map<IListener, Long> ids = new HashMap<>();
    private final Set<IListener> set = new TreeSet<>(new Comparator<IListener>() {
        @Override
        public int compare(IListener o1, IListener o2) {
            int res = o1.getPriority().compareTo(o2.getPriority());
            if(res == 0)
                res = ids.get(o1).compareTo(ids.get(o2));
            return res;
        }
    });

    @Override
    public Iterator<IListener> iterator() {
        return new Iterator<IListener>() {
            Iterator<IListener> it = set.iterator();
            private IListener e;

            @Override
            public boolean hasNext() {
                return it.hasNext();
            }

            @Override
            public IListener next() {
                this.e = it.next();
                return e;
            }

            @Override
            public void remove() {
                it.remove();
                ids.remove(e);
            }
        };
    }

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

    @Override
    public boolean contains(Object o) {
        return ids.containsKey(o);
    }

    @Override
    public boolean add(IListener e) {
        if(ids.get(e) != null)
            return false;
        // assign new id and store it in the internal map
        ids.put(e, ++maxId);
        return set.add(e);
    }

    @Override
    public boolean remove(Object o) {
        if(!ids.containsKey(o)) return false;
        set.remove(o);
        return true;
    }

    @Override
    public void clear() {
        ids.clear();
        set.clear();
    }
}

Upvotes: 2

Paul Boddington
Paul Boddington

Reputation: 37645

As indicated in the comments, I don't think there is any collection in the JDK that exactly meets your requirements.

IListenerSet is an implementation of Set that meets your needs. The iterator always returns the elements in order of priority. If two elements have the same priority, they are returned in the order they were put into the set. The set supports addition and removal. The iterator supports the remove() method. The set cannot contain null, and throws a NullPointerException if you try to add null. The set cannot contain an IListener for which getPriority() returns null, and throws an IllegalArgumentException if you try to add such an element.

public final class IListenerSet<T extends IListener> extends AbstractSet<T> {

    private final Map<IListener.Priority, Set<T>> map;

    public IListenerSet() {
        map = new EnumMap<>(IListener.Priority.class);
        for (IListener.Priority p : IListener.Priority.values())
            map.put(p, new LinkedHashSet<>());
    }

    public IListenerSet(Collection<? extends T> collection) {
        this();
        addAll(collection);
    }

    @Override
    public int size() {
        int size = 0;
        for (Set<T> set : map.values())
            size += set.size();
        return size;
    }

    @Override
    public boolean contains(Object o) {
        if (!(o instanceof IListener))
            return false;
        IListener listener = (IListener) o;
        IListener.Priority p = listener.getPriority();
        return p != null && map.get(p).contains(listener);
    }

    @Override
    public boolean add(T listener) {
        IListener.Priority p = listener.getPriority();
        if (p == null)
            throw new IllegalArgumentException();
        return map.get(p).add(listener);
    }

    @Override
    public boolean remove(Object o) {
        if (!(o instanceof IListener))
            return false;
        IListener listener = (IListener) o;
        IListener.Priority p = listener.getPriority();
        return p != null && map.get(p).remove(listener);
    }

    @Override
    public void clear() {
        for (Set<T> set : map.values())
            set.clear();
    }

    @Override
    public Iterator<T> iterator() {
        return new Iterator<T>() {

            private Iterator<T> iterator = map.get(IListener.Priority.values()[0]).iterator();
            private int nextIndex = 1;
            private Iterator<T> nextIterator = null;

            @Override
            public boolean hasNext() {
                if (iterator.hasNext() || nextIterator != null)
                    return true;
                IListener.Priority[] priorities = IListener.Priority.values();
                while (nextIndex < priorities.length) {
                    Set<T> set = map.get(priorities[nextIndex++]);
                    if (!set.isEmpty()) {
                        nextIterator = set.iterator();
                        return true;
                    }
                }
                return false;
            }

            @Override
            public T next() {
                if (iterator.hasNext())
                    return iterator.next();
                if (!hasNext())
                    throw new NoSuchElementException();
                iterator = nextIterator;
                nextIterator = null;
                return iterator.next();
            }

            @Override
            public void remove() {
                iterator.remove();
            }
        };
    }
}

Upvotes: 3

Related Questions