/*
* 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 com.addthis.hydra.data.query;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import com.addthis.basis.util.MemoryCounter;
import com.addthis.bundle.core.Bundle;
import com.addthis.bundle.core.BundleField;
import com.addthis.bundle.core.BundleFormat;
import com.addthis.bundle.table.DataTable;
import com.addthis.bundle.table.DataTableFactory;
import com.addthis.bundle.value.ValueObject;
import com.google.common.collect.ForwardingList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An implementation of DataTable that switches the underlying DataTable object
* based on performance and resource constraints. Never used directly outside of
* this package. TODO when not tipped keep a hash of added objects to prevent
* double-counting TODO which can occur when 'depivot'ing a data set, for
* example (row/col keys).
*/
public class ResultTableTuned extends ForwardingList<Bundle> implements DataTable, DataTableFactory {
private static final Random random = new Random(System.currentTimeMillis());
private static final Logger log = LoggerFactory.getLogger(ResultTableTuned.class);
private DataTable result;
private DataTableFactory factory;
private File tempDir;
private boolean tipped;
private boolean cantip;
private long memTip;
private long rowTip;
private int cells;
private long estMem;
protected ResultTableTuned(File tempDir,
long rowtip,
long memtip,
DataTableFactory factory,
int sizeHint) throws IOException {
this.tempDir = tempDir;
this.memTip = memtip;
this.rowTip = rowtip;
this.factory = factory;
this.cantip = tempDir != null && tempDir.exists() && tempDir.isDirectory() &&
((rowtip > 0) || (memtip > 0));
this.tipped = !cantip;
log.debug("creating RAT temp={}, rowTip={}, memTip={}, cantip={}, tipped={}", tempDir,
rowtip, memTip, cantip, tipped);
if (cantip && (sizeHint >= rowtip) && (rowtip > 0)) {
File tmp = createTempFile();
tipped = true;
result = new ResultTableDisk(factory, tmp);
if (log.isDebugEnabled()) {
log.debug(hashCode() + " creating result disk backed to " + tmp + " tip=" + rowtip + " sizeHint=" + sizeHint);
}
} else {
result = new ResultTable(factory, sizeHint * 2);
}
}
@Override protected List<Bundle> delegate() {
return result;
}
@Override
public String toString() {
return "(RAT:" + (cantip ? "cantip" : "notip") + ":rt=" + rowTip + ":mt=" + memTip + ":" + result + ")";
}
protected void cleanup() {
if (result instanceof ResultTableDisk) {
((ResultTableDisk) result).delete();
}
}
private void estimateRow(Bundle row) {
for (BundleField f : row.getFormat()) {
ValueObject qv = row.getValue(f);
if (qv != null) {
cells++;
}
}
if (memTip > 0) {
estMem += MemoryCounter.estimateSize(row);
}
}
private File createTempFile() {
return new File(tempDir, "result." + Long.toHexString(random.nextLong()) + ".tmp");
}
private void tipCheck() {
if (!tipped && ((rowTip > 0 && result.size() > rowTip) || (memTip > 0 && estMem > memTip))) {
try {
File tmp = createTempFile();
if (log.isDebugEnabled()) {
log.debug(hashCode() + " tipping to " + tmp + " @ rows=" + result.size() + " cells=" + cells + " mem=" + estMem);
}
ResultTableDisk dbl = new ResultTableDisk(factory, tmp);
dbl.append(result);
result = dbl;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
tipped = true;
}
}
@Override
public DataTable createTable(int sizeHint) {
return factory.createTable(sizeHint);
}
@Override public boolean add(Bundle element) {
result.add(element);
if (!tipped) {
estimateRow(element);
}
tipCheck();
return true;
}
@Override public void add(int index, Bundle element) {
result.add(index, element);
if (!tipped) {
estimateRow(element);
}
tipCheck();
}
@Override public boolean addAll(Collection<? extends Bundle> collection) {
return standardAddAll(collection);
}
@Override public boolean addAll(int index, Collection<? extends Bundle> collection) {
return standardAddAll(index, collection);
}
@Override
public Bundle createBundle() {
return result.createBundle();
}
@Override
public BundleFormat getFormat() {
return result.getFormat();
}
@Override
public void sort(Comparator<? super Bundle> comp) {
result.sort(comp);
}
// DEPRECATED METHODS
@Override public void append(Bundle row) {
this.add(row);
}
@Override public void insert(int index, Bundle row) {
this.add(index, row);
}
@Override public void append(DataTable result) {
this.addAll(result);
}
}