/*
* Copyright 2012 Jason Miller
*
* 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 jj.conversion;
import static jj.util.CodeGenHelper.*;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* <p>
* Provides simple object conversion services. While it's written to be extensible,
* and may well get extracted into an independent project, right now it's just
* intended to support basic internal needs and as such isn't super flexible in the interface
* </p>
*
* <p>
* Credit to guice et al for inspiring me as to the basic technique.
* </p>
* @author jason
*
*/
@Singleton
public class Converters {
private static final Map<Class<?>, Object> primitiveDefaults;
static {
Map<Class<?>, Object> primitiveDefaultsBuilder = new HashMap<>();
primitiveDefaultsBuilder.put(Boolean.TYPE, false);
primitiveDefaultsBuilder.put(Character.TYPE, (char)0);
primitiveDefaultsBuilder.put(Byte.TYPE, (byte)0);
primitiveDefaultsBuilder.put(Short.TYPE, (short)0);
primitiveDefaultsBuilder.put(Integer.TYPE, 0);
primitiveDefaultsBuilder.put(Long.TYPE, 0L);
primitiveDefaultsBuilder.put(Float.TYPE, 0F);
primitiveDefaultsBuilder.put(Double.TYPE, 0.0);
primitiveDefaults = Collections.unmodifiableMap(primitiveDefaultsBuilder);
}
private final Map<Class<?>, Map<Class<?>, Converter<?, ?>>> converters = new HashMap<>();
@Inject
public Converters(final Set<Converter<?, ?>> converters) {
for (Converter<?, ?> converter : converters) {
register(converter);
}
}
private void register(Converter<?, ?> converter) {
Type[] interfaceTypes = converter.getClass().getGenericInterfaces();
Type[] types = ((ParameterizedType)interfaceTypes[0]).getActualTypeArguments();
Class<?> fromClass = (Class<?>)types[0];
Class<?> fromPrimitiveClass = wrappersToPrimitives.get(fromClass);
Class<?> toClass = (Class<?>)types[1];
Class<?> toPrimitiveClass = wrappersToPrimitives.get(toClass);
// first the basic
add(fromClass, toClass, converter);
// now check the wrappers
if (fromPrimitiveClass != null) {
add(fromPrimitiveClass, toClass, converter);
}
if (toPrimitiveClass != null) {
add(fromClass, toPrimitiveClass, converter);
}
if (fromPrimitiveClass != null && toPrimitiveClass != null) {
add(fromPrimitiveClass, toPrimitiveClass, converter);
}
}
private void add(Class<?> fromClass, Class<?> toClass, Converter<?, ?> converter) {
Map<Class<?>, Converter<?, ?>> fromConverters = converters.get(fromClass);
if (fromConverters == null) {
fromConverters = new HashMap<>();
converters.put(fromClass, fromConverters);
}
converters.get(fromClass).put(toClass, converter);
}
// java makes you do weird shit to avoid warnings sometimes
@SuppressWarnings("unchecked")
private <To> To cast(Object in) {
return (To)in;
}
/**
* <p>
* Converts an incoming value to the desire Class, according to
* the runtime type of the from parameter and the specified class
* of the to parameter.
* </p>
*
* <p>Identity conversions always work, so converting without needing to is just fine</p>
*
* <p>String-to-enum conversions work, provided the value exactly matches the enum name</p>
*
* <p>
* Throws {@link AssertionError}s if the type cannot be handled since
* it's currently configured programmatically
* </p>
* @param from The object being converted
* @param to The type to convert to
* @return the converted value, or null if the conversion could not be performed
*/
public <From, To> To convert(From from, Class<To> to) {
assert to != null; // gotta be there
if (from == null) {
if (primitiveDefaults.containsKey(to)) {
return cast(primitiveDefaults.get(to));
}
return null;
}
Class<?> fromClass = from.getClass();
if (to.isAssignableFrom(fromClass) ||
to.isPrimitive() && primitivesToWrappers.get(to).isAssignableFrom(fromClass)) {
return cast(from);
}
if (to.isEnum() && from instanceof String) {
for (To eTo : to.getEnumConstants()) {
Enum<?> e = (Enum<?>)eTo;
if (from.equals(e.name())) {
return eTo;
}
}
return null;
}
Map<Class<?>, Converter<?, ?>> fromConverters = converters.get(fromClass);
if (fromConverters == null && to == String.class) {
return cast(String.valueOf(from));
}
assert (fromConverters != null) : "don't know how to convert from " + fromClass + " to " + to;
@SuppressWarnings("unchecked")
Converter<From, To> converter = (Converter<From, To>)fromConverters.get(to);
if (converter == null && to == String.class) {
return cast(String.valueOf(from));
}
assert (converter != null) : "don't know how to convert " + fromClass + " to " + to;
Object value = converter.convert(from);
To result = cast(primitivesToWrappers.containsKey(to) ? primitivesToWrappers.get(to).cast(value) : value);
if (result == null && primitiveDefaults.containsKey(to)) {
result = cast(primitiveDefaults.get(to));
}
return result;
}
@Override
public String toString() {
return converters.toString();
}
}