blob: 5eb92aad47122c525c7067d7f3ca4d85a1b9b1cd [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.trevni;
import java.io.IOException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Set;
import java.util.HashSet;
/** Writes data to a column file.
* All data is buffered until {@link #writeTo(File)} is called.
*/
public class ColumnFileWriter {
static final byte[] MAGIC_0 = new byte[] {'T', 'r', 'v', 0};
static final byte[] MAGIC_1 = new byte[] {'T', 'r', 'v', 1};
static final byte[] MAGIC = new byte[] {'T', 'r', 'v', 2};
private ColumnFileMetaData metaData;
private ColumnOutputBuffer[] columns;
private long rowCount;
private int columnCount;
private long size;
/** Construct given metadata for each column in the file. */
public ColumnFileWriter(ColumnFileMetaData fileMeta,
ColumnMetaData... columnMeta) throws IOException {
checkColumns(columnMeta);
this.metaData = fileMeta;
this.columnCount = columnMeta.length;
this.columns = new ColumnOutputBuffer[columnCount];
for (int i = 0; i < columnCount; i++) {
ColumnMetaData c = columnMeta[i];
c.setDefaults(metaData);
columns[i] = c.isArray()
? new ArrayColumnOutputBuffer(this, c)
: new ColumnOutputBuffer(this, c);
size += OutputBuffer.BLOCK_SIZE; // over-estimate
}
}
private void checkColumns(ColumnMetaData[] columnMeta) {
Set<String> seen = new HashSet<String>();
for (int i = 0; i < columnMeta.length; i++) {
ColumnMetaData c = columnMeta[i];
String name = c.getName();
if (seen.contains(name))
throw new TrevniRuntimeException("Duplicate column name: "+name);
ColumnMetaData parent = c.getParent();
if (parent != null && !seen.contains(parent.getName()))
throw new TrevniRuntimeException("Parent must precede child: "+name);
seen.add(name);
}
}
void incrementSize(int n) { size += n; }
/** Return the approximate size of the file that will be written. Tries to
* slightly over-estimate. Indicates both the size in memory of the buffered
* data as well as the size of the file that will be written by {@link
* #writeTo(OutputStream)}. */
public long sizeEstimate() { return size; }
/** Return this file's metadata. */
public ColumnFileMetaData getMetaData() { return metaData; }
/** Return the number of columns in the file. */
public int getColumnCount() { return columnCount; }
/** Add a row to the file. */
public void writeRow(Object... row) throws IOException {
startRow();
for (int column = 0; column < columnCount; column++)
writeValue(row[column], column);
endRow();
}
/** Expert: Called before any values are written to a row. */
public void startRow() throws IOException {
for (int column = 0; column < columnCount; column++)
columns[column].startRow();
}
/** Expert: Declare a count of items to be written to an array column or a
* column whose parent is an array. */
public void writeLength(int length, int column) throws IOException {
columns[column].writeLength(length);
}
/** Expert: Add a value to a row. For values in array columns or whose
* parents are array columns, this must be preceded by a call to {@link
* #writeLength(int, int)} and must be called that many times. For normal
* columns this is called once for each row in the column. */
public void writeValue(Object value, int column) throws IOException {
columns[column].writeValue(value);
}
/** Expert: Called after all values are written to a row. */
public void endRow() throws IOException {
for (int column = 0; column < columnCount; column++)
columns[column].endRow();
rowCount++;
}
/** Write all rows added to the named file. */
public void writeTo(File file) throws IOException {
OutputStream out = new FileOutputStream(file);
try {
writeTo(out);
} finally {
out.close();
}
}
/** Write all rows added to the named output stream. */
public void writeTo(OutputStream out) throws IOException {
writeHeader(out);
for (int column = 0; column < columnCount; column++)
columns[column].writeTo(out);
}
private void writeHeader(OutputStream out) throws IOException {
OutputBuffer header = new OutputBuffer();
header.write(MAGIC); // magic
header.writeFixed64(rowCount); // row count
header.writeFixed32(columnCount); // column count
metaData.write(header); // file metadata
for (ColumnOutputBuffer column : columns)
column.getMeta().write(header); // column metadata
for (long start : computeStarts(header.size()))
header.writeFixed64(start); // column starts
header.writeTo(out);
}
private long[] computeStarts(long start) throws IOException {
long[] result = new long[columnCount];
start += columnCount * 8; // room for starts
for (int column = 0; column < columnCount; column++) {
result[column] = start;
start += columns[column].size();
}
return result;
}
}