docs: private ThriftType buildThriftTypeInternal(Type javaType)
              throws IllegalArgumentException
      {
          Class<?> rawType = TypeToken.of(javaType).getRawType();
          ThriftType manualType = manualTypes.get(rawType);
          if (manualType != null) {
              return manualType;
          }
          if (boolean.class == rawType) {
              return BOOL;
          }
          if (byte.class == rawType) {
              return BYTE;
          }
          if (short.class == rawType) {
              return I16;
          }
          if (int.class == rawType) {
              return I32;
          }
          if (long.class == rawType) {
              return I64;
          }
          if (double.class == rawType) {
              return DOUBLE;
          }
          if (String.class == rawType) {
              return STRING;
          }
          if (ByteBuffer.class.isAssignableFrom(rawType)) {
              return BINARY;
          }
          if (Enum.class.isAssignableFrom(rawType)) {
              ThriftEnumMetadata<? extends Enum<?>> thriftEnumMetadata = getThriftEnumMetadata(rawType);
              return enumType(thriftEnumMetadata);
          }
          if (rawType.isArray()) {
              Class<?> elementType = rawType.getComponentType();
              if (elementType == byte.class) {
                  // byte[] is encoded as BINARY and requires a coersion
                  return coercions.get(javaType).getThriftType();
              }
              return array(getCollectionElementThriftTypeReference(elementType));
          }
          if (Map.class.isAssignableFrom(rawType)) {
              Type mapKeyType = getMapKeyType(javaType);
              Type mapValueType = getMapValueType(javaType);
              return map(getMapKeyThriftTypeReference(mapKeyType), getMapValueThriftTypeReference(mapValueType));
          }
          if (Set.class.isAssignableFrom(rawType)) {
              Type elementType = getIterableType(javaType);
              return set(getCollectionElementThriftTypeReference(elementType));
          }
          if (Iterable.class.isAssignableFrom(rawType)) {
              Type elementType = getIterableType(javaType);
              return list(getCollectionElementThriftTypeReference(elementType));
          }
          // The void type is used by service methods and is encoded as an empty struct
          if (void.class.isAssignableFrom(rawType) || Void.class.isAssignableFrom(rawType)) {
              return VOID;
          }
          if (isStructType(rawType)) {
              ThriftStructMetadata structMetadata = getThriftStructMetadata(javaType);
              // Unions are covered because a union looks like a struct with a single field.
              return struct(structMetadata);
          }
  
          if (ListenableFuture.class.isAssignableFrom(rawType)) {
              Type returnType = getFutureReturnType(javaType);
              // TODO: check that we aren't recursing through multiple futures
              // TODO: find a way to restrict this to return values only
              return getThriftType(returnType);
          }
  
          // coerce the type if possible
          TypeCoercion coercion = coercions.get(javaType);
          if (coercion != null) {
              return coercion.getThriftType();
          }
          throw new IllegalArgumentException("Type can not be coerced to a Thrift type: " + javaType);
      }