All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.almworks.jira.structure.api.item.ItemIdentitySet Maven / Gradle / Ivy

There is a newer version: 17.25.3
Show newest version
package com.almworks.jira.structure.api.item;

import com.almworks.integers.*;
import com.almworks.jira.structure.api.row.RowRetriever;
import com.atlassian.annotations.Internal;
import com.google.common.collect.AbstractIterator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

import static com.almworks.jira.structure.api.item.ItemIdentity.longId;
import static com.almworks.jira.structure.api.item.ItemIdentity.stringId;
import static com.almworks.jira.structure.api.row.ItemAccessMode.ITEM_NOT_NEEDED;
import static com.almworks.jira.structure.api.row.RowRetriever.IGNORE_MISSING_ROWS;

/**
 * 

An implementation of a {@link Set} of {@link ItemIdentity}, optimized for memory consumption and objects count.

* *

Implementation Notes

* *

Statistics: compared to HashSet, 4x less memory taken, 1.5x slower. The number of objects is ~ 10 (compared to N*2 with * HashSet), if all item IDs are long. String item IDs create K*2 objects as well.

*/ @Internal public class ItemIdentitySet extends AbstractSet { private static final int SUBSET_INITIAL_CAPACITY = 10; @Nullable private Map myLongIds; @Nullable private Map myStringIds; private int myModCount; private volatile boolean myImmutable; public ItemIdentitySet() { } public ItemIdentitySet(ItemIdentity... ids) { if (ids != null) { addAll(Arrays.asList(ids)); } } public ItemIdentitySet(Collection ids) { if (ids != null) { addAll(ids); } } public static ItemIdentitySet of(String itemType, long[] ids) { return of(itemType, new LongArray(ids)); } public static ItemIdentitySet of(String itemType, LongIterable ids) { ItemIdentitySet set = new ItemIdentitySet(); set.addAll(itemType, ids); return set; } public static ItemIdentitySet of(String itemType, Iterable ids) { ItemIdentitySet set = new ItemIdentitySet(); set.addAll(itemType, ids); return set; } @NotNull @Override public Iterator iterator() { return new RecollectingIterator(); } @Override public int size() { int r = 0; if (myLongIds != null) { for (COWLongSet set : myLongIds.values()) { r += set.set().size(); } } if (myStringIds != null) { for (COWStringSet set : myStringIds.values()) { r += set.set().size(); } } return r; } @Override public boolean contains(Object o) { if (!(o instanceof ItemIdentity)) return false; ItemIdentity id = (ItemIdentity) o; if (id.isLongId()) { if (myLongIds == null) return false; COWLongSet set = myLongIds.get(id.getItemType()); return set != null && set.set().contains(id.getLongId()); } else { if (myStringIds == null) return false; COWStringSet set = myStringIds.get(id.getItemType()); return set != null && set.set().contains(id.getStringId()); } } @Override public boolean containsAll(@NotNull Collection c) { if (c.size() > size()) return false; if (!(c instanceof ItemIdentitySet)) { return super.containsAll(c); } ItemIdentitySet set = (ItemIdentitySet) c; if (set.myLongIds != null) { for (Map.Entry e : set.myLongIds.entrySet()) { if (!e.getValue().set().isEmpty()) { if (myLongIds == null) return false; COWLongSet longSet = myLongIds.get(e.getKey()); if (longSet == null || !longSet.set().containsAll(e.getValue().set())) return false; } } } if (set.myStringIds != null) { for (Map.Entry e : set.myStringIds.entrySet()) { if (!e.getValue().set().isEmpty()) { if (myStringIds == null) return false; COWStringSet stringSet = myStringIds.get(e.getKey()); if (stringSet == null || !stringSet.set().containsAll(e.getValue().set())) return false; } } } return true; } @Override public boolean add(ItemIdentity id) { checkImmutable(); if (id == null) { throw new IllegalArgumentException("null id"); } boolean changed; String itemType = id.getItemType(); if (id.isLongId()) { Map map = ensureLongIdsMap(1); COWLongSet set = map.get(itemType); if (set == null) { set = new COWLongSet(id.getLongId()); map.put(itemType, set); changed = true; } else { changed = set.add(id.getLongId()); } } else { Map map = ensureStringIdsMap(1); COWStringSet set = map.get(itemType); if (set == null) { set = new COWStringSet(id.getStringId()); map.put(itemType, set); changed = true; } else { changed = set.add(id.getStringId()); } } if (changed) { myModCount++; } return changed; } @Override public boolean addAll(@NotNull Collection c) { checkImmutable(); if (c == this) { return false; } if (!(c instanceof ItemIdentitySet)) { return super.addAll(c); } boolean modified = false; ItemIdentitySet set = (ItemIdentitySet) c; if (set.myLongIds != null) { Map map = ensureLongIdsMap(set.myLongIds.size()); for (Map.Entry e : set.myLongIds.entrySet()) { COWLongSet longSet = map.get(e.getKey()); if (longSet == null) { map.put(e.getKey(), e.getValue().copy(this)); modified = true; } else { modified |= longSet.addAll(e.getValue()); } } } if (set.myStringIds != null) { Map map = ensureStringIdsMap(set.myStringIds.size()); for (Map.Entry e : set.myStringIds.entrySet()) { COWStringSet stringSet = map.get(e.getKey()); if (stringSet == null) { map.put(e.getKey(), e.getValue().copy(this)); modified = true; } else { modified |= stringSet.addAll(e.getValue()); } } } if (modified) { myModCount++; } return modified; } public void addAll(@NotNull String type, @NotNull Iterable strings) { for (String id: strings) { add(stringId(type, id)); } } public void addAll(@NotNull String type, @NotNull LongIterable longs) { for (LongIterator it: longs) { add(longId(type, it.value())); } } @Override public boolean remove(Object o) { checkImmutable(); if (!(o instanceof ItemIdentity)) return false; ItemIdentity id = (ItemIdentity) o; String itemType = id.getItemType(); boolean removed; if (id.isLongId()) { if (myLongIds == null) { removed = false; } else { COWLongSet set = myLongIds.get(itemType); removed = set != null && set.remove(id.getLongId()); if (removed && set.set().isEmpty()) { myLongIds.remove(itemType); } } } else { if (myStringIds == null) { removed = false; } else { COWStringSet set = myStringIds.get(itemType); removed = set != null && set.remove(id.getStringId()); if (removed && set.set().isEmpty()) { myStringIds.remove(itemType); } } } if (removed) { myModCount++; } return removed; } @Override public boolean removeAll(Collection c) { // todo could be optimized if we use it boolean changed = false; for (Object o : c) { changed |= remove(o); } return changed; } @Override public boolean retainAll(Collection c) { // todo optimize if we use it ItemIdentitySet copy = copy(); boolean changed = false; for (ItemIdentity id : copy) { if (!c.contains(id)) { remove(id); changed = true; } } return changed; } public ItemIdentitySet makeImmutable() { myImmutable = true; return this; } public boolean isImmutable() { return myImmutable; } private void checkImmutable() { if (myImmutable) { throw new IllegalStateException(this + " is immutable"); } } @Override public void clear() { checkImmutable(); if (myLongIds != null) { myLongIds.clear(); } if (myStringIds != null) { myStringIds.clear(); } myModCount++; } public Iterable getItemTypes() { return new Iterable() { @Override public Iterator iterator() { return new TypesIterator(); } }; } @NotNull private Map ensureLongIdsMap(int initialSize) { if (myLongIds == null) { myLongIds = new HashMap<>(initialSize); } return myLongIds; } @NotNull private Map ensureStringIdsMap(int initialSize) { if (myStringIds == null) { myStringIds = new HashMap<>(initialSize); } return myStringIds; } public ItemIdentitySet copyAllOfType(String typeId) { ItemIdentitySet r = new ItemIdentitySet(); if (myLongIds != null) { COWLongSet set = myLongIds.get(typeId); if (set != null) { r.myLongIds = new HashMap<>(1); r.myLongIds.put(typeId, set.copy(r)); } } if (myStringIds != null) { COWStringSet set = myStringIds.get(typeId); if (set != null) { r.myStringIds = new HashMap<>(1); r.myStringIds.put(typeId, set.copy(r)); } } return r; } public ItemIdentitySet copy() { ItemIdentitySet r = new ItemIdentitySet(); r.addAll(this); return r; } private WritableLongSet createLongSet(int capacity, Long addElement) { LongOpenHashSet set = new LongOpenHashSet(capacity); if (addElement != null) { set.addAll(addElement); } return set; } private Set createStringSet(int capacity, String addElement) { Set set = new HashSet<>(capacity); if (addElement != null) { set.add(addElement); } return set; } public LongSizedIterable longIds(String typeId) { COWLongSet set = myLongIds == null ? null : myLongIds.get(typeId); if (set == null) { return LongList.EMPTY; } return set.set(); } public Set stringIds(String typeId) { COWStringSet set = myStringIds == null ? null : myStringIds.get(typeId); if (set == null) { return Collections.emptySet(); } return Collections.unmodifiableSet(set.set()); } @NotNull public static Set collectItemIds(@NotNull RowRetriever rowRetriever, @Nullable LongIterable rows) { if (rows == null) return Collections.emptySet(); ItemIdentitySet r = new ItemIdentitySet(); rowRetriever.scanRows(rows, false, ITEM_NOT_NEEDED, IGNORE_MISSING_ROWS, row -> { r.add(row.getItemId()); return true; }); return r; } private abstract class COWBaseSet> { @NotNull private T mySet; private boolean myCopyOnWrite; public COWBaseSet(@NotNull T set, boolean copyOnWrite) { mySet = set; myCopyOnWrite = copyOnWrite; } protected abstract T makeCopy(T set, int additionalSize); protected abstract S createInstance(T set, boolean copyOnWrite, ItemIdentitySet owner); public T set() { return mySet; } public S copy(ItemIdentitySet owner) { T copied = set(); if (isSafeToReuse()) { return createInstance(copied, true, owner); } else { return createInstance(makeCopy(copied, 0), false, owner); } } protected boolean isSafeToReuse() { return myImmutable || myCopyOnWrite; } protected void maybeCopy(int additionalSize) { if (myCopyOnWrite) { mySet = makeCopy(mySet, additionalSize); myCopyOnWrite = false; } } } private class COWLongSet extends COWBaseSet { public COWLongSet(@NotNull WritableLongSet set, boolean copyOnWrite) { super(set, copyOnWrite); } public COWLongSet(long id) { super(createLongSet(SUBSET_INITIAL_CAPACITY, id), false); } @Override protected WritableLongSet makeCopy(WritableLongSet set, int additionalSize) { WritableLongSet newSet = createLongSet(set.size() + additionalSize, null); newSet.addAll(set); return newSet; } @Override protected COWLongSet createInstance(WritableLongSet set, boolean copyOnWrite, ItemIdentitySet owner) { return owner.new COWLongSet(set, copyOnWrite); } public boolean addAll(COWLongSet value) { maybeCopy(value.set().size()); WritableLongSet set = set(); int sizeBefore = set.size(); set.addAll(value.set()); return sizeBefore != set.size(); } public boolean add(long id) { maybeCopy(1); return set().include(id); } public boolean remove(long id) { maybeCopy(0); return set().exclude(id); } } private class COWStringSet extends COWBaseSet, COWStringSet> { public COWStringSet(@NotNull Set set, boolean copyOnWrite) { super(set, copyOnWrite); } public COWStringSet(String id) { super(createStringSet(SUBSET_INITIAL_CAPACITY, id), false); } @Override protected Set makeCopy(Set set, int additionalSize) { Set newSet = createStringSet(set.size() + additionalSize, null); newSet.addAll(set); return newSet; } @Override protected COWStringSet createInstance(Set set, boolean copyOnWrite, ItemIdentitySet owner) { return owner.new COWStringSet(set, copyOnWrite); } public boolean addAll(COWStringSet value) { maybeCopy(value.set().size()); return set().addAll(value.set()); } public boolean add(String id) { maybeCopy(1); return set().add(id); } public boolean remove(String id) { maybeCopy(0); return set().remove(id); } } // todo do we need it writable? private class RecollectingIterator extends AbstractIterator { private final int myStartingModCount = myModCount; // java 8 streams would have been helpful private Iterator> myLongIdsIterator = myLongIds == null ? Collections.>emptyIterator() : myLongIds.entrySet().iterator(); private Iterator> myStringIdsIterator = myStringIds == null ? Collections.>emptyIterator() : myStringIds.entrySet().iterator(); private String myCurrentType; private LongIterator myCurrentLongIterator; private Iterator myCurrentStringIterator; @Override protected ItemIdentity computeNext() { if (myModCount != myStartingModCount) { throw new ConcurrentModificationException(); } if (myLongIdsIterator != null) { // iterating longs if (myCurrentLongIterator != null && myCurrentLongIterator.hasNext()) { assert myCurrentType != null : this; return longId(myCurrentType, myCurrentLongIterator.nextValue()); } while (myLongIdsIterator.hasNext()) { Map.Entry next = myLongIdsIterator.next(); myCurrentType = next.getKey(); myCurrentLongIterator = next.getValue().set().iterator(); if (myCurrentLongIterator.hasNext()) { return longId(myCurrentType, myCurrentLongIterator.nextValue()); } } myCurrentLongIterator = null; myLongIdsIterator = null; } if (myStringIdsIterator != null) { // iterating strings if (myCurrentStringIterator != null && myCurrentStringIterator.hasNext()) { assert myCurrentType != null : this; return stringId(myCurrentType, myCurrentStringIterator.next()); } while (myStringIdsIterator.hasNext()) { Map.Entry next = myStringIdsIterator.next(); myCurrentType = next.getKey(); myCurrentStringIterator = next.getValue().set().iterator(); if (myCurrentStringIterator.hasNext()) { return stringId(myCurrentType, myCurrentStringIterator.next()); } } myCurrentStringIterator = null; myStringIdsIterator = null; } return endOfData(); } } private class TypesIterator extends AbstractIterator { private final int myStartingModCount = myModCount; private Iterator myLongKeysIterator = myLongIds == null ? Collections.emptyIterator() : myLongIds.keySet().iterator(); private Iterator myStringIdsIterator = myStringIds == null ? Collections.emptyIterator() : myStringIds.keySet().iterator(); @Override protected String computeNext() { if (myModCount != myStartingModCount) { throw new ConcurrentModificationException(); } if (myLongKeysIterator != null) { if (myLongKeysIterator.hasNext()) { return myLongKeysIterator.next(); } myLongKeysIterator = null; } if (myStringIdsIterator != null) { while (myStringIdsIterator.hasNext()) { String type = myStringIdsIterator.next(); if (myLongIds == null || !myLongIds.containsKey(type)) { return type; } } myStringIdsIterator = null; } return endOfData(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy