/*
* Copyright 2014 Ruediger Moeller.
*
* 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.nustaq.kson;
import java.lang.reflect.Array;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* simple implementation of type mapper.
* * Maps Classes to short string names and vice versa.
* * allows to add user-defined type conversions (e.g. Date, Collections)
*
* This default implementation supports Date<=>String and Collections<=>Array coercion.
*/
public class KsonTypeMapper {
public static final Object NULL_LITERAL = "NULL";
protected boolean useSimplClzName = true;
protected HashMap<String,Class> typeMap = new HashMap<String, Class>(31);
protected HashMap<Class, String> reverseTypeMap = new HashMap<Class, String>(31);
protected DateFormat dateTimeInstance = DateFormat.getDateTimeInstance();
public KsonTypeMapper() {
map("map", HashMap.class).map("list", HashMap.class).map("set",HashSet.class);
}
final Class NONE = Object.class;
public Class getType(String type) {
Class res = typeMap.get(type);
if ( res == null ) {
try {
res = Class.forName(type);
if ( res == null ) {
typeMap.put(type,NONE);
} else
typeMap.put(type,res);
} catch (ClassNotFoundException e) {
typeMap.put(type,NONE);
return null;
}
}
if ( res == NONE )
res = null;
return res;
}
public KsonTypeMapper map(String name, Class c) {
typeMap.put(name, c);
reverseTypeMap.put(c,name);
return this;
}
public KsonTypeMapper map(Object ... stringAndClasses) {
for (int i = 0; i < stringAndClasses.length; i+=2) {
map( stringAndClasses[i], stringAndClasses[i+1]);
}
return this;
}
public KsonTypeMapper map(Class ... c) {
for (int i = 0; i < c.length; i++) {
Class aClass = c[i];
map(aClass.getSimpleName(),aClass);
}
return this;
}
public boolean isUseSimplClzName() {
return useSimplClzName;
}
public void setUseSimplClzName(boolean useSimplClzName) {
this.useSimplClzName = useSimplClzName;
}
/**
* map given Object to a target type.
* (needs support in coerceWriting also)
* Note one could add a pluggable Serializer/Coercer pattern here if required. Skipped for now for simplicity.
*
* @param type - of target field
* @param readObject - object read from string
* @return
*/
public Object coerceReading(Class type, Object readObject) {
if (type==null)
return readObject;
// make hashmaps from arrays. warning: for optimal performance, use direct arrays[] only in your serialized classes
if ( Map.class.isAssignableFrom(type) && readObject.getClass().isArray() ) {
try {
Map c = (Map) type.newInstance();
int len = Array.getLength(readObject);
for ( int i = 0; i < len; i+=2 ) {
c.put(Array.get(readObject, i), Array.get(readObject, i + 1));
}
return c;
} catch (Exception e) {
e.printStackTrace();
}
} else // make collections from arrays. warning: for optimal performance, use direct arrays[] only in your serialized classes
if ( Collection.class.isAssignableFrom(type) && readObject.getClass().isArray() ) {
try {
if ( type.isInterface() ) {
if ( List.class.isAssignableFrom(type) ) {
type = ArrayList.class;
} else if (Map.class.isAssignableFrom(type) ) {
type = HashMap.class;
}
}
Collection c = (Collection) type.newInstance();
int len = Array.getLength(readObject);
for ( int i = 0; i < len; i++ ) {
c.add(Array.get(readObject,i));
}
return c;
} catch (Exception e) {
e.printStackTrace();
}
} else if ( Date.class.isAssignableFrom(type) && readObject instanceof String) {
try {
return dateTimeInstance.parse((String) readObject);
} catch (ParseException pe) {
pe.printStackTrace();
}
} else if ( (type == char.class || Character.class.isAssignableFrom(type)) && readObject instanceof String ) {
return ((String) readObject).charAt(0);
}
return readObject;
}
public DateFormat getDateTimeInstance() {
return dateTimeInstance;
}
public void setDateTimeInstance(DateFormat dateTimeInstance) {
this.dateTimeInstance = dateTimeInstance;
}
public Object mapLiteral(String type) {
if (type.equals("null")) {
return NULL_LITERAL;
}
if (type.equals("true") || type.equals("yes") || type.equals("y")) {
return Boolean.TRUE;
}
if (type.equals("false") || type.equals("no") || type.equals("n")) {
return Boolean.FALSE;
}
return null;
}
public String getStringForType(Class<? extends Object> aClass) {
String res = reverseTypeMap.get(aClass);
if (res==null)
res = useSimplClzName ? aClass.getSimpleName() : aClass.getName();
return res;
}
}