/** * This file is part of Erjang - A JVM-based Erlang VM * * Copyright (c) 2009 by Trifork * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ package erjang.m.ets; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import com.trifork.clj_ds.IMapEntry; import com.trifork.clj_ds.IPersistentCollection; import com.trifork.clj_ds.IPersistentMap; import com.trifork.clj_ds.IPersistentSet; import com.trifork.clj_ds.ISeq; import com.trifork.clj_ds.PersistentHashSet; import com.trifork.clj_ds.PersistentHashMap; import com.trifork.clj_ds.Seqable; import erjang.EAtom; import erjang.ECons; import erjang.EInteger; import erjang.EInternalPID; import erjang.EList; import erjang.EObject; import erjang.EProc; import erjang.EPseudoTerm; import erjang.ERT; import erjang.ESeq; import erjang.ETuple; import erjang.ETuple2; import erjang.NotImplemented; /** * */ @SuppressWarnings("rawtypes") public class ETableBag extends ETable { /** holds an Integer with the bag size */ AtomicInteger sizeRef; ETableBag(EProc owner, EAtom type, EInteger tid, EAtom aname, EAtom access, int keypos, boolean writeConcurrency, boolean isNamed, EInternalPID heirPid, EObject heirData) { super(owner, type, tid, aname, access, keypos, isNamed, heirPid, heirData, PersistentHashMap.EMPTY); try { sizeRef = new AtomicInteger(0); } catch (Exception e) { throw new Error(e); } } // // A bag is implemented as a map (of keys), that refer to // a collection of values with that key. This function creates // an empty one of those nested collections depending on // the ets type. // IPersistentCollection empty() { return type==Native.am_bag // normal bag; there can be several elements with the // same key, but no duplicates (i.e. == each other) ? EPersistentInsertionOrderedSet.EMPTY // duplicate bag, allows duplicate elements with the // same key; just hold values as a list : PersistentBag.EMPTY; } @Override protected void insert_many(final ESeq values) { in_tx(new WithMap<Object>() { @Override protected Object run(IPersistentMap map) { int count = 0; for (ESeq seq = values; !seq.isNil(); seq = seq.tail()) { ETuple value = seq.head().testTuple(); if (value == null) throw ERT.badarg(values); EObject key = get_key(value); IPersistentCollection c = (IPersistentCollection) map.valAt(key, empty()); // Insert element - noting whether the map grows int sizeBefore = c.count(); c = c.cons(value); int sizeAfter = c.count(); map = map.assoc(key, c); // Update size - if the map grew if (sizeAfter > sizeBefore) count += 1; } sizeRef.set(count + (Integer)sizeRef.get()); set(map); return null; } }); } @Override protected boolean insert_new_many(ESeq values) { throw new NotImplemented(); } @Override protected boolean insert_new_one(ETuple value) { throw new NotImplemented(); } @Override protected void insert_one(final ETuple value) { in_tx(new WithMap<Object>() { @Override protected Object run(IPersistentMap map) { IPersistentCollection empty = empty(); EObject key = get_key(value); IPersistentCollection c = (IPersistentCollection) map.valAt(key, empty); // Insert element - noting whether the map grows int sizeBefore = c.count(); c = c.cons(value); int sizeAfter = c.count(); map = map.assoc(key, c); set(map); // Update size - if the map grew Integer cnt0 = (Integer)sizeRef.get(); if (sizeAfter > sizeBefore) sizeRef.set(1 + (Integer)sizeRef.get()); return null; } }); } /** return a list of elements at given key */ @Override protected ESeq lookup(EObject key) { IPersistentMap ipm = deref(); IPersistentCollection set = (IPersistentCollection) ipm.valAt(key); ESeq res = ERT.NIL; if (set == null) return res; for(ISeq s = set.seq(); s != null; s = s.next()) { res = res.cons((EObject) s.first()); } return res.reverse(); } @Override protected EAtom member(EObject key) { IPersistentMap ipm = deref(); IPersistentCollection set = (IPersistentCollection) ipm.valAt(key); if (set != null && set.count() != 0) { return ERT.TRUE; } else { return ERT.FALSE; } } @Override protected EObject last() { return first(); } @Override protected EObject first() { // no need to run in_tx if we're only reading IPersistentMap map = deref(); if (map.count() == 0) { return Native.am_$end_of_table; } else { ISeq entseq = map.seq(); IMapEntry<EObject, EObject> ent = (IMapEntry<EObject, EObject>) entseq.first(); return (EObject) ent.getKey(); } } @Override int size() { return sizeRef.get(); } @SuppressWarnings("rawtypes") @Override public ESeq match(EPattern matcher) { EObject key = matcher.getKey(keypos1); if (key == null) { // oops, .. tablescan ESeq res = ERT.NIL; IPersistentMap map = deref(); for (ISeq entseq = map.seq(); entseq != null; entseq = entseq.next()) { IMapEntry ent = (IMapEntry) entseq.first(); if (ent == null) continue; Seqable coll = (Seqable)ent.getValue(); res = matcher.match_vars(res, coll.seq()); } return res.reverse(); } IPersistentMap map = deref(); IPersistentCollection coll = (IPersistentCollection) map.valAt(key); if (coll == null) return ERT.NIL; return matcher.match_vars(ERT.NIL, coll.seq()).reverse(); } @SuppressWarnings("rawtypes") @Override public ESeq match_object(final EPattern matcher) { EObject key = matcher.getKey(keypos1); if (key == null) { // oops, .. tablescan ESeq res = ERT.NIL; IPersistentMap map = deref(); for (ISeq entseq = map.seq(); entseq != null; entseq = entseq.next()) { IMapEntry ent = (IMapEntry) entseq.first(); if (ent == null) break; Seqable coll = (Seqable)ent.getValue(); res = matcher.match_members(res, coll.seq()); } return res.reverse(); } IPersistentMap map = deref(); IPersistentCollection coll = (IPersistentCollection) map.valAt(key); if (coll == null) return ERT.NIL; return matcher.match_members(ERT.NIL, coll.seq()).reverse(); } @Override protected void delete(final EObject key) { in_tx(new WithMap<Object>() { @Override protected Object run(IPersistentMap map) { IPersistentCollection empty = empty(); IPersistentCollection c = (IPersistentCollection) map.valAt(key, empty); try { map = map.without(key); } catch (Exception e) { // should not happen! throw new Error(e); } set(map); sizeRef.addAndGet(- c.count()); return null; } }); } @Override protected void delete_object(final ETuple obj) { in_tx(new WithMap<Object>() { @Override protected Object run(IPersistentMap map) { EObject key = get_key(obj); IPersistentCollection empty = empty(); IPersistentCollection c = (IPersistentCollection) map.valAt(key, empty); if (c == null || c.count()==0) return null; IPersistentCollection out = empty(); int deleted = 0; for (ISeq s = c.seq(); s != null; s = s.next()) { EObject val = (EObject) s.first(); if (val == null) break; if (! obj.equalsExactly(val)) { out = out.cons(val); } else { deleted += 1; } } if (out.count() == 0) { try { map = map.without(key); } catch (Exception e) { throw new Error(e); } } else { map = map.assoc(key, out); } set(map); sizeRef.addAndGet(-deleted); return null; } }); } @Override public EObject select(final EMatchSpec matcher, int limit) { IPersistentMap map = deref(); EObject key = matcher.getTupleKey(keypos1); if (key == null) { EBagCont cont0 = new EBagCont(matcher, map.seq(), null, limit); return cont0.select(); } else { IPersistentCollection coll = (IPersistentCollection) map.valAt(key); if (coll == null) { return Native.am_$end_of_table; } else { EBagCont cont0 = new EBagCont(matcher, null, coll.seq(), limit); return cont0.select(); } } } static class EBagCont extends EPseudoTerm implements ISelectContinuation { private final EMatchSpec matcher; private final ISeq map_ent; private final ISeq coll_ent; private final int limit; public EBagCont(EMatchSpec matcher, ISeq mapent, ISeq conent, int limit) { this.matcher = matcher; this.map_ent = mapent; this.coll_ent = conent; this.limit = limit; } public EObject select() { int count = 0; ESeq vals = ERT.NIL; ISeq map_seq = map_ent; ISeq coll_seq = coll_ent; while ((seq_has_more(map_seq) || seq_has_more(coll_seq)) && (limit < 0 || count < limit)) { if (!seq_has_more(coll_seq)) { IMapEntry ent = (IMapEntry) map_seq.first(); IPersistentCollection coll = (IPersistentCollection) ent.getValue(); coll_seq = coll.seq(); map_seq = map_seq.next(); } assert seq_has_more(coll_seq); ETuple candidate = (ETuple) coll_seq.first(); coll_seq = coll_seq.next(); EObject res; if ((res = matcher.match(candidate)) != null) { count += 1; vals = vals.cons(res); } } if (vals == ERT.NIL) { return Native.am_$end_of_table; } else if (!seq_has_more(map_seq) && !seq_has_more(coll_seq)) { return new ETuple2(vals, Native.am_$end_of_table); } else { return new ETuple2(vals, new EBagCont(matcher, map_seq, coll_seq, limit)); } } private boolean seq_has_more(ISeq ent) { return ent != null && ent != ent.empty(); } @Override public int hashCode() { // Shouldn't be called. return System.identityHashCode(this); } } @Override protected EInteger select_delete(final EMatchSpec matcher) { int delete_count = in_tx(new WithMap<Integer>() { @Override protected Integer run(IPersistentMap map) { ESeq vals = ERT.NIL; int initial_count = sizeRef.get(); EObject key = matcher.getTupleKey(keypos1); if (key == null) { vals = matcher.matching_values_bag(vals, (Map<EObject, IPersistentCollection>) map); } else { IPersistentCollection coll = (IPersistentCollection) map.valAt(key); if (coll != null) { vals = matcher.matching_values_coll(vals, coll.seq()); } } int count = 0; for (; !vals.isNil(); vals = vals.tail()) { try { ETuple val = (ETuple) vals.head(); key = val.elm(keypos1); IPersistentCollection coll = (IPersistentCollection) map.valAt(key); if (coll instanceof IPersistentSet) { IPersistentSet set = (IPersistentSet) coll; set = set.disjoin(val); if (set != coll) { count += 1; if (set.count() == 0) { map = map.without(key); } else { map = map.assoc(key, set); } } } else if (coll instanceof IPersistentBag) { IPersistentBag bag = (IPersistentBag)coll; bag = bag.disjoin(val); if (bag != coll) { count += 1; if (bag.count() == 0) { map = map.without(key); } else { map = map.assoc(key, bag); } } } } catch (Exception e) { // should not happen! throw new Error(e); } } set(map); sizeRef.set(initial_count-count); return count; }}); return ERT.box(delete_count); } protected void delete_all_objects() { in_tx(new WithMap<Object>() { @Override protected Object run(IPersistentMap map) { set(empty); sizeRef.set(0); return null; } }); } @Override public ESeq slot() { IPersistentMap map = deref(); if (map.count() == 0) return ERT.NIL; return new ELSeq(map.seq()); } static class ELSeq extends ESeq { private final ISeq coll; private final ISeq map_seq; ELSeq(ISeq map) { IMapEntry collent = (IMapEntry)map.first(); IPersistentCollection c = (IPersistentCollection) collent.getValue(); while (c.count() == 0) { map = map.next(); collent = (IMapEntry) map.first(); c = (IPersistentCollection) collent.getValue(); } coll = c.seq(); map_seq = map.next(); } ELSeq(ISeq coll, ISeq map_seq) { this.coll = coll; this.map_seq = map_seq; } @Override public ESeq cons(EObject h) { return new EList(h, this); } @Override public ESeq tail() { ISeq c2 = coll.next(); if (c2 == null) { if (map_seq == null || map_seq == map_seq.empty()) return ERT.NIL; return new ELSeq(map_seq); } else { return new ELSeq(c2, map_seq); } } @Override public EObject head() { return (EObject) coll.first(); } @Override public boolean isNil() { return false; } @Override public ECons testNonEmptyList() { return this; } } }