/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.elasticsearch.hadoop.serialization.builder;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.elasticsearch.hadoop.cfg.Settings;
import org.elasticsearch.hadoop.serialization.FieldType;
import org.elasticsearch.hadoop.serialization.Parser;
import org.elasticsearch.hadoop.serialization.Parser.Token;
import org.elasticsearch.hadoop.serialization.SettingsAware;
import org.elasticsearch.hadoop.serialization.field.FieldFilter;
import org.elasticsearch.hadoop.serialization.field.FieldFilter.NumberedInclude;
import org.elasticsearch.hadoop.serialization.field.FieldFilter.Result;
import org.elasticsearch.hadoop.util.DateUtils;
import org.elasticsearch.hadoop.util.SettingsUtils;
import org.elasticsearch.hadoop.util.StringUtils;
import org.elasticsearch.hadoop.util.unit.Booleans;
/**
* Basic value reader handling using the implied JSON type.
*/
public class JdkValueReader implements SettingsAware, ValueReader {
private boolean emptyAsNull = true;
private boolean richDate = true;
protected Collection<NumberedInclude> arrayInclude = Collections.<NumberedInclude> emptyList();
protected Collection<String> arrayExclude = Collections.emptyList();
protected String currentFieldName;
protected int nestedArrayLevel = 0;
@Override
public Object readValue(Parser parser, String value, FieldType esType) {
if (esType == null) {
return nullValue();
}
switch (esType) {
case NULL:
return nullValue();
case STRING:
case TEXT:
case KEYWORD:
return textValue(value);
case BYTE:
return byteValue(value, parser);
case SHORT:
return shortValue(value, parser);
case INTEGER:
return intValue(value, parser);
case TOKEN_COUNT:
case LONG:
return longValue(value, parser);
case HALF_FLOAT:
case SCALED_FLOAT:
case FLOAT:
return floatValue(value, parser);
case DOUBLE:
return doubleValue(value, parser);
case BOOLEAN:
return booleanValue(value, parser);
case BINARY:
byte[] binValue = parser.binaryValue();
if(binValue == null) binValue = value.getBytes();
return binaryValue(binValue);
case DATE:
return date(value, parser);
// catch-all - exists really for the other custom types that might be introduced
// compound types should have been handled earlier in the stream
default:
return textValue(value);
}
}
@Override
public Object createMap() {
return new LinkedHashMap<Object, Object>();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public void addToMap(Object map, Object key, Object value) {
((Map) map).put(key, (value != null ? value : nullValue()));
}
@Override
public Object createArray(FieldType type) {
nestedArrayLevel++;
// no need to create a collection, we'll just reuse the one passed to #addToArray
return Collections.emptyList();
}
@Override
public Object addToArray(Object array, List<Object> value) {
nestedArrayLevel--;
array = value;
// outer most array (a multi level array might be defined)
if (nestedArrayLevel == 0) {
Result result = FieldFilter.filter(currentFieldName, arrayInclude, arrayExclude);
if (result.matched && result.depth > 1) {
int actualDepth = arrayDepth(value);
int extraDepth = result.depth - actualDepth;
if (extraDepth > 0) {
array = wrapArray(array, extraDepth);
}
}
}
return array;
}
protected int arrayDepth(Object potentialArray) {
int depth = 0;
for (; potentialArray instanceof List; ) {
depth++;
List col = (List) potentialArray;
if (col.size() > 0) {
potentialArray = col.get(0);
}
}
return depth;
}
protected Object wrapArray(Object array, int extraDepth) {
for (int i = 0; i < extraDepth; i++) {
array = Arrays.asList(array);
}
return array;
}
@Override
public Object wrapString(String value) {
return textValue(value);
}
@Override
public void beginField(String fieldName) {
currentFieldName = fieldName;
}
@Override
public void endField(String fieldName) {
currentFieldName = null;
}
private boolean isEmpty(String value) {
return value.length() == 0 && emptyAsNull;
}
protected Object binaryValue(byte[] value) {
return value;
}
protected Object booleanValue(String value, Parser parser) {
Boolean val = null;
if (value == null) {
return nullValue();
}
else {
Token tk = parser.currentToken();
if (tk == Token.VALUE_NULL) {
return nullValue();
}
if (tk == Token.VALUE_BOOLEAN) {
val = parser.booleanValue();
} else if (tk == Token.VALUE_NUMBER) {
val = parser.intValue() != 0;
}
else {
val = parseBoolean(value);
}
}
return processBoolean(val);
}
protected Boolean parseBoolean(String value) {
return Booleans.parseBoolean(value);
}
protected Object processBoolean(Boolean value) {
return value;
}
protected Object doubleValue(String value, Parser parser) {
Double val = null;
if (value == null || isEmpty(value)) {
return nullValue();
}
else {
Token tk = parser.currentToken();
if (tk == Token.VALUE_NUMBER) {
val = parser.doubleValue();
}
else {
val = parseDouble(value);
}
}
return processDouble(val);
}
protected Double parseDouble(String value) {
return Double.parseDouble(value);
}
protected Object processDouble(Double value) {
return value;
}
protected Object floatValue(String value, Parser parser) {
Float val = null;
if (value == null || isEmpty(value)) {
return nullValue();
}
else {
Token tk = parser.currentToken();
if (tk == Token.VALUE_NUMBER) {
val = parser.floatValue();
}
else {
val = parseFloat(value);
}
}
return processFloat(val);
}
protected Float parseFloat(String value) {
return Float.parseFloat(value);
}
protected Object processFloat(Float value) {
return value;
}
protected Object longValue(String value, Parser parser) {
Long val = null;
if (value == null || isEmpty(value)) {
return nullValue();
}
else {
Token tk = parser.currentToken();
if (tk == Token.VALUE_NUMBER) {
val = parser.longValue();
}
else {
val = parseLong(value);
}
}
return processLong(val);
}
protected Long parseLong(String value) {
return Long.parseLong(value);
}
protected Object processLong(Long value) {
return value;
}
protected Object intValue(String value, Parser parser) {
Integer val = null;
if (value == null || isEmpty(value)) {
return nullValue();
}
else {
Token tk = parser.currentToken();
if (tk == Token.VALUE_NUMBER) {
val = parser.intValue();
}
else {
val = parseInteger(value);
}
}
return processInteger(val);
}
protected Integer parseInteger(String value) {
return Integer.parseInt(value);
}
protected Object processInteger(Integer value) {
return value;
}
protected Object byteValue(String value, Parser parser) {
Byte val = null;
if (value == null || isEmpty(value)) {
return nullValue();
}
else {
Token tk = parser.currentToken();
if (tk == Token.VALUE_NUMBER) {
val = (byte) parser.intValue();
}
else {
val = parseByte(value);
}
}
return processByte(val);
}
protected Byte parseByte(String value) {
return Byte.parseByte(value);
}
protected Object processByte(Byte value) {
return value;
}
protected Object shortValue(String value, Parser parser) {
Short val = null;
if (value == null || isEmpty(value)) {
return nullValue();
}
else {
Token tk = parser.currentToken();
if (tk == Token.VALUE_NUMBER) {
val = parser.shortValue();
}
else {
val = parseShort(value);
}
}
return processShort(val);
}
protected Short parseShort(String value) {
return Short.parseShort(value);
}
protected Object processShort(Short value) {
return value;
}
protected Object textValue(String value) {
return (value != null ? (!StringUtils.hasText(value) && emptyAsNull ? nullValue() : parseString(value)) : nullValue());
}
protected Object parseString(String value) {
return value;
}
protected Object nullValue() {
return null;
}
protected Object date(String value, Parser parser) {
Object val = null;
if (value == null || isEmpty(value)) {
return nullValue();
}
else {
Token tk = parser.currentToken();
// UNIX time format
if (tk == Token.VALUE_NUMBER) {
val = parseDate(parser.longValue(), richDate);
}
else {
val = parseDate(value, richDate);
}
}
return processDate(val);
}
protected Object parseDate(Long value, boolean richDate) {
return (richDate ? createDate(value) : value);
}
protected Object parseDate(String value, boolean richDate) {
return (richDate ? createDate(DateUtils.parseDate(value).getTimeInMillis()) : parseString(value));
}
protected Object createDate(long timestamp) {
return new Date(timestamp);
}
protected Object processDate(Object value) {
return value;
}
@Override
public void setSettings(Settings settings) {
emptyAsNull = settings.getReadFieldEmptyAsNull();
richDate = settings.getMappingDateRich();
arrayInclude = SettingsUtils.getFieldArrayFilterInclude(settings);
arrayExclude = StringUtils.tokenize(settings.getReadFieldAsArrayExclude());
}
}