/**
* 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.Map.Entry;
import com.trifork.clj_ds.IMapEntry;
import com.trifork.clj_ds.IPersistentMap;
import com.trifork.clj_ds.ISeq;
import com.trifork.clj_ds.PersistentHashMap;
import com.trifork.clj_ds.PersistentTreeMap;
import com.trifork.clj_ds.Reversible;
import erjang.EAtom;
import erjang.ECons;
import erjang.EInteger;
import erjang.EInternalPID;
import erjang.EList;
import erjang.ENumber;
import erjang.EObject;
import erjang.EPID;
import erjang.EProc;
import erjang.EPseudoTerm;
import erjang.ERT;
import erjang.ESeq;
import erjang.ESmall;
import erjang.ETuple;
import erjang.ETuple2;
import erjang.NotImplemented;
import erjang.m.erlang.ErlBif;
/**
*
*/
@SuppressWarnings("rawtypes")
public class ETableSet extends ETable {
private boolean ordered;
ETableSet(EProc owner, EAtom type, EInteger tid, EAtom aname, EAtom access, int keypos,
boolean write_concurrency, boolean is_named, EInternalPID heirPID, EObject heirData) {
super(owner, type, tid, aname, access, keypos,
is_named, heirPID, heirData,
type == Native.am_set
? EPersistentInsertionOrderedMap.EMPTY
: new PersistentTreeMap(null, EObject.ERLANG_ORDERING));
this.ordered = type != Native.am_set;
}
@Override
int size() {
return deref().count();
}
@Override
protected void insert_one(final ETuple value) {
in_tx(new WithMap<Object>() {
@Override
protected Object run(IPersistentMap map) {
EObject key = get_key(value);
IPersistentMap new_map = map.assoc(key, value);
set(new_map);
return null;
}
});
}
@Override
protected void insert_many(final ESeq values) {
in_tx(new WithMap<Object>() {
@Override
protected Object run(IPersistentMap map) {
for (ESeq seq = values; !seq.isNil(); seq = seq.tail()) {
ETuple value = seq.head().testTuple();
if (value == null) throw ERT.badarg(values);
map = map.assoc(get_key(value), value);
}
set(map);
return null;
}});
}
@Override
protected ESeq lookup(EObject key) {
ESeq res = ERT.NIL;
// no need to run in_tx if we're only reading
EObject val = (EObject) deref().valAt(key);
if (val != null) {
return res.cons(val);
} else {
return res;
}
}
@Override
protected EAtom member(EObject key) {
// no need to run in_tx if we're only reading
EObject val = (EObject) deref().valAt(key);
if (val != null) {
return ERT.TRUE;
} else {
return ERT.FALSE;
}
}
@Override
public ESeq slot() {
IPersistentMap map = deref();
if (map.count() == 0) return ERT.NIL;
ISeq seq = map.seq();
return new ELSeq(seq);
}
static class ELSeq extends ESeq {
private ISeq seq;
ELSeq(ISeq s) {
this.seq = s;
}
@Override
public ESeq cons(EObject h) {
return new EList(h, this);
}
@Override
public ESeq tail() {
ISeq next = seq.next();
if (next == null) return ERT.NIL;
return new ELSeq(next);
}
@Override
public EObject head() {
IMapEntry ent = (IMapEntry) seq.first();
return (EObject) ent.getValue();
}
@Override
public boolean isNil() {
return false;
}
@Override
public ECons testNonEmptyList() {
return this;
}
}
@Override
protected EObject last() {
if (!ordered) { return 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;
try {
entseq = ((Reversible)map).rseq();
} catch (Exception e) {
throw new InternalError("cannot reverse");
}
if (entseq == null) return Native.am_$end_of_table;
IMapEntry ent = (IMapEntry) entseq.first();
if (ent == null) return Native.am_$end_of_table;
return (EObject) ent.getKey();
}
}
@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();
if (entseq == null) return Native.am_$end_of_table;
IMapEntry ent = (IMapEntry) entseq.first();
if (ent == null) return Native.am_$end_of_table;
return (EObject) ent.getKey();
}
}
@Override
protected boolean insert_new_many(final ESeq values) {
// Input verification outside of transaction:
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);
}
return in_tx(new WithMap<Boolean>() {
@Override
protected Boolean run(IPersistentMap map) {
for (ESeq seq = values; !seq.isNil(); seq = seq.tail()) {
ETuple value = seq.head().testTuple();
EObject key = get_key(value);
if (map.containsKey(key)) {
return false;
}
}
for (ESeq seq = values; !seq.isNil(); seq = seq.tail()) {
ETuple value = seq.head().testTuple();
EObject key = get_key(value);
map = map.assoc(key, value);
}
set(map);
return true;
}
});
}
@Override
protected boolean insert_new_one(final ETuple value) {
final EObject key = get_key(value);
if (!deref().containsKey(key)) {
return in_tx(new WithMap<Boolean>() {
@Override
protected Boolean run(IPersistentMap map) {
if (!map.containsKey(key)) {
set(map.assoc(key, value));
return true;
}
return false;
}
});
} else {
return false;
}
}
@Override
public ESeq match(EPattern matcher) {
IPersistentMap map = deref();
ESeq res = ERT.NIL;
EObject key = matcher.getKey(keypos1);
if (key == null) {
res = matcher.match(res, (Map<EObject, ETuple>) map);
if (ordered) res = res.reverse();
} else {
ETuple candidate = (ETuple) map.valAt(key);
if (candidate != null) {
res = matcher.match(res, candidate);
}
}
return res;
}
@Override
public ESeq match_object(EPattern matcher) {
IPersistentMap map = deref();
ESeq res = ERT.NIL;
EObject key = matcher.getKey(keypos1);
if (key == null) {
res = matcher.match_members(res, (Map<EObject, ETuple>) map);
if (ordered) res = res.reverse();
} else {
ETuple candidate = (ETuple) map.valAt(key);
if (candidate != null) {
res = matcher.match_members(res, candidate);
}
}
return res;
}
@Override
protected void delete(final EObject key) {
in_tx(new WithMap<Object>() {
@Override
protected Object run(IPersistentMap map) {
try {
map = map.without(key);
} catch (Exception e) {
// should not happen!
throw new Error(e);
}
set(map);
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);
IMapEntry candidateEntry = map.entryAt(key);
if (candidateEntry == null) return null;
EObject candidate = (EObject)candidateEntry.val();
if (candidate != null && obj.equalsExactly(candidate)) {
try {
map = map.without(key);
set(map);
} catch (Exception e) {
throw new Error(e);
}
}
return null;
}
});
}
@Override
public EObject select(final EMatchSpec matcher, int limit) {
IPersistentMap map = deref();
EObject key = matcher.getTupleKey(keypos1);
if (key == null) {
ESetCont cont0 = new ESetCont(matcher, map.seq(), ordered, limit);
return cont0.select();
} else {
ETuple candidate = (ETuple) map.valAt(key);
if (candidate == null) return Native.am_$end_of_table;
EObject res;
if ((res = matcher.match(candidate)) != null) {
return new ETuple2(ERT.NIL.cons(res), Native.am_$end_of_table);
}
}
return Native.am_$end_of_table;
}
static class ESetCont extends EPseudoTerm implements ISelectContinuation {
private final ISeq ent;
private final EMatchSpec matcher;
private final boolean ordered;
private final int limit;
public ESetCont(EMatchSpec matcher, ISeq ent, boolean ordered, int limit) {
this.matcher = matcher;
this.ent = ent;
this.ordered = ordered;
this.limit = limit;
}
public EObject select() {
int count = 0;
ESeq vals = ERT.NIL;
ISeq map_seq = this.ent;
while (seq_has_more(map_seq) && (limit < 0 || count < limit)) {
IMapEntry mape = (IMapEntry) map_seq.first();
map_seq = map_seq.next();
ETuple candidate = (ETuple) mape.getValue();
EObject res;
if ((res = matcher.match(candidate)) != null) {
count += 1;
vals = vals.cons(res);
}
}
if (ordered) vals = vals.reverse();
if (vals == ERT.NIL) {
return Native.am_$end_of_table;
} else if (!seq_has_more(map_seq)) {
return new ETuple2(vals, Native.am_$end_of_table);
} else {
return new ETuple2(vals, new ESetCont(matcher, map_seq, ordered, 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
public EInteger select_delete(final EMatchSpec matcher) {
int delete_count = in_tx(new WithMap<Integer>() {
@Override
protected Integer run(IPersistentMap map) {
EObject key = matcher.getTupleKey(keypos1);
int count = 0;
if (key == null) {
for (Map.Entry<EObject, ETuple> ent : ((Map<EObject, ETuple>) map).entrySet()) {
ETuple val = ent.getValue();
if (matcher.matches(val)) {
try {
map = map.without(ent.getKey());
} catch (Exception e) {
throw new RuntimeException(e);
}
count += 1;
}
}
} else {
ETuple candidate = (ETuple) map.valAt(key);
if (candidate != null && matcher.matches(candidate)) {
try {
map = map.without(key);
} catch (Exception e) {
throw new RuntimeException(e);
}
count += 1;
}
}
set(map);
return count;
}});
return ERT.box(delete_count);
}
public EObject update_counter(final EObject key, final EObject upd) {
return in_tx(new WithMap<EObject>() {
@Override
protected EObject run(IPersistentMap map) {
ETuple rec = (ETuple) map.valAt(key);
if (rec == null)
return null; // fail with badarg
// TODO: figure out match/equals semantics
if (type == Native.am_set) {
if (!key.equalsExactly( get_key(rec) )) {
return null;
}
}
EInteger incr;
ETuple one;
if ((incr=upd.testInteger()) != null) {
int idx = keypos1+1;
rec = update(rec, idx, incr);
if (rec == null) return null;
map = map.assoc(get_key(rec), rec);
set(map);
return rec.elm(idx);
} else if ((one=upd.testTuple()) != null) {
if (one.arity() == 2) {
ESmall eidx = one.elm(1).testSmall();
incr = one.elm(2).testInteger();
if (eidx == null || eidx.value > rec.arity() || incr == null) return null;
int idx = eidx.value;
rec = update(rec, idx, incr);
if (rec == null) return null;
map = map.assoc(get_key(rec), rec);
set(map);
return rec.elm(idx);
} else if (one.arity() == 4){
ESmall eidx = one.elm(1).testSmall();
incr = one.elm(2).testInteger();
EInteger threshold = one.elm(3).testInteger();
EInteger setvalue = one.elm(4).testInteger();
if (eidx == null || eidx.value > rec.arity() || incr == null
|| threshold == null || setvalue == null) return null;
int idx = eidx.value;
rec = update(rec, idx, incr, threshold, setvalue);
if (rec == null) return null;
map = map.assoc(get_key(rec), rec);
set(map);
return rec.elm(idx);
} else {
return null;
}
} else {
throw new NotImplemented();
}
}
private ETuple update(ETuple rec, int idx, EInteger incr) {
EInteger old = rec.elm(idx).testInteger();
if (old == null) return null;
EObject val = old.add(incr);
rec = ErlBif.setelement(idx, rec, val);
return rec;
}
private ETuple update(ETuple rec, int idx, EInteger incr, EInteger threshold, EInteger setvalue) {
EInteger old = rec.elm(idx).testInteger();
if (old == null) return null;
ENumber val = old.add(incr);
if (incr.is_ge(ESmall.ZERO)) {
if (threshold.is_lt(val)) {
val = setvalue;
}
} else {
if (val.is_lt(threshold)) {
val = setvalue;
}
}
rec = ErlBif.setelement(idx, rec, val);
return rec;
}
});
}
public EObject update_element(final EObject key, final ESeq upd) {
return in_tx(new WithMap<EObject>() {
@Override
protected EObject run(IPersistentMap map) {
ETuple rec = (ETuple) map.valAt(key);
if (rec == null)
return ERT.FALSE;
// TODO: figure out match/equals semantics
if (type == Native.am_set) {
if (!key.equalsExactly( get_key(rec) )) {
return ERT.FALSE;
}
}
ETuple rep = null;
for (ESeq next = upd ; !next.isNil() ; next = next.tail()) {
ETuple2 update = ETuple2.cast(next.head());
if (update == null) return null;
ESmall idx1 = update.elem1.testSmall();
if (idx1 == null
|| idx1.value < 1
|| idx1.value > rec.arity()
|| idx1.value == keypos1) return null;
if (rep == null) {
rep = rec.setelement(idx1.value, update.elem2);
} else {
rep.set(idx1.value, update.elem2);
}
}
if (rep != null) {
map = map.assoc(get_key(rec), rep);
set(map);
}
return ERT.TRUE;
}
});
}
}