/**
* Copyright 2011-2014 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.math.BigDecimal;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import parquet.column.Dictionary;
import parquet.io.api.Binary;
import parquet.io.api.RecordConsumer;
import parquet.schema.OriginalType;
import parquet.schema.PrimitiveType;
import parquet.schema.PrimitiveType.PrimitiveTypeName;
import parquet.schema.Type;
import parquet.schema.Type.Repetition;
import com.asakusafw.directio.hive.util.TemporalUtil;
import com.asakusafw.runtime.value.DateOption;
import com.asakusafw.runtime.value.DateTimeOption;
import com.asakusafw.runtime.value.DecimalOption;
import com.asakusafw.runtime.value.ValueOption;
/**
* Converts between {@link ValueOption} and {@code string (binary)}.
* @since 0.7.0
*/
public enum StringValueDrivers implements ParquetValueDriver {
/**
* {@link DecimalOption}.
*/
DECIMAL(DecimalOption.class) {
@Override
public ValueConverter getConverter() {
return new ToDecimalOption();
}
@Override
public ValueWriter getWriter() {
return new FromDecimalOption();
}
},
/**
* {@link DateOption}.
*/
DATE(DateOption.class) {
@Override
public ValueConverter getConverter() {
return new ToDateOption();
}
@Override
public ValueWriter getWriter() {
return new FromDateOption();
}
},
/**
* {@link DateTimeOption}.
*/
DATETIME(DateTimeOption.class) {
@Override
public ValueConverter getConverter() {
return new ToDateTimeOption();
}
@Override
public ValueWriter getWriter() {
return new FromDateTimeOption();
}
},
;
final Class<? extends ValueOption<?>> valueOptionClass;
private StringValueDrivers(Class<? extends ValueOption<?>> valueOptionClass) {
this.valueOptionClass = valueOptionClass;
}
@Override
public Type getType(String name) {
return new PrimitiveType(Repetition.OPTIONAL, PrimitiveTypeName.BINARY, name, OriginalType.UTF8);
}
/**
* 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<?>, StringValueDrivers> FROM_CLASS;
static {
Map<Class<?>, StringValueDrivers> map = new HashMap<Class<?>, StringValueDrivers>();
for (StringValueDrivers element : StringValueDrivers.values()) {
map.put(element.valueOptionClass, element);
}
FROM_CLASS = map;
}
private Lazy() {
return;
}
}
abstract static class AbstractWriter implements ValueWriter {
protected abstract String toString(Object value);
@Override
public void write(Object value, RecordConsumer consumer) {
consumer.addBinary(Binary.fromString(toString(value)));
}
}
abstract static class AbstractConverter<T> extends ValueConverter {
private T[] dict;
protected AbstractConverter() {
return;
}
protected abstract T parse(String value);
protected abstract void drive(T value);
@Override
public boolean hasDictionarySupport() {
return true;
}
@Override
public void setDictionary(Dictionary dictionary) {
T[] buf = prepareDictionaryBuffer(dictionary);
for (int id = 0, max = dictionary.getMaxId(); id <= max; id++) {
buf[id] = parse(dictionary.decodeToBinary(id).toStringUsingUTF8());
}
}
@Override
public void addValueFromDictionary(int dictionaryId) {
drive(dict[dictionaryId]);
}
@Override
public void addBinary(Binary value) {
drive(parse(value.toStringUsingUTF8()));
}
@SuppressWarnings("unchecked")
private T[] prepareDictionaryBuffer(Dictionary dictionary) {
int size = dictionary.getMaxId() + 1;
if (this.dict == null || this.dict.length < size) {
int capacity = (int) (size * 1.2) + 1;
this.dict = (T[]) new Object[capacity];
} else {
Arrays.fill(this.dict, null);
}
return this.dict;
}
}
abstract static class AbstractIntConverter extends ValueConverter {
private int[] dict;
protected AbstractIntConverter() {
return;
}
protected abstract int parse(String value);
protected abstract void drive(int value);
@Override
public boolean hasDictionarySupport() {
return true;
}
@Override
public void setDictionary(Dictionary dictionary) {
int[] buf = prepareDictionaryBuffer(dictionary);
for (int id = 0, max = dictionary.getMaxId(); id <= max; id++) {
buf[id] = parse(dictionary.decodeToBinary(id).toStringUsingUTF8());
}
}
@Override
public void addValueFromDictionary(int dictionaryId) {
drive(dict[dictionaryId]);
}
@Override
public void addBinary(Binary value) {
drive(parse(value.toStringUsingUTF8()));
}
private int[] prepareDictionaryBuffer(Dictionary dictionary) {
int size = dictionary.getMaxId() + 1;
if (this.dict == null || this.dict.length < size) {
int capacity = (int) (size * 1.2) + 1;
this.dict = new int[capacity];
} else {
Arrays.fill(this.dict, -1);
}
return this.dict;
}
}
abstract static class AbstractLongConverter extends ValueConverter {
private long[] dict;
protected AbstractLongConverter() {
return;
}
protected abstract long parse(String value);
protected abstract void drive(long value);
@Override
public boolean hasDictionarySupport() {
return true;
}
@Override
public void setDictionary(Dictionary dictionary) {
long[] buf = prepareDictionaryBuffer(dictionary);
for (int id = 0, max = dictionary.getMaxId(); id <= max; id++) {
buf[id] = parse(dictionary.decodeToBinary(id).toStringUsingUTF8());
}
}
@Override
public void addValueFromDictionary(int dictionaryId) {
drive(dict[dictionaryId]);
}
@Override
public void addBinary(Binary value) {
drive(parse(value.toStringUsingUTF8()));
}
private long[] prepareDictionaryBuffer(Dictionary dictionary) {
int size = dictionary.getMaxId() + 1;
if (this.dict == null || this.dict.length < size) {
int capacity = (int) (size * 1.2) + 1;
this.dict = new long[capacity];
} else {
Arrays.fill(this.dict, -1L);
}
return this.dict;
}
}
static final class FromDecimalOption extends AbstractWriter {
@Override
protected String toString(Object value) {
DecimalOption option = (DecimalOption) value;
return option.get().toPlainString();
}
}
static final class ToDecimalOption extends AbstractConverter<BigDecimal> {
private DecimalOption target;
@Override
public void set(ValueOption<?> value) {
this.target = (DecimalOption) value;
}
@SuppressWarnings("deprecation")
@Override
protected void drive(BigDecimal value) {
target.modify(value);
}
@Override
protected BigDecimal parse(String value) {
return new BigDecimal(value);
}
}
static final class FromDateOption extends AbstractWriter {
@Override
protected String toString(Object value) {
DateOption option = (DateOption) value;
return TemporalUtil.toDateString(option.get().getElapsedDays());
}
}
static final class ToDateOption extends AbstractIntConverter {
private DateOption target;
@Override
public void set(ValueOption<?> value) {
this.target = (DateOption) value;
}
@SuppressWarnings("deprecation")
@Override
protected void drive(int value) {
target.modify(value);
}
@Override
protected int parse(String value) {
int days = TemporalUtil.parseDate(value);
if (days < 0) {
throw new IllegalArgumentException(MessageFormat.format(
"Invalid date string: {0}",
value));
}
return days;
}
}
static final class FromDateTimeOption extends AbstractWriter {
@Override
protected String toString(Object value) {
DateTimeOption option = (DateTimeOption) value;
return TemporalUtil.toTimestampString(option.get().getElapsedSeconds());
}
}
static final class ToDateTimeOption extends AbstractLongConverter {
private DateTimeOption target;
@Override
public void set(ValueOption<?> value) {
this.target = (DateTimeOption) value;
}
@SuppressWarnings("deprecation")
@Override
protected void drive(long value) {
target.modify(value);
}
@Override
protected long parse(String value) {
long seconds = TemporalUtil.parseTimestamp(value);
if (seconds < 0) {
throw new IllegalArgumentException(MessageFormat.format(
"Invalid timestamp string: {0}",
value));
}
return seconds;
}
}
}