/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* 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.asakusafw.directio.hive.parquet;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;
import com.asakusafw.directio.hive.util.TemporalUtil;
import com.asakusafw.runtime.value.DateOption;
import com.asakusafw.runtime.value.DateTimeOption;
import com.asakusafw.runtime.value.DateUtil;
import com.asakusafw.runtime.value.ValueOption;
import parquet.column.Dictionary;
import parquet.io.api.Binary;
import parquet.io.api.RecordConsumer;
import parquet.schema.PrimitiveType;
import parquet.schema.PrimitiveType.PrimitiveTypeName;
import parquet.schema.Type;
import parquet.schema.Type.Repetition;
/**
* Converts between {@link ValueOption} and {@code timestamp (binary)}.
* @since 0.7.2
*/
public enum TimestampValueDrivers implements ParquetValueDriver {
/**
* {@link DateOption}.
*/
DATE(DateOption.class) {
@Override
public ValueConverter getConverter() {
return new DateConverter();
}
@Override
public ValueWriter getWriter() {
return new DateWriter();
}
},
/**
* {@link DateTimeOption}.
*/
DATETIME(DateTimeOption.class) {
@Override
public ValueConverter getConverter() {
return new DateTimeConverter();
}
@Override
public ValueWriter getWriter() {
return new DateTimeWriter();
}
},
;
final Class<? extends ValueOption<?>> valueOptionClass;
TimestampValueDrivers(Class<? extends ValueOption<?>> valueOptionClass) {
this.valueOptionClass = valueOptionClass;
}
@Override
public Type getType(String name) {
return new PrimitiveType(Repetition.OPTIONAL, PrimitiveTypeName.INT96, name);
}
/**
* Returns a {@link ParquetValueDriver} for the specified type.
* @param valueClass the {@link ValueOption} type
* @return the corresponded {@link ParquetValueDriver}, or {@code null} if it is not found
*/
public static ParquetValueDriver find(Class<?> valueClass) {
return Lazy.FROM_CLASS.get(valueClass);
}
private static final class Lazy {
static final Map<Class<?>, TimestampValueDrivers> FROM_CLASS;
static {
Map<Class<?>, TimestampValueDrivers> map = new HashMap<>();
for (TimestampValueDrivers element : TimestampValueDrivers.values()) {
map.put(element.valueOptionClass, element);
}
FROM_CLASS = map;
}
private Lazy() {
return;
}
}
abstract static class AbstractWriter implements ValueWriter {
void write(int julianDay, long timeOfDayNanos, RecordConsumer consumer) {
ByteBuffer buf = ByteBuffer.allocate(12).order(ByteOrder.LITTLE_ENDIAN);
buf.clear();
buf.putLong(timeOfDayNanos);
buf.putInt(julianDay);
buf.flip();
consumer.addBinary(Binary.fromByteBuffer(buf));
}
}
static class DateWriter extends AbstractWriter {
@Override
public void write(Object value, RecordConsumer consumer) {
DateOption option = (DateOption) value;
int julianDayNumber = TemporalUtil.getJulianDayNumber(option.get());
long nanoTime = TemporalUtil.getTimeOfDayNanos(option.get());
write(julianDayNumber, nanoTime, consumer);
}
}
static class DateTimeWriter extends AbstractWriter {
@Override
public void write(Object value, RecordConsumer consumer) {
DateTimeOption option = (DateTimeOption) value;
int julianDayNumber = TemporalUtil.getJulianDayNumber(option.get());
long nanoTime = TemporalUtil.getTimeOfDayNanos(option.get());
write(julianDayNumber, nanoTime, consumer);
}
}
abstract static class AbstractConverter extends ValueConverter {
private int[] julianDays;
private long[] nanoTimes;
protected AbstractConverter() {
return;
}
@Override
public boolean hasDictionarySupport() {
return true;
}
@Override
public void setDictionary(Dictionary dictionary) {
int size = dictionary.getMaxId() + 1;
if (this.julianDays == null || this.julianDays.length < size) {
int capacity = (int) (size * 1.2) + 1;
this.julianDays = new int[capacity];
this.nanoTimes = new long[capacity];
}
for (int id = 0, max = dictionary.getMaxId(); id <= max; id++) {
ByteBuffer bytes = dictionary.decodeToBinary(id).toByteBuffer().order(ByteOrder.LITTLE_ENDIAN);
long time = bytes.getLong();
int day = bytes.getInt();
julianDays[id] = day;
nanoTimes[id] = time;
}
}
@Override
public void addValueFromDictionary(int dictionaryId) {
addNanoTime(julianDays[dictionaryId], nanoTimes[dictionaryId]);
}
@Override
public void addBinary(Binary value) {
ByteBuffer bytes = value.toByteBuffer().order(ByteOrder.LITTLE_ENDIAN);
long time = bytes.getLong();
int day = bytes.getInt();
addNanoTime(day, time);
}
abstract void addNanoTime(int julianDay, long nanoTime);
}
static class DateConverter extends AbstractConverter {
private DateOption target;
@Override
public void set(ValueOption<?> value) {
this.target = (DateOption) value;
}
@SuppressWarnings("deprecation")
@Override
void addNanoTime(int julianDay, long nanoTime) {
long seconds = TemporalUtil.toElapsedSeconds(julianDay, nanoTime);
target.modify(DateUtil.getDayFromSeconds(seconds));
}
}
static class DateTimeConverter extends AbstractConverter {
private DateTimeOption target;
@Override
public void set(ValueOption<?> value) {
this.target = (DateTimeOption) value;
}
@SuppressWarnings("deprecation")
@Override
void addNanoTime(int julianDay, long nanoTime) {
target.modify(TemporalUtil.toElapsedSeconds(julianDay, nanoTime));
}
}
}