Skip to content
Permalink
Browse files
better coercing for commons csv mapper
  • Loading branch information
Romain Manni-Bucau committed Nov 24, 2015
1 parent 7dc84fc commit 6047c395cd4bcca76bbefa6a44c5ca94135bb066
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 54 deletions.
@@ -42,5 +42,12 @@
<artifactId>commons-csv</artifactId>
<version>1.2</version>
</dependency>

<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-reflect</artifactId>
<version>4.4</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,21 @@
/*
* 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.batchee.csv.mapper;

public interface CoercingConverter {
Object valueFor(Class<?> type, String value);
}
@@ -29,14 +29,20 @@

public class DefaultMapper<T> implements CsvReaderMapper<T>, CsvWriterMapper<T> {
private final Class<T> type;
private final CoercingConverter coercingConverter;

private final SortedMap<Integer, Field> fieldByPosition = new TreeMap<Integer, Field>();
private final SortedMap<String, Field> fieldByName = new TreeMap<String, Field>();
private final SortedMap<Integer, String> headers = new TreeMap<Integer, String>();
private final int maxIndex;

public DefaultMapper(final Class<T> type) {
this(type, loadConverter());
}

protected DefaultMapper(final Class<T> type, final CoercingConverter coercingConverter) {
this.type = type;
this.coercingConverter = coercingConverter;

int higherIdx = -1;

@@ -137,10 +143,23 @@ private Object convert(final Class<?> type, final String value) {
if (String.class == type) {
return value;
}
final Object primVal = Primitives.valueFor(type, value);
if (primVal != null) {
return primVal;

if (coercingConverter != null) {
final Object val = coercingConverter.valueFor(type, value);
if (val != null) {
return val;
}
}

throw new IllegalArgumentException("Unsupported type " + type);
}

private static CoercingConverter loadConverter() {
try {
Thread.currentThread().getContextClassLoader().loadClass("org.apache.xbean.propertyeditor.PropertyEditors");
return XBeanConverter.INSTANCE;
} catch (ClassNotFoundException e) {
return Primitives.INSTANCE;
}
}
}
@@ -16,60 +16,68 @@
*/
package org.apache.batchee.csv.mapper;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public enum Primitives {
BooleanType(Boolean.class, boolean.class, new Converter() {
@Override
public Object convert(final String o) {
return Boolean.parseBoolean(o);
}
}),
IntegerType(Integer.class, int.class, new Converter() {
@Override
public Object convert(final String o) {
return Integer.parseInt(o);
}
}),
ShortType(Short.class, short.class, new Converter() {
@Override
public Object convert(final String o) {
return Short.parseShort(o);
}
}),
LongType(Long.class, long.class, new Converter() {
@Override
public Object convert(final String o) {
return Long.parseLong(o);
}
}),
ByteType(Byte.class, byte.class, new Converter() {
@Override
public Object convert(final String o) {
return Byte.parseByte(o);
}
}),
FloatType(Float.class, float.class, new Converter() {
@Override
public Object convert(final String o) {
return Float.parseFloat(o);
}
}),
DoubleType(Double.class, double.class, new Converter() {
@Override
public Object convert(final String o) {
return Double.parseDouble(o);
}
}),
CharacterType(Character.class, char.class, new Converter() {
@Override
public Object convert(final String o) {
return o.length() == 0 ? null : o.charAt(0);
}
});
public class Primitives implements CoercingConverter {
static {
register(Boolean.class, boolean.class, false, new Converter() {
@Override
public Object convert(final String o) {
return Boolean.parseBoolean(o);
}
});
register(Integer.class, int.class, (int) 0, new Converter() {
@Override
public Object convert(final String o) {
return Integer.parseInt(o);
}
});
register(Short.class, short.class, (short) 0, new Converter() {
@Override
public Object convert(final String o) {
return Short.parseShort(o);
}
});
register(Long.class, long.class, 0L, new Converter() {
@Override
public Object convert(final String o) {
return Long.parseLong(o);
}
});
register(Byte.class, byte.class, (byte) 0, new Converter() {
@Override
public Object convert(final String o) {
return Byte.parseByte(o);
}
});
register(Float.class, float.class, 0.f, new Converter() {
@Override
public Object convert(final String o) {
return Float.parseFloat(o);
}
});
register(Double.class, double.class, 0., new Converter() {
@Override
public Object convert(final String o) {
return Double.parseDouble(o);
}
});
register(Character.class, char.class, (char) 0, new Converter() {
@Override
public Object convert(final String o) {
return o.length() == 0 ? null : o.charAt(0);
}
});
}

public static final CoercingConverter INSTANCE = new Primitives();

private static final class Mapping {
private static final Map<Class<?>, Method> PRIMITIVES = new HashMap<Class<?>, Method>();
private static final Map<Class<?>, Object> PRIMITIVE_DEFAULTS = new HashMap<Class<?>, Object>();
private static final Map<Class<?>, Class<?>> WRAPPERS = new HashMap<Class<?>, Class<?>>();
private static final Map<Class<?>, Converter> CONVERTERS = new HashMap<Class<?>, Converter>();

@@ -82,20 +90,56 @@ private interface Converter {
Object convert(String o);
}

Primitives(final Class<?> wrapper, final Class<?> primitive, final Converter converter) {
public static Object primitiveDefaultValue(final Class<?> type) {
return Mapping.PRIMITIVE_DEFAULTS.get(type);
}

private static void register(final Class<?> wrapper, final Class<?> primitive, final Object defaultValue, final Converter converter) {
Mapping.WRAPPERS.put(wrapper, primitive);
Mapping.CONVERTERS.put(wrapper, converter);
Mapping.PRIMITIVE_DEFAULTS.put(primitive, defaultValue);
if (wrapper != Character.class) {
try {
Mapping.PRIMITIVES.put(primitive, wrapper.getMethod("valueOf", String.class));
} catch (final NoSuchMethodException e) {
throw new IllegalStateException(e);
}
}
}

public static Object valueFor(final Class<?> type, final String value) {
@Override
public Object valueFor(final Class<?> type, final String value) {
final Method valueOf = Mapping.PRIMITIVES.get(type);
if (valueOf != null) {
if (value == null || value.trim().isEmpty()) {
return Mapping.PRIMITIVE_DEFAULTS.get(type);
}

try {
return valueOf.invoke(null, value);
} catch (final IllegalAccessException e) {
throw new IllegalArgumentException(e);
} catch (InvocationTargetException e) {
throw new IllegalArgumentException(e.getCause());
}
} else if (char.class == type) {
if (value == null || value.trim().isEmpty()) {
return Mapping.PRIMITIVE_DEFAULTS.get(type);
}
return value.charAt(0);
}

if (value == null) {
return null;
}

final Class<?> currentType = value.getClass();
final Class<?> primitive = Mapping.WRAPPERS.get(currentType);
if (primitive != null && type == primitive) {
return Mapping.CONVERTERS.get(currentType).convert(value);
}


return null;
}
}
@@ -0,0 +1,37 @@
/*
* 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.batchee.csv.mapper;

import org.apache.xbean.propertyeditor.PropertyEditors;

public final class XBeanConverter implements CoercingConverter {
public static final CoercingConverter INSTANCE = new XBeanConverter();

@Override
public Object valueFor(final Class<?> type, final String value) {
if (value == null || value.isEmpty()) {
final Object def = Primitives.primitiveDefaultValue(type);
if (def != null) {
return def;
}
}
if (value == null) {
return null;
}
return PropertyEditors.canConvert(type) ? PropertyEditors.getValue(type, value) : null;
}
}
@@ -0,0 +1,54 @@
/*
* 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.batchee.csv.mapper;

import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;

import static org.junit.Assert.assertEquals;

@RunWith(Theories.class)
public class PrimitivesTest {
@DataPoints
public static CoercingConverter[] converters() {
return new CoercingConverter[]{ new Primitives(), new XBeanConverter() };
}

@Theory
public void defaultValues(final CoercingConverter converter) {
assertEquals(0L, converter.valueFor(long.class, ""));
assertEquals((short) 0, converter.valueFor(short.class, ""));
assertEquals(0, converter.valueFor(int.class, ""));
assertEquals((byte) 0, converter.valueFor(byte.class, ""));
assertEquals((char) 0, converter.valueFor(char.class, ""));
assertEquals(0.f, converter.valueFor(float.class, ""));
assertEquals(0., converter.valueFor(double.class, ""));
}

@Theory
public void primitives(final CoercingConverter converter) {
assertEquals(1L, converter.valueFor(long.class, "1"));
assertEquals((short) 1, converter.valueFor(short.class, "1"));
assertEquals(1, converter.valueFor(int.class, "1"));
assertEquals((byte) 1, converter.valueFor(byte.class, "1"));
assertEquals(1.f, converter.valueFor(float.class, "1"));
assertEquals(1., converter.valueFor(double.class, "1"));
assertEquals('1', converter.valueFor(char.class, "1"));
}
}

0 comments on commit 6047c39

Please sign in to comment.