docs: @SuppressWarnings({"unchecked", "rawtypes"})
  	private <IN1> TypeInformation<?> createTypeInfoFromInput(TypeVariable<?> returnTypeVar, ArrayList<Type> inputTypeHierarchy, Type inType, TypeInformation<IN1> inTypeInfo) {
  		TypeInformation<?> info = null;
  
  		// use a factory to find corresponding type information to type variable
  		final ArrayList<Type> factoryHierarchy = new ArrayList<>(inputTypeHierarchy);
  		final TypeInfoFactory<?> factory = getClosestFactory(factoryHierarchy, inType);
  		if (factory != null) {
  			// the type that defines the factory is last in factory hierarchy
  			final Type factoryDefiningType = factoryHierarchy.get(factoryHierarchy.size() - 1);
  			// defining type has generics, the factory need to be asked for a mapping of subtypes to type information
  			if (factoryDefiningType instanceof ParameterizedType) {
  				final Type[] typeParams = typeToClass(factoryDefiningType).getTypeParameters();
  				final Type[] actualParams = ((ParameterizedType) factoryDefiningType).getActualTypeArguments();
  				// go thru all elements and search for type variables
  				for (int i = 0; i < actualParams.length; i++) {
  					final Map<String, TypeInformation<?>> componentInfo = inTypeInfo.getGenericParameters();
  					final String typeParamName = typeParams[i].toString();
  					if (!componentInfo.containsKey(typeParamName) || componentInfo.get(typeParamName) == null) {
  						throw new InvalidTypesException("TypeInformation '" + inTypeInfo.getClass().getSimpleName() +
  							"' does not supply a mapping of TypeVariable '" + typeParamName + "' to corresponding TypeInformation. " +
  							"Input type inference can only produce a result with this information. " +
  							"Please implement method 'TypeInformation.getGenericParameters()' for this.");
  					}
  					info = createTypeInfoFromInput(returnTypeVar, factoryHierarchy, actualParams[i], componentInfo.get(typeParamName));
  					if (info != null) {
  						break;
  					}
  				}
  			}
  		}
  		// the input is a type variable
  		else if (sameTypeVars(inType, returnTypeVar)) {
  			return inTypeInfo;
  		}
  		else if (inType instanceof TypeVariable) {
  			Type resolvedInType = materializeTypeVariable(inputTypeHierarchy, (TypeVariable<?>) inType);
  			if (resolvedInType != inType) {
  				info = createTypeInfoFromInput(returnTypeVar, inputTypeHierarchy, resolvedInType, inTypeInfo);
  			}
  		}
  		// input is an array
  		else if (inType instanceof GenericArrayType) {
  			TypeInformation<?> componentInfo = null;
  			if (inTypeInfo instanceof BasicArrayTypeInfo) {
  				componentInfo = ((BasicArrayTypeInfo<?,?>) inTypeInfo).getComponentInfo();
  			}
  			else if (inTypeInfo instanceof PrimitiveArrayTypeInfo) {
  				componentInfo = BasicTypeInfo.getInfoFor(inTypeInfo.getTypeClass().getComponentType());
  			}
  			else if (inTypeInfo instanceof ObjectArrayTypeInfo) {
  				componentInfo = ((ObjectArrayTypeInfo<?,?>) inTypeInfo).getComponentInfo();
  			}
  			info = createTypeInfoFromInput(returnTypeVar, inputTypeHierarchy, ((GenericArrayType) inType).getGenericComponentType(), componentInfo);
  		}
  		// the input is a tuple
  		else if (inTypeInfo instanceof TupleTypeInfo && isClassType(inType) && Tuple.class.isAssignableFrom(typeToClass(inType))) {
  			ParameterizedType tupleBaseClass;
  
  			// get tuple from possible tuple subclass
  			while (!(isClassType(inType) && typeToClass(inType).getSuperclass().equals(Tuple.class))) {
  				inputTypeHierarchy.add(inType);
  				inType = typeToClass(inType).getGenericSuperclass();
  			}
  			inputTypeHierarchy.add(inType);
  
  			// we can assume to be parameterized since we
  			// already did input validation
  			tupleBaseClass = (ParameterizedType) inType;
  
  			Type[] tupleElements = tupleBaseClass.getActualTypeArguments();
  			// go thru all tuple elements and search for type variables
  			for (int i = 0; i < tupleElements.length; i++) {
  				info = createTypeInfoFromInput(returnTypeVar, inputTypeHierarchy, tupleElements[i], ((TupleTypeInfo<?>) inTypeInfo).getTypeAt(i));
  				if(info != null) {
  					break;
  				}
  			}
  		}
  		// the input is a pojo
  		else if (inTypeInfo instanceof PojoTypeInfo && isClassType(inType)) {
  			// build the entire type hierarchy for the pojo
  			getTypeHierarchy(inputTypeHierarchy, inType, Object.class);
  			// determine a field containing the type variable
  			List<Field> fields = getAllDeclaredFields(typeToClass(inType), false);
  			for (Field field : fields) {
  				Type fieldType = field.getGenericType();
  				if (fieldType instanceof TypeVariable && sameTypeVars(returnTypeVar, materializeTypeVariable(inputTypeHierarchy, (TypeVariable<?>) fieldType))) {
  					return getTypeOfPojoField(inTypeInfo, field);
  				}
  				else if (fieldType instanceof ParameterizedType || fieldType instanceof GenericArrayType) {
  					ArrayList<Type> typeHierarchyWithFieldType = new ArrayList<>(inputTypeHierarchy);
  					typeHierarchyWithFieldType.add(fieldType);
  					TypeInformation<?> foundInfo = createTypeInfoFromInput(returnTypeVar, typeHierarchyWithFieldType, fieldType, getTypeOfPojoField(inTypeInfo, field));
  					if (foundInfo != null) {
  						return foundInfo;
  					}
  				}
  			}
  		}
  		return info;
  	}