package org.basex.index;
import org.basex.io.IOFile;
import org.basex.io.in.DataInput;
import org.basex.io.out.DataOutput;
import org.basex.util.Array;
import org.basex.util.list.IntList;
import java.io.IOException;
import java.util.Arrays;
/**
* ID -> PRE mapping.
*
* @author BaseX Team 2005-12, BSD License
* @author Dimitar Popov
*/
public class IdPreMap {
/** 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[pres.length];
nids = new int[pres.length];
incs = new int[pres.length];
oids = new int[pres.length];
}
/**
* Construct 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 {
final DataInput in = new DataInput(f);
try {
baseid = in.readNum();
rows = in.readNum();
pres = in.readNums();
fids = in.readNums();
nids = in.readNums();
incs = in.readNums();
oids = in.readNums();
} finally {
in.close();
}
}
/**
* Write the map to the specified file.
* @param f file to write to
* @throws IOException I/O error while writing to the file
*/
public void write(final IOFile f) throws IOException {
final DataOutput out = new DataOutput(f);
try {
out.writeNum(baseid);
out.writeNum(rows);
out.writeNums(pres);
out.writeNums(fids);
out.writeNums(nids);
out.writeNums(incs);
out.writeNums(oids);
} finally {
out.close();
}
}
/**
* Find 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;
// id was inserted by update
if(id > baseid) {
// optimize if performance is low
for(int i = 0; i < rows; ++i) {
if(fids[i] == id) return pres[i];
if(fids[i] < id && id <= nids[i]) return pres[i] + id - fids[i];
}
}
// id is affected by updates
final int i = sortedLastIndexOf(oids, id);
return id + incs[i < 0 ? -i - 2 : i];
}
/**
* Find the PRE values of a given list of IDs.
* @param ids IDs
* @param off start position in ids (inclusive)
* @param len number of ids
* @return a sorted array of PRE values
*/
public int[] pre(final int[] ids, final int off, final int len) {
final IntList p = new IntList(ids.length);
for(int i = off; i < len; ++i) p.add(pre(ids[i]));
return p.sort().toArray();
}
/**
* Insert 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];
}
// apply correction to all subsequent intervals
for(int k = pos; k < rows; ++k) {
pres[k] += c;
incs[k] += c;
}
}
// add the new interval
add(pos, pre, id, id + c - 1, inc, oid);
}
/**
* Delete records.
* @param pre PRE of the first record
* @param id ID of the first deleted record
* @param c number of deleted records
*/
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;
}
int inc = c;
int oid = id;
if(rows > 0) {
final int p = pre - c - 1;
int i1 = findPre(pre);
int i2 = -c > 1 ? findPre(p) : i1;
final boolean found1 = i1 >= 0;
final boolean found2 = i2 >= 0;
if(!found1) i1 = -i1 - 1;
if(!found2) i2 = -i2 - 1;
if(i1 >= rows) {
// no other intervals are affected => append the new one
add(i1, pre, -1, -1, incs[i1 - 1] + inc, oid);
return;
}
final int min1 = pres[i1];
final int max1 = pres[i1] + nids[i1] - fids[i1];
final int min2;
final int max2;
if(i2 >= rows) {
min2 = max2 = p + 1;
} else {
min2 = pres[i2];
max2 = pres[i2] + nids[i2] - fids[i2];
}
// apply correction to all subsequent intervals
for(int k = found2 ? i2 + 1 : i2; k < rows; ++k) {
pres[k] += c;
incs[k] += c;
}
if(i1 == i2) {
// pre1 <= p <= max2
if(pre <= min1) {
if(p == max2) {
if(i2 + 1 < rows && pres[i2 + 1] == pre) {
// remove interval if the next one (already changed) is the same
remove(i1, i2);
} else {
incs[i2] += c;
pres[i2] = pre;
fids[i2] = -1;
nids[i2] = -1;
//remove(i1, i2 - 1);
}
} else if(min2 <= p && p < max2) {
incs[i2] += c;
pres[i2] = pre;
fids[i2] += p - min2 + 1;
//remove(i1, i2 - 1);
} else if(p < min2 - 1) {
// the interval is not adjacent to next one => should be added
add(i1, pre, -1, -1, i1 > 0 ? incs[i1 - 1] + c : c, oid);
}
} else if(min1 < pre) {
if(min2 < p && p < max2) {
final int fid = fids[i2] + p - min2 + 1;
add(i2 + 1, p + c + 1, fid, nids[i2], incs[i1] + c, oids[i2]);
}
final int s = max1 - pre + 1;
nids[i1] -= s;
incs[i1] -= s;
}
} else if(i1 < i2) {
// pre1 <= max1 < p <= max2
// min1 <= max1 < min2 <= max2
if(pre <= min1) {
if(p == max2) {
if(i2 + 1 < rows && pres[i2 + 1] == pre) {
// remove interval if the next one (already changed) is the same
remove(i1, i2);
} else {
incs[i2] += c;
pres[i2] = pre;
fids[i2] = -1;
nids[i2] = -1;
remove(i1, i2 - 1);
}
} else if(min2 <= p && p < max2) {
incs[i2] += c;
pres[i2] = pre;
fids[i2] += p - min2 + 1;
remove(i1, i2 - 1);
} else if(p < min2) {
if(i2 < rows && pres[i2] == pre) {
// remove interval if the next one (already changed) is the same
remove(i1, --i2);
} else {
incs[--i2] += c;
pres[i2] = pre;
fids[i2] = -1;
nids[i2] = -1;
remove(i1, i2 - 1);
}
}
} else if(min1 < pre) {
if(p == max2) {
inc += incs[i2];
oid = oids[i2];
remove(i1 + 1, i2);
nids[i1] -= max1 - pre + 1;
incs[i1] = inc;
oids[i1] = oid;
} else if(min2 <= p && p < max2) {
fids[i2] += p - min2 + 1;
incs[i2] += c;
pres[i2] = pre;
remove(i1 + 1, i2 - 1);
final int s = max1 - pre + 1;
nids[i1] -= s;
incs[i1] -= s;
} else if(p < min2) {
incs[i1] = incs[i2 - 1] + c;
remove(i1 + 1, i2 - 1);
final int s = max1 - pre + 1;
nids[i1] -= s;
}
}
}
} else {
add(0, pre, -1, -1, inc, oid);
}
}
@Override
public String toString() {
final StringBuilder b = new StringBuilder();
b.append("pres, fids, nids, incs, oids");
for(int i = 0; i < rows; i++) {
b.append('\n');
b.append(pres[i]); b.append(", ");
b.append(fids[i]); b.append(", ");
b.append(nids[i]); b.append(", ");
b.append(incs[i]); b.append(", ");
b.append(oids[i]);
}
return b.toString();
}
/**
* Search for a given pre value.
* @param pre pre value
* @return index of the record where the pre was found, or the negative
* insertion point - 1
*/
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 + 1); // key not found.
}
/**
* Binary search for 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;
}
/**
* Add 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) {
System.arraycopy(pres, i, pres, i + 1, rows - i);
System.arraycopy(fids, i, fids, i + 1, rows - i);
System.arraycopy(nids, i, nids, i + 1, rows - i);
System.arraycopy(incs, i, incs, i + 1, rows - i);
System.arraycopy(oids, i, oids, i + 1, rows - i);
}
pres[i] = pre;
fids[i] = fid;
nids[i] = nid;
incs[i] = inc;
oids[i] = oid;
++rows;
}
/**
* Remove 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) {
System.arraycopy(pres, e + 1, pres, s, rows - (e + 1));
System.arraycopy(fids, e + 1, fids, s, rows - (e + 1));
System.arraycopy(nids, e + 1, nids, s, rows - (e + 1));
System.arraycopy(incs, e + 1, incs, s, rows - (e + 1));
System.arraycopy(oids, e + 1, oids, s, rows - (e + 1));
rows -= e - s + 1;
}
}
}