3

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.

7
  • You could use getPriority().ordinal() for getting the numeric value that corresponds to the priority order (order in which the constants are declared). Commented Oct 4, 2015 at 23:51
  • 2
    Doesn't PriorityQueue solve your problem? Use this constructor: PriorityQueue(Comparator<? super E> comparator). Commented Oct 4, 2015 at 23:52
  • You definitely don't want a LinkedHashSet. A LinkedHashSet keeps the items in insertion order, whereas you want priority order. Commented Oct 4, 2015 at 23:54
  • Problem with using PriorityQueue is that its iterator is not sorted. I need something that is not queue-like (which is more get-and-remove). Commented Oct 5, 2015 at 0:00
  • 1
    The trouble with a PriorityQueue is that ties are broken arbitrarily, whereas you want ties to be resolved on a first-in-first-out basis (I think). I think a Map<Priority, Deque<IListener>> would meet your exact requirements but I don't know if there's a simpler way. Commented Oct 5, 2015 at 0:00

3 Answers 3

3

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(); } }; } } 
Sign up to request clarification or add additional context in comments.

Comments

2

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(); } } 

Comments

1

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.

2 Comments

All enums already implement Comparable based on ordinal(), so you can use a TreeSet without specifying a Comparator. I also don't know what you mean by assigning ordinals. The ordinal field is final.
I didn't know, thanks. It's even simplier, then. Assigning ordinal values? Ups. I guess I messed up with C. I'll correct it. Thanks.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.