code: package com.taxonic.carml.rdf_mapper.impl;
  
  import com.google.common.collect.ImmutableMap;
  import com.taxonic.carml.rdf_mapper.Mapper;
  import com.taxonic.carml.rdf_mapper.TypeDecider;
  import java.lang.reflect.Type;
  import java.util.Map;
  import java.util.Set;
  import java.util.function.Function;
  import org.eclipse.rdf4j.model.IRI;
  import org.eclipse.rdf4j.model.Literal;
  import org.eclipse.rdf4j.model.Model;
  import org.eclipse.rdf4j.model.Resource;
  import org.eclipse.rdf4j.model.Value;
  import org.eclipse.rdf4j.model.vocabulary.XSD;
  
  public class ComplexValueTransformer implements ValueTransformer {
  
  	private TypeDecider typeDecider;
  	private MappingCache mappingCache;
  	private Mapper mapper;
  	private Function<Object, Object> typeAdapter;
  	
  	public ComplexValueTransformer(
  		TypeDecider typeDecider,
  		MappingCache mappingCache,
  		Mapper mapper,
  		Function<Object, Object> typeAdapter
  	) {
  		this.typeDecider = typeDecider;
  		this.mappingCache = mappingCache;
  		this.mapper = mapper;
  		this.typeAdapter = typeAdapter;
  	}
  	
  	private static final Map<IRI, Function<Literal, Object>> literalGetters =
  		ImmutableMap.<IRI, Function<Literal, Object>>builder()
  			.put(XSD.BOOLEAN, Literal::booleanValue)
  			.put(XSD.STRING, Literal::getLabel)
  			.put(XSD.DECIMAL, Literal::decimalValue)
  			.put(XSD.FLOAT, Literal::floatValue)
  			.put(XSD.INT, Literal::intValue)
  			.put(XSD.INTEGER, Literal::integerValue) // BigInteger
  			.put(XSD.DOUBLE, Literal::doubleValue)
  			// TODO more types, most notably xsd:date and variations
  			.build();
  
  	private Object transform(Literal literal) {
  		IRI type = literal.getDatatype();
  		Function<Literal, Object> getter = literalGetters.get(type);
  		if (getter == null)
  			throw new RuntimeException(String.format("no getter for Literal [%s] defined that can handle literal with datatype [%s]",
  					literal, type));
  		return getter.apply(literal);
  	}
  	
  	@Override
  	public Object transform(Model model, Value value) {
  	
  		if (value instanceof Literal)
  			return transform((Literal) value);
  		
  		
  		// =========== RESOURCE ===========
  		
  		
  		Resource resource = (Resource) value;
  		
  		// determine exact target type
  		Set<Type> targetTypes = typeDecider.decide(model, resource);
  		
  		// TODO check for target type conditions?
  		// - must be a subtype of 'propertyType'
  		// - must be a specific type, eg. no unbound type parameters, not an interface
  		
  		Object targetValue = mapper.map(model, resource, targetTypes);
  			
  		// TODO check cache for adapted value (key: typeAdapter + targetValue)
  		Object adaptedValue = typeAdapter.apply(targetValue);
  		// TODO maybe we should cache this as well, in a diff. cache. (key: typeAdapter + targetValue)
  
  		return adaptedValue;
  	}
  }
  