/*******************************************************************************
*
* Pentaho Big Data
*
* Copyright (C) 2002-2015 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* 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 org.pentaho.hadoop.mapreduce.converter;
import org.apache.hadoop.io.BooleanWritable;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;
import org.pentaho.di.core.row.ValueMeta;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.hadoop.mapreduce.converter.spi.ITypeConverter;
import java.math.BigDecimal;
import java.sql.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
public class TypeConverterFactory {
/**
* Map key to represent a converter capable of converting an object from the given type to another type.
*/
private static class Key {
private Class from;
private Class to;
private Key( Class from, Class to ) {
this.from = from;
this.to = to;
}
@Override
public boolean equals( Object o ) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Key key = (Key) o;
if ( from != null ? !from.equals( key.from ) : key.from != null ) {
return false;
}
if ( to != null ? !to.equals( key.to ) : key.to != null ) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = from != null ? from.hashCode() : 0;
result = 31 * result + ( to != null ? to.hashCode() : 0 );
return result;
}
}
/**
* Determines if a class can be converted by {@code ValueMetaInterface}.
*
* @param type Type to check
* @return True if this type maps to a {@code ValueMetaInterface.TYPE_*}
*/
public static boolean isKettleType( Class<?> type ) {
return type != null && ( CharSequence.class.isAssignableFrom( type )
|| Number.class.isAssignableFrom( type )
|| byte[].class.equals( type )
|| Boolean.class.equals( type )
|| java.sql.Date.class.equals( type ) );
}
/**
* Attempt to determine the Java {@link Class} for the {@link ValueMetaInterface} provided
*
* @param vmi Value Meta with type information to look up
* @return the class that represents the type {@link ValueMetaInterface} represents; {@code null} if no type can be
* matched.
*/
public Class<?> getJavaClass( ValueMetaInterface vmi ) {
Class<?> metaClass = null;
switch ( vmi.getType() ) {
case ValueMeta.TYPE_BIGNUMBER:
metaClass = BigDecimal.class;
break;
case ValueMeta.TYPE_BINARY:
metaClass = byte[].class;
break;
case ValueMeta.TYPE_BOOLEAN:
metaClass = Boolean.class;
break;
case ValueMeta.TYPE_DATE:
metaClass = Date.class;
break;
case ValueMeta.TYPE_INTEGER:
metaClass = Long.class;
break;
case ValueMeta.TYPE_NUMBER:
metaClass = Double.class;
break;
case ValueMeta.TYPE_STRING:
metaClass = String.class;
break;
case ValueMeta.TYPE_SERIALIZABLE:
metaClass = Object.class;
break;
}
return metaClass;
}
/**
* Determine the Hadoop writable type to pass Kettle type back to Hadoop as.
*
* @param kettleType
* @return Java type to convert {@code kettleType} to when sending data back to Hadoop.
*/
public static Class<? extends Writable> getWritableForKettleType( ValueMetaInterface kettleType ) {
if ( kettleType == null ) {
return NullWritable.class;
}
switch ( kettleType.getType() ) {
case ValueMetaInterface.TYPE_STRING:
case ValueMetaInterface.TYPE_BIGNUMBER:
case ValueMetaInterface.TYPE_DATE:
return Text.class;
case ValueMetaInterface.TYPE_INTEGER:
return LongWritable.class;
case ValueMetaInterface.TYPE_NUMBER:
return DoubleWritable.class;
case ValueMetaInterface.TYPE_BOOLEAN:
return BooleanWritable.class;
case ValueMetaInterface.TYPE_BINARY:
return BytesWritable.class;
default:
return Text.class;
}
}
/**
* Local cache of type converters
*/
private Map<Key, ITypeConverter<?, ?>> cache;
public TypeConverterFactory() {
cache = new HashMap<Key, ITypeConverter<?, ?>>();
}
/**
* Find a converter by dynamically loading SPI implementations of {@link ITypeConverter} and returning the first one
* that returns {@code true} from {@link ITypeConverter#canConvert(Class, Class) canConvert(from, to)}.
*
* @param from Type to convert from
* @param to Type to convert to
* @return A type converter that can handle converting between {@code from} and {@code to}
* @throws TypeConversionException Error instantiating a converter while traversing the list of registered type
* converters
*/
protected <F, T> ITypeConverter<F, T> findConverter( Class<F> from, Class<T> to ) throws TypeConversionException {
ServiceLoader<ITypeConverter> loader = ServiceLoader.load( ITypeConverter.class );
try {
for ( ITypeConverter tc : loader ) {
if ( tc.canConvert( from, to ) ) {
return tc;
}
}
} catch ( ServiceConfigurationError ex ) {
throw new TypeConversionException( "Error instantiating type converter", ex );
}
return null;
}
/**
* Registers a converter that is capable of converting from type {@code from} to type {@code to}.
*
* @param from Type this converter can convert from
* @param to Type this converter can convert to
* @param converter The converter to handle the conversion between {@code from} and {@code to}
*/
public <F, T> void registerConverter( Class<F> from, Class<T> to, ITypeConverter<F, T> converter ) {
cache.put( new Key( from, to ), converter );
}
/**
* Obtain a {@link ITypeConverter} that can handle converting from {@code from} and to {@code to}.
*
* @param from Type to convert from
* @param to Type to convert to
* @return Converter that is capable of converting from {@code from} and to {@code to}.
* @throws TypeConversionException No converter available for these types
*/
public <F, T> ITypeConverter<F, T> getConverter( Class<F> from, Class<T> to ) throws TypeConversionException {
ITypeConverter converter = cache.get( new Key( from, to ) );
if ( converter == null ) {
converter = findConverter( from, to );
registerConverter( from, to, converter );
}
if ( converter == null ) {
throw new TypeConversionException( BaseMessages.getString( getClass(), "ConverterNotFound", from, to ) );
}
return converter;
}
/**
* Obtain a {@link ITypeConverter} that can handle converting from {@code from} to the value meta type provided. The
* {@link Class} to convert to is obtained through {@link #getJavaClass(org.pentaho.di.core.row.ValueMetaInterface)
* getJavaClass(vmi)}. If no destination type can be determined from {@code vmi} no converter will be returned.
*
* @param from Type to convert from
* @param vmi Value meta with type information to convert to
* @return Converter that is capable of converting from {@code from} to the type for {@code vmi} or {@code null} if no
* Java {@link Class} could be determined from {@code vmi}.
* @throws TypeConversionException No converter available for these types
*/
public <F> ITypeConverter<F, ?> getConverter( Class<F> from, ValueMetaInterface vmi ) throws TypeConversionException {
Class<?> to = getJavaClass( vmi );
return to == null ? null : getConverter( from, to );
}
}