/**
* RAMIndexCluster
* Copyright 2009 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany
* First released 12.03.2009 at http://yacy.net
*
* $LastChangedDate$
* $LastChangedRevision$
* $LastChangedBy$
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program in the file lgpl21.txt
* If not, see <http://www.gnu.org/licenses/>.
*/
package net.yacy.kelondro.index;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import net.yacy.cora.order.CloneableIterator;
import net.yacy.cora.storage.HandleSet;
import net.yacy.cora.util.ConcurrentLog;
import net.yacy.cora.util.SpaceExceededException;
import net.yacy.kelondro.index.Row.Entry;
import net.yacy.kelondro.util.MergeIterator;
import net.yacy.kelondro.util.StackIterator;
public final class RAMIndexCluster implements Index, Iterable<Row.Entry>, Cloneable {
private final String name;
private final Row rowdef;
private final RAMIndex[] cluster;
public RAMIndexCluster(final String name, final Row rowdef, final int clusterSize) {
//assert arraySize < 100 : arraySize;
this.name = name;
this.cluster = new RAMIndex[clusterSize];
this.rowdef = rowdef;
for (int i = 0; i < this.cluster.length; i++) {
this.cluster[i] = null; // lazy initialization, the actual initialization is at accessArray()
}
}
private RAMIndexCluster(final String name, final Row rowdef, final RAMIndex[] array) {
this.name = name;
this.cluster = array;
this.rowdef = rowdef;
}
@Override
public void optimize() {
for (final RAMIndex i: this.cluster) if (i != null) i.optimize();
}
@Override
public RAMIndexCluster clone() {
final RAMIndex[] a = new RAMIndex[this.cluster.length];
for (int i = 0; i < this.cluster.length; i++) {
a[i] = this.cluster[i] == null ? null : this.cluster[i].clone();
}
return new RAMIndexCluster(this.name + ".clone", this.rowdef, a);
}
private final int indexFor(final byte[] key) {
return (int) ((this.rowdef.objectOrder.cardinal(key) / 17) % (this.cluster.length));
}
private final int indexFor(final Entry row) {
return (int) ((this.rowdef.objectOrder.cardinal(row.bytes(), 0, row.getPrimaryKeyLength()) / 17) % (this.cluster.length));
}
@Override
public final byte[] smallestKey() {
final HandleSet keysort = new RowHandleSet(this.rowdef.primaryKeyLength, this.rowdef.objectOrder, this.cluster.length);
synchronized (this.cluster) {
for (final RAMIndex rs: this.cluster) try {
keysort.put(rs.smallestKey());
} catch (final SpaceExceededException e) {
ConcurrentLog.logException(e);
}
}
return keysort.smallestKey();
}
@Override
public final byte[] largestKey() {
final HandleSet keysort = new RowHandleSet(this.rowdef.primaryKeyLength, this.rowdef.objectOrder, this.cluster.length);
synchronized (this.cluster) {
for (final RAMIndex rs: this.cluster) try {
keysort.put(rs.largestKey());
} catch (final SpaceExceededException e) {
ConcurrentLog.logException(e);
}
}
return keysort.largestKey();
}
private final RAMIndex accessArray(final int i) {
RAMIndex r = this.cluster[i];
if (r == null) synchronized (this.cluster) {
r = this.cluster[i];
if (r == null) {
r = new RAMIndex(this.name + "." + i, this.rowdef);
this.cluster[i] = r;
}
}
return r;
}
@Override
public final void addUnique(final Entry row) throws SpaceExceededException {
final int i = indexFor(row);
assert i >= 0 : "i = " + i;
if (i < 0) return;
accessArray(i).addUnique(row);
}
public final void addUnique(final List<Entry> rows) throws SpaceExceededException {
for (final Entry row: rows) addUnique(row);
}
@Override
public final void clear() {
synchronized (this.cluster) {
for (final RAMIndex c: this.cluster) if (c != null) c.clear();
}
}
@Override
public final void close() {
synchronized (this.cluster) {
for (final RAMIndex c: this.cluster) {
if (c != null) {
//Log.logInfo("RAMIndexCluster", "Closing RAM index at " + c.getName() + " with " + c.size() + " entries ...");
c.close();
}
}
}
}
@Override
public final void deleteOnExit() {
// no nothing here
}
@Override
public final String filename() {
// we don't have a file name
return null;
}
@Override
public final Entry get(final byte[] key, final boolean forcecopy) {
final int i = indexFor(key);
if (i < 0) return null;
final RAMIndex r = this.cluster[i];
if (r == null) return null;
return r.get(key, forcecopy);
}
@Override
public Map<byte[], Row.Entry> get(final Collection<byte[]> keys, final boolean forcecopy) throws IOException, InterruptedException {
final Map<byte[], Row.Entry> map = new TreeMap<byte[], Row.Entry>(row().objectOrder);
Row.Entry entry;
for (final byte[] key: keys) {
entry = get(key, forcecopy);
if (entry != null) map.put(key, entry);
}
return map;
}
@Override
public final boolean has(final byte[] key) {
final int i = indexFor(key);
if (i < 0) return false;
final RAMIndex r = this.cluster[i];
if (r == null) return false;
return r.has(key);
}
@Override
public final CloneableIterator<byte[]> keys(final boolean up, final byte[] firstKey) {
synchronized (this.cluster) {
final Collection<CloneableIterator<byte[]>> col = new ArrayList<CloneableIterator<byte[]>>();
for (final RAMIndex element : this.cluster) {
if (element != null) {
col.add(element.keys(up, firstKey));
}
}
return MergeIterator.cascade(col, this.rowdef.objectOrder, MergeIterator.simpleMerge, up);
}
}
/**
* Adds the row to the index. The row is identified by the primary key of the row.
* @param row a index row
* @return true if this set did _not_ already contain the given row.
* @throws IOException
* @throws SpaceExceededException
*/
@Override
public final boolean put(final Entry row) throws SpaceExceededException {
final int i = indexFor(row);
assert i >= 0 : "i = " + i;
if (i < 0) return true;
return accessArray(i).put(row);
}
@Override
public final boolean delete(final byte[] key) {
final int i = indexFor(key);
if (i < 0) return false;
return accessArray(i).delete(key);
}
@Override
public final Entry remove(final byte[] key) {
final int i = indexFor(key);
if (i < 0) return null;
return accessArray(i).remove(key);
}
@Override
public final ArrayList<RowCollection> removeDoubles() throws SpaceExceededException {
final ArrayList<RowCollection> col = new ArrayList<RowCollection>();
synchronized (this.cluster) {
for (int i = 0; i < this.cluster.length; i++) {
if (this.cluster[i] != null) {
col.addAll(this.cluster[i].removeDoubles());
if (this.cluster[i].isEmpty()) this.cluster[i] = null;
}
}
}
return col;
}
@Override
public final Entry removeOne() {
synchronized (this.cluster) {
for (int i = 0; i < this.cluster.length; i++) {
if (this.cluster[i] != null) {
final Entry entry = this.cluster[i].removeOne();
if (this.cluster[i].isEmpty()) this.cluster[i] = null;
return entry;
}
}
}
return null;
}
@Override
public List<Row.Entry> top(final int count) {
final List<Row.Entry> list = new ArrayList<Row.Entry>();
synchronized (this.cluster) {
for (final RAMIndex element : this.cluster) {
if (element != null) {
try {
final List<Row.Entry> list0 = element.top(count - list.size());
list.addAll(list0);
} catch (final IOException e) {
continue;
}
}
if (list.size() >= count) return list;
}
}
return list;
}
@Override
public List<Row.Entry> random(final int count) {
final List<Row.Entry> list = new ArrayList<Row.Entry>();
synchronized (this.cluster) {
for (final RAMIndex element : this.cluster) {
if (element != null) {
try {
final List<Row.Entry> list0 = element.random(count - list.size());
list.addAll(list0);
} catch (final IOException e) {
continue;
}
}
if (list.size() >= count) return list;
}
}
return list;
}
@Override
public final Entry replace(final Entry row) throws SpaceExceededException {
final int i = indexFor(row);
assert i >= 0 : "i = " + i;
if (i < 0) return null;
return accessArray(i).replace(row);
}
@Override
public final Row row() {
return this.rowdef;
}
@Override
@SuppressWarnings("unchecked")
public final CloneableIterator<Row.Entry> rows(final boolean up, final byte[] firstKey) {
synchronized (this.cluster) {
final List<CloneableIterator<Row.Entry>> col = new ArrayList<CloneableIterator<Row.Entry>>(this.cluster.length);
for (RAMIndex element : this.cluster) {
if (element != null) {
col.add(element.rows(up, firstKey));
}
}
return StackIterator.stack(col.toArray((CloneableIterator<Row.Entry>[]) Array.newInstance(CloneableIterator.class, col.size())), getEntryComparator(), up);
}
}
private Comparator<Row.Entry> getEntryComparator() {
return new Comparator<Row.Entry>() {
@Override
public int compare(Row.Entry o1, Row.Entry o2) {
return RAMIndexCluster.this.rowdef.objectOrder.compare(o1.getPrimaryKeyBytes(), o2.getPrimaryKeyBytes());
}
};
}
@Override
public final CloneableIterator<Row.Entry> rows() {
return rows(true, null);
}
@Override
public final int size() {
int c = 0;
for (final RAMIndex i: this.cluster) if (i != null) c += i.size();
return c;
}
@Override
public long mem() {
long m = 0;
for (final RAMIndex i: this.cluster) if (i != null) m += i.mem();
return m;
}
@Override
public final boolean isEmpty() {
for (final RAMIndex i: this.cluster) if (i != null && !i.isEmpty()) return false;
return true;
}
@Override
public final Iterator<Entry> iterator() {
return this.rows(true, null);
}
public final long inc(final byte[] key, final int col, final long add, final Entry initrow) throws SpaceExceededException {
final int i = indexFor(key);
if (i < 0) return -1;
return accessArray(i).inc(key, col, add, initrow);
}
}