Effectively streams data from multiple opened AFF4Stream objects.

    Args:
      fds: A list of opened AFF4Stream (or AFF4Stream descendants) objects.

    Yields:
      Tuples (chunk, fd) where chunk is a binary blob of data and fd is an
      object from the fds argument. Chunks within one file are not shuffled:
      every file's chunks are yielded in order and the file is never truncated.

      The files themselves are grouped by their type and the order of the
      groups is non-deterministic. The order of the files within a single
      type group is the same as in the fds argument.

    Raises:
      ValueError: If one of items in the fds list is not an AFF4Stream.

      MissingChunksError: if one or more chunks are missing. This exception
      is only raised after all the files are read and their respective chunks
      are yielded. MultiStream does its best to skip the file entirely if
      one of its chunks is missing, but in case of very large files it's still
      possible to yield a truncated file.

def MultiStream(cls, fds):
    """Effectively streams data from multiple opened AFF4Stream objects.

    Args:
      fds: A list of opened AFF4Stream (or AFF4Stream descendants) objects.

    Yields:
      Tuples (chunk, fd) where chunk is a binary blob of data and fd is an
      object from the fds argument. Chunks within one file are not shuffled:
      every file's chunks are yielded in order and the file is never truncated.

      The files themselves are grouped by their type and the order of the
      groups is non-deterministic. The order of the files within a single
      type group is the same as in the fds argument.

    Raises:
      ValueError: If one of items in the fds list is not an AFF4Stream.

      MissingChunksError: if one or more chunks are missing. This exception
      is only raised after all the files are read and their respective chunks
      are yielded. MultiStream does its best to skip the file entirely if
      one of its chunks is missing, but in case of very large files it's still
      possible to yield a truncated file.
    """
    for fd in fds:
      if not isinstance(fd, AFF4Stream):
        raise ValueError("All object to be streamed have to inherit from "
                         "AFF4Stream (found one inheriting from %s)." %
                         (fd.__class__.__name__))

    classes_map = {}
    for fd in fds:
      classes_map.setdefault(fd.__class__, []).append(fd)

    for fd_class, fds in iteritems(classes_map):
      # pylint: disable=protected-access
      for fd, chunk, exception in fd_class._MultiStream(fds):
        yield fd, chunk, exception