package org.basex.index;
import java.io.*;
import java.util.*;
import org.basex.io.*;
import org.basex.io.in.DataInput;
import org.basex.io.out.DataOutput;
import org.basex.util.*;
import org.basex.util.list.*;
/**
* Updatable ID -> PRE mapping.
*
* @author BaseX Team 2005-17, BSD License
* @author Dimitar Popov
*/
public class IdPreMap {
/** Invalid id-value. */
private static final int INV = -1;
/** Base ID value. */
private int baseid;
/** PRE values of the inserted/deleted IDs. */
private int[] pres;
/** Inserted first ID values. */
private int[] fids;
/** Inserted last ID values. */
private int[] nids;
/** Increments showing how the PRE values have been modified. */
private int[] incs;
/** ID values for the PRE, before inserting/deleting a record. */
private int[] oids;
/** Number of records in the table. */
private int rows;
/**
* Constructor.
* @param id last inserted ID
*/
public IdPreMap(final int id) {
baseid = id;
pres = new int[1];
fids = new int[1];
nids = new int[1];
incs = new int[1];
oids = new int[1];
}
/**
* Constructs a map by reading it from a file.
* @param f file to read from
* @throws IOException I/O error while reading from the file
*/
public IdPreMap(final IOFile f) throws IOException {
try(DataInput in = new DataInput(f)) {
baseid = in.readNum();
rows = in.readNum();
pres = in.readNums();
fids = in.readNums();
nids = in.readNums();
incs = in.readNums();
oids = in.readNums();
}
}
/**
* Write the map to the specified file.
* @param file file to write to
* @throws IOException I/O error while writing to the file
*/
public final void write(final IOFile file) throws IOException {
try(DataOutput out = new DataOutput(file)) {
out.writeNum(baseid);
out.writeNum(rows);
out.writeNums(pres);
out.writeNums(fids);
out.writeNums(nids);
out.writeNums(incs);
out.writeNums(oids);
}
}
/**
* Finishes database creation.
* @param base last id
*/
public final void finish(final int base) {
baseid = base;
}
/**
* Finds the PRE value of a given ID.
* @param id ID
* @return PRE or -1 if the ID is already deleted
*/
public int pre(final int id) {
// no updates or id is not affected by updates
if(rows == 0 || id < pres[0]) return id;
if(id > baseid) {
// id was inserted by update
for(int i = 0; i < rows; ++i) {
// if(fids[i] == id) return pres[i]; // is this optimization?
if(fids[i] <= id && id <= nids[i]) return pres[i] + id - fids[i];
}
} else {
// id is affected by updates
final int i = sortedLastIndexOf(oids, id);
return id + incs[i < 0 ? -i - 2 : i];
}
return -1;
}
/**
* Inserts a new record.
* @param pre record PRE
* @param id record ID
* @param c number of inserted records
*/
public void insert(final int pre, final int id, final int c) {
if(rows == 0 && pre == id && id == baseid + 1) {
// no mapping and we append at the end => nothing to do
baseid += c;
return;
}
int pos = 0;
int inc = c;
int oid = pre;
if(rows > 0) {
pos = Arrays.binarySearch(pres, 0, rows, pre);
if(pos < 0) {
pos = -pos - 1;
if(pos != 0) {
// check if inserting into an existing id interval
final int prev = pos - 1;
final int prevcnt = nids[prev] - fids[prev] + 1;
final int prevpre = pres[prev];
if(pre < prevpre + prevcnt) {
// split the id interval
final int split = pre - prevpre;
final int fid = fids[prev] + split;
// add a new next interval
add(pos, pre, fid, nids[prev], incs[prev], oids[prev]);
// shrink the previous interval
nids[prev] = fid - 1;
incs[prev] -= prevcnt - split;
oid = oids[prev];
inc += incs[prev];
} else {
oid = pre - incs[prev];
inc += incs[prev];
}
}
} else if(pos > 0) {
oid = oids[pos];
inc += incs[pos - 1];
}
increment(pos, c);
}
// add the new interval
add(pos, pre, id, id + c - 1, inc, oid);
}
/**
* Deletes records.
* @param pre PRE of the first record
* @param id ID of the first deleted record
* @param c number of deleted records (negative)
*/
public void delete(final int pre, final int id, final int c) {
if(rows == 0 && pre == id && id - c == baseid + 1) {
// no mapping and we delete at the end => nothing to do
baseid += c;
return;
}
if(rows == 0) {
// no previous updates: add a new record
add(0, pre, INV, INV, c, id);
return;
}
final int end = pre - c - 1;
final int startIndex = findPre(pre);
// remove all updates which has affected records which now have to be deleted
final int removeStart = startIndex < rows && pres[startIndex] < pre ?
startIndex + 1 : startIndex;
int removeEnd = -1;
for(int i = startIndex; i < rows; ++i) {
if(end < pres[i] + nids[i] - fids[i]) break;
removeEnd = i;
}
final int inc;
final int oid;
int endIndex;
if(removeEnd >= 0) {
inc = incs[removeEnd];
oid = oids[removeEnd];
endIndex = removeStart;
remove(removeStart, removeEnd);
} else {
inc = startIndex > 0 ? incs[startIndex - 1] : 0;
oid = id;
endIndex = startIndex;
}
if(rows <= startIndex) {
// the delete does not affect previous updates
add(startIndex, pre, INV, INV, inc + c, oid);
return;
}
final int min = pres[startIndex];
if(startIndex < endIndex) {
if(endIndex < rows && pres[endIndex] <= end) {
shrinkFromStart(endIndex, pre, c);
shrinkFromEnd(startIndex, pre, inc + c);
} else {
--endIndex; // endIndex is not processed, so we let the increment do that
shrinkFromEnd(startIndex, pre, inc + c);
}
} else if(min < pre) {
add(++endIndex, pres[startIndex], fids[startIndex], nids[startIndex],
incs[startIndex], oids[startIndex]);
shrinkFromStart(endIndex, pre, c);
shrinkFromEnd(startIndex, pre, inc + c);
} else if(end < min) {
add(endIndex, pre, INV, INV, inc + c, oid);
} else {
shrinkFromStart(startIndex, pre, c);
}
increment(endIndex + 1, c);
}
/**
* Shrinks the given tuple from the start.
* @param i index of the tuple
* @param pre pre-value
* @param c number of deleted records (negative number)
*/
private void shrinkFromStart(final int i, final int pre, final int c) {
incs[i] += c;
fids[i] += pre - c - pres[i];
pres[i] = pre;
}
/**
* Shrinks the given tuple from the end.
* @param i index of the tuple
* @param pre pre-value
* @param inc new inc-value
*/
private void shrinkFromEnd(final int i, final int pre, final int inc) {
nids[i] = fids[i] + pre - pres[i] - 1;
incs[i] = inc;
}
/**
* Increments the pre- and inc-values of all tuples starting from the given index.
* @param from start index
* @param with increment value
*/
private void increment(final int from, final int with) {
for(int i = from; i < rows; ++i) {
pres[i] += with;
incs[i] += with;
}
}
/**
* Returns the size of the map.
* @return number of stored tuples.
*/
public int size() {
return rows;
}
/**
* Searches for a given pre value.
* @param pre pre value
* @return index of the record where the pre is found, or the insertion point if not found
*/
private int findPre(final int pre) {
int low = 0;
int high = rows - 1;
while(low <= high) {
final int mid = low + high >>> 1;
final int midValMin = pres[mid];
final int midValMax = midValMin + nids[mid] - fids[mid];
if(midValMax < pre) low = mid + 1;
else if(midValMin > pre) high = mid - 1;
else return mid; // key found
}
return low; // key not found.
}
/**
* Binary search of a key in a list. If there are several hits the last one is returned.
* @param a array to search into
* @param e key to search for
* @return index of the found hit or where the key ought to be inserted
*/
private int sortedLastIndexOf(final int[] a, final int e) {
int i = Arrays.binarySearch(a, 0, rows, e);
if(i >= 0) {
while(++i < rows && a[i] == e);
return i - 1;
}
return i;
}
/**
* Adds a record to the table and the ID index.
* @param i index in the table where the record should be inserted
* @param pre pre value
* @param fid first ID value
* @param nid last ID value
* @param inc increment value
* @param oid original ID value
*/
private void add(final int i, final int pre, final int fid, final int nid,
final int inc, final int oid) {
if(rows == pres.length) {
final int s = Array.newSize(rows);
pres = Arrays.copyOf(pres, s);
fids = Arrays.copyOf(fids, s);
nids = Arrays.copyOf(nids, s);
incs = Arrays.copyOf(incs, s);
oids = Arrays.copyOf(oids, s);
}
if(i < rows) {
final int destPos = i + 1;
final int length = rows - i;
System.arraycopy(pres, i, pres, destPos, length);
System.arraycopy(fids, i, fids, destPos, length);
System.arraycopy(nids, i, nids, destPos, length);
System.arraycopy(incs, i, incs, destPos, length);
System.arraycopy(oids, i, oids, destPos, length);
}
pres[i] = pre;
fids[i] = fid;
nids[i] = nid;
incs[i] = inc;
oids[i] = oid;
++rows;
}
/**
* Removes a records from the table and the ID index.
* @param s start index of records in the table (inclusive)
* @param e end index of records in the table (inclusive)
*/
private void remove(final int s, final int e) {
if(s <= e) {
final int last = e + 1;
final int length = rows - last;
System.arraycopy(pres, last, pres, s, length);
System.arraycopy(fids, last, fids, s, length);
System.arraycopy(nids, last, nids, s, length);
System.arraycopy(incs, last, incs, s, length);
System.arraycopy(oids, last, oids, s, length);
rows -= last - s;
}
}
@Override
public String toString() {
final Table t = new Table();
t.header.add("PRE").add("FID").add("NID").add("INC").add("OID");
for(int i = 0; i < 5; ++i) t.align.add(true);
for(int i = 0; i < rows; i++) {
final TokenList tl = new TokenList();
tl.add(pres[i]).add(fids[i]).add(nids[i]).add(incs[i]).add(oids[i]);
t.contents.add(tl);
}
return t + "\n- BaseID: " + baseid + '\n';
}
}