package org.javers.core.metamodel.type;
import org.javers.common.collections.Primitives;
import org.javers.common.collections.WellKnownValueTypes;
import org.javers.common.exception.JaversException;
import org.javers.common.exception.JaversExceptionCode;
import org.javers.common.validation.Validate;
import org.javers.core.metamodel.clazz.ClientsClassDefinition;
import org.javers.core.metamodel.object.GlobalId;
import org.javers.core.metamodel.property.Property;
import org.javers.core.metamodel.scanner.ClassScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.javers.common.validation.Validate.argumentIsNotNull;
/**
* Maps Java types into Javers types
*
* @author bartosz walacik
*/
public class TypeMapper {
private static final Logger logger = LoggerFactory.getLogger(TypeMapper.class);
private final TypeMapperState state;
private final DehydratedTypeFactory dehydratedTypeFactory = new DehydratedTypeFactory(this);
public TypeMapper(ClassScanner classScanner) {
//Pico doesn't support cycles, so manual construction
TypeFactory typeFactory = new TypeFactory(classScanner, this);
this.state = new TypeMapperState(typeFactory);
registerCoreTypes();
}
/**
* for TypeMapperConcurrentTest only, no better idea how to writhe this test
* without additional constructor
*/
protected TypeMapper(TypeFactory typeFactory ) {
this.state = new TypeMapperState(typeFactory);
registerCoreTypes();
}
private void registerCoreTypes(){
//primitives & boxes
for (Class primitiveOrBox : Primitives.getPrimitiveAndBoxTypes()) {
registerPrimitiveType(primitiveOrBox);
}
//String & Enum
registerPrimitiveType(String.class);
registerPrimitiveType(CharSequence.class);
registerPrimitiveType(Enum.class);
//array
addType(new ArrayType(Object[].class));
//well known Value types
for (Class valueType : WellKnownValueTypes.getValueTypes()) {
registerValueType(valueType);
}
//Collections
addType(new CollectionType(Collection.class)); //only for exception handling
addType(new SetType(Set.class));
addType(new ListType(List.class));
addType(new OptionalType());
//& Maps
addType(new MapType(Map.class));
}
public MapContentType getMapContentType(KeyValueType mapType){
JaversType keyType = getJaversType(mapType.getKeyType());
JaversType valueType = getJaversType(mapType.getValueType());
return new MapContentType(keyType, valueType);
}
/**
* only for change appenders
*/
public MapContentType getMapContentType(ContainerType containerType){
JaversType keyType = getJaversType(Integer.class);
JaversType valueType = getJaversType(containerType.getItemType());
return new MapContentType(keyType, valueType);
}
/**
* is Set, List or Array of ManagedClasses
*/
public boolean isContainerOfManagedTypes(JaversType javersType){
if (! (javersType instanceof ContainerType)) {
return false;
}
return getJaversType(((ContainerType)javersType).getItemType()) instanceof ManagedType;
}
/**
* is Map (or Multimap) with ManagedClass on Key or Value position
*/
public boolean isKeyValueTypeWithManagedTypes(JaversType enumerableType) {
if (enumerableType instanceof KeyValueType){
KeyValueType mapType = (KeyValueType)enumerableType;
JaversType keyType = getJaversType(mapType.getKeyType());
JaversType valueType = getJaversType(mapType.getValueType());
return keyType instanceof ManagedType || valueType instanceof ManagedType;
} else{
return false;
}
}
/**
* returns mapped type or spawns new one from prototype
* or infers new one using default mapping
*/
public JaversType getJaversType(Type javaType) {
argumentIsNotNull(javaType);
return state.getJaversType(javaType);
}
/**
* @throws JaversException TYPE_NAME_NOT_FOUND if given typeName is not registered
* @since 1.4
*/
public ManagedType getJaversManagedType(String typeName){
return getJaversManagedType(state.getClassByTypeName(typeName), ManagedType.class);
}
/**
* @throws JaversException TYPE_NAME_NOT_FOUND if given typeName is not registered
* @since 1.4
*/
public ManagedType getJaversManagedType(GlobalId globalId){
return getJaversManagedType(state.getClassByTypeName(globalId.getTypeName()), ManagedType.class);
}
/**
* @throws JaversException TYPE_NAME_NOT_FOUND if given typeName is not registered
* @since 1.4
*/
public <T extends ManagedType> T getJaversManagedType(String typeName, Class<T> expectedType) {
return getJaversManagedType(state.getClassByTypeName(typeName), expectedType);
}
/**
* @throws JaversException TYPE_NAME_NOT_FOUND if given typeName is not registered
* @since 1.4
*/
public <T extends ManagedType> T getJaversManagedType(DuckType duckType, Class<T> expectedType) {
return getJaversManagedType(state.getClassByDuckType(duckType), expectedType);
}
/**
* If given javaClass is mapped to ManagedType, returns its JaversType
*
* @throws JaversException MANAGED_CLASS_MAPPING_ERROR
*/
public ManagedType getJaversManagedType(Class javaType) {
return getJaversManagedType(javaType, ManagedType.class);
}
/**
* If given javaClass is mapped to expected ManagedType, returns its JaversType
*
* @throws JaversException MANAGED_CLASS_MAPPING_ERROR
*/
public <T extends ManagedType> T getJaversManagedType(Class javaClass, Class<T> expectedType) {
JaversType mType = getJaversType(javaClass);
if (expectedType.isAssignableFrom(mType.getClass())) {
return (T) mType;
} else {
throw new JaversException(JaversExceptionCode.MANAGED_CLASS_MAPPING_ERROR,
javaClass,
mType.getClass().getSimpleName(),
expectedType.getSimpleName());
}
}
public <T extends JaversType> T getPropertyType(Property property){
argumentIsNotNull(property);
try {
return (T) getJaversType(property.getGenericType());
}catch (JaversException e) {
logger.error("Can't calculate JaversType for property: {}", property);
throw e;
}
}
private void registerPrimitiveType(Class<?> primitiveClass) {
addType(new PrimitiveType(primitiveClass));
}
public void registerClientsClass(ClientsClassDefinition def) {
state.computeIfAbsent(def);
}
public void registerValueType(Class<?> valueCLass) {
addType(new ValueType(valueCLass));
}
public boolean isValueObject(Type type) {
JaversType jType = getJaversType(type);
return jType instanceof ValueObjectType;
}
/**
* Dehydrated type for JSON representation
*/
public Type getDehydratedType(Type type) {
return dehydratedTypeFactory.build(type);
}
public void addType(JaversType jType) {
Validate.argumentIsNotNull(jType);
state.putIfAbsent(jType.getBaseJavaType(), jType);
}
public void addTypes(Collection<JaversType> jTypes) {
Validate.argumentIsNotNull(jTypes);
for (JaversType t : jTypes) {
addType(t);
}
}
boolean contains(Type javaType){
return state.contains(javaType);
}
}