/**
 * Copyright (c) 2012 to original author or authors
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package io.takari.aether.connector.test.suite;

import static org.eclipse.aether.transfer.TransferEvent.EventType.INITIATED;
import static org.eclipse.aether.transfer.TransferEvent.EventType.PROGRESSED;
import static org.eclipse.aether.transfer.TransferEvent.EventType.SUCCEEDED;
import static org.junit.Assert.assertArrayEquals;
import io.takari.aether.connector.test.suite.server.ResourceServer;
import io.tesla.webserver.WebServer;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicInteger;

import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.internal.test.util.TestFileUtils;
import org.eclipse.aether.metadata.DefaultMetadata;
import org.eclipse.aether.metadata.Metadata;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.spi.connector.ArtifactDownload;
import org.eclipse.aether.spi.connector.ArtifactUpload;
import org.eclipse.aether.spi.connector.MetadataDownload;
import org.eclipse.aether.spi.connector.MetadataUpload;
import org.eclipse.aether.spi.connector.RepositoryConnector;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.Transfer;
import org.eclipse.aether.transfer.AbstractTransferListener;
import org.eclipse.aether.transfer.NoRepositoryConnectorException;
import org.eclipse.aether.transfer.TransferCancelledException;
import org.eclipse.aether.transfer.TransferEvent;
import org.eclipse.aether.transfer.TransferEvent.EventType;
import org.eclipse.aether.transfer.TransferListener;
import org.eclipse.aether.transfer.TransferResource;
import org.junit.Assert;
import org.junit.Test;

/**
 * The ConnectorTestSuite bundles standard tests for {@link RepositoryConnector}s. The only thing the client code
 * must provide is a {@link RepositoryConnectorFactory}.
 * <p>
 * To use these tests, provide a (Junit4-)class extending this class, and provide a default constructor calling
 * {@link AetherConnectorTest#ConnectorTestSuite(ConnectorTestSetup)} with a self-implemented {@link ConnectorTestSetup}.
 */
public class AetherConnectorTest extends AetherTestCase {

  protected void configureServer(WebServer server) {
    addBehaviour("/*", new ResourceServer());
  }

  /**
   * Test successful event order.
   * 
   * @see TransferEventTester#testSuccessfulTransferEvents(RepositoryConnectorFactory, TestRepositorySystemSession,
   *      RemoteRepository)
   */
  public void testSuccessfulEvents() throws NoRepositoryConnectorException, IOException {
    testSuccessfulTransferEvents(repositoryConnectorFactory, session(), remoteRepository());
  }

  public void testFailedEvents() throws NoRepositoryConnectorException, IOException {
    testFailedTransferEvents(repositoryConnectorFactory, session(), remoteRepository());
  }

  public void testFileHandleLeakage() throws IOException, NoRepositoryConnectorException {

    Artifact artifact = new DefaultArtifact("testGroup", "testArtifact", "", "jar", "1-test");
    Metadata metadata = new DefaultMetadata("testGroup", "testArtifact", "1-test", "maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT);

    RepositoryConnector connector = connector();

    File tmpFile = TestFileUtils.createTempFile("testFileHandleLeakage");
    ArtifactUpload artUp = new ArtifactUpload(artifact, tmpFile);
    connector.put(Arrays.asList(artUp), null);
    assertTrue("Leaking file handle in artifact upload", tmpFile.delete());

    tmpFile = TestFileUtils.createTempFile("testFileHandleLeakage");
    MetadataUpload metaUp = new MetadataUpload(metadata, tmpFile);
    connector.put(null, Arrays.asList(metaUp));
    assertTrue("Leaking file handle in metadata upload", tmpFile.delete());

    tmpFile = TestFileUtils.createTempFile("testFileHandleLeakage");
    ArtifactDownload artDown = new ArtifactDownload(artifact, null, tmpFile, null);
    connector.get(Arrays.asList(artDown), null);
    new File(tmpFile.getAbsolutePath() + ".sha1").deleteOnExit();
    assertTrue("Leaking file handle in artifact download", tmpFile.delete());

    tmpFile = TestFileUtils.createTempFile("testFileHandleLeakage");
    MetadataDownload metaDown = new MetadataDownload(metadata, null, tmpFile, null);
    connector.get(null, Arrays.asList(metaDown));
    new File(tmpFile.getAbsolutePath() + ".sha1").deleteOnExit();
    assertTrue("Leaking file handle in metadata download", tmpFile.delete());

    connector.close();
  }

  private static class CountingTransferListener extends AbstractTransferListener {
    public final AtomicInteger successCount = new AtomicInteger();
    
    @Override
    public void transferSucceeded(TransferEvent event) {
      successCount.incrementAndGet();
    }
  }

  @Test
  public void testBlocking() throws NoRepositoryConnectorException, IOException {

    RepositoryConnector connector = connector();

    int count = 10;

    byte[] pattern = "tmpFile".getBytes("UTF-8");
    File tmpFile = TestFileUtils.createTempFile(pattern, 100000);

    CountingTransferListener artUpsCounter = new CountingTransferListener();
    CountingTransferListener metaUpsCounter = new CountingTransferListener();
    CountingTransferListener artDownsCounter = new CountingTransferListener();
    CountingTransferListener metaDownsCounter = new CountingTransferListener();

    List<ArtifactUpload> artUps =
        createTransfers(ArtifactUpload.class, count, tmpFile, artUpsCounter);
    List<MetadataUpload> metaUps =
        createTransfers(MetadataUpload.class, count, tmpFile, metaUpsCounter);
    List<ArtifactDownload> artDowns =
        createTransfers(ArtifactDownload.class, count, null, artDownsCounter);
    List<MetadataDownload> metaDowns =
        createTransfers(MetadataDownload.class, count, null, metaDownsCounter);

    // this should block until all transfers are done - racing condition, better way to test this?
    connector.put(artUps, metaUps);
    connector.get(artDowns, metaDowns);

    Assert.assertEquals(count, artUpsCounter.successCount.intValue());
    Assert.assertEquals(count, metaUpsCounter.successCount.intValue());
    Assert.assertEquals(count, artDownsCounter.successCount.intValue());
    Assert.assertEquals(count, metaDownsCounter.successCount.intValue());

    connector.close();
  }

  public void testMkdirConcurrencyBug() throws IOException, NoRepositoryConnectorException {
    
    RepositoryConnector connector = connector();
    File artifactFile = TestFileUtils.createTempFile("mkdirsBug0");
    File metadataFile = TestFileUtils.createTempFile("mkdirsBug1");

    int numTransfers = 2;

    ArtifactUpload[] artUps = new ArtifactUpload[numTransfers];
    MetadataUpload[] metaUps = new MetadataUpload[numTransfers];

    for (int i = 0; i < numTransfers; i++) {
      Artifact art = new DefaultArtifact("testGroup", "testArtifact", "", "jar", i + "-test");
      Metadata meta = new DefaultMetadata("testGroup", "testArtifact", i + "-test", "maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT);

      ArtifactUpload artUp = new ArtifactUpload(art, artifactFile);
      MetadataUpload metaUp = new MetadataUpload(meta, metadataFile);

      artUps[i] = artUp;
      metaUps[i] = metaUp;
    }

    connector.put(Arrays.asList(artUps), null);
    connector.put(null, Arrays.asList(metaUps));

    File localRepo = session().getLocalRepository().getBasedir();

    StringBuilder localPath = new StringBuilder(localRepo.getAbsolutePath());

    for (int i = 0; i < 50; i++) {
      localPath.append("/d");
    }

    
    ArtifactDownload[] artDowns = new ArtifactDownload[numTransfers];
    MetadataDownload[] metaDowns = new MetadataDownload[numTransfers];

    for (int m = 0; m < 20; m++) {
      CountingTransferListener artDownsCounter = new CountingTransferListener();
      CountingTransferListener metaDownsCounter = new CountingTransferListener();

      for (int i = 0; i < numTransfers; i++) {
        File artFile = new File(localPath.toString() + "/a" + i);
        File metaFile = new File(localPath.toString() + "/m" + i);

        Artifact art = new DefaultArtifact("testGroup", "testArtifact", "", "jar", i + "-test");
        Metadata meta = new DefaultMetadata("testGroup", "testArtifact", i + "-test", "maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT);

        ArtifactDownload artDown = new ArtifactDownload(art, null, artFile, RepositoryPolicy.CHECKSUM_POLICY_FAIL);
        artDown.setListener(artDownsCounter);
        MetadataDownload metaDown = new MetadataDownload(meta, null, metaFile, RepositoryPolicy.CHECKSUM_POLICY_FAIL);
        metaDown.setListener(metaDownsCounter);

        artDowns[i] = artDown;
        metaDowns[i] = metaDown;
      }

      connector.get(Arrays.asList(artDowns), Arrays.asList(metaDowns));

      Assert.assertEquals(numTransfers, artDownsCounter.successCount.intValue());
      Assert.assertEquals(numTransfers, metaDownsCounter.successCount.intValue());

      TestFileUtils.deleteFile(localRepo);
    }

    connector.close();
  }

  /**
   * See https://issues.sonatype.org/browse/AETHER-8
   */
  public void testTransferZeroBytesFile() throws IOException, NoRepositoryConnectorException {
    File emptyFile = TestFileUtils.createTempFile("");

    Artifact artifact = new DefaultArtifact("gid:aid:ext:ver");
    ArtifactUpload upA = new ArtifactUpload(artifact, emptyFile);
    File dir = TestFileUtils.createTempDir("con-test");
    File downAFile = new File(dir, "downA.file");
    downAFile.deleteOnExit();
    ArtifactDownload downA = new ArtifactDownload(artifact, "", downAFile, RepositoryPolicy.CHECKSUM_POLICY_FAIL);

    Metadata metadata = new DefaultMetadata("gid", "aid", "ver", "maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT);
    MetadataUpload upM = new MetadataUpload(metadata, emptyFile);
    File downMFile = new File(dir, "downM.file");
    downMFile.deleteOnExit();
    MetadataDownload downM = new MetadataDownload(metadata, "", downMFile, RepositoryPolicy.CHECKSUM_POLICY_FAIL);

    RepositoryConnector connector = connector();
    connector.put(Arrays.asList(upA), Arrays.asList(upM));
    connector.get(Arrays.asList(downA), Arrays.asList(downM));

    assertNull(String.valueOf(upA.getException()), upA.getException());
    assertNull(String.valueOf(upM.getException()), upM.getException());
    assertNull(String.valueOf(downA.getException()), downA.getException());
    assertNull(String.valueOf(downM.getException()), downM.getException());

    assertEquals(0, downAFile.length());
    assertEquals(0, downMFile.length());

    connector.close();
  }

  public void testProgressEventsDataBuffer() throws UnsupportedEncodingException, IOException, NoSuchAlgorithmException, NoRepositoryConnectorException {
    byte[] bytes = "These are the test contents.\n".getBytes("UTF-8");
    int count = 120000;
    MessageDigest digest = MessageDigest.getInstance("SHA-1");
    for (int i = 0; i < count; i++) {
      digest.update(bytes);
    }
    byte[] hash = digest.digest();

    File file = TestFileUtils.createTempFile(bytes, count);

    Artifact artifact = new DefaultArtifact("gid:aid:ext:ver");
    ArtifactUpload upA = new ArtifactUpload(artifact, file);

    File dir = TestFileUtils.createTempDir("con-test");
    File downAFile = new File(dir, "downA.file");
    downAFile.deleteOnExit();
    ArtifactDownload downA = new ArtifactDownload(artifact, "", downAFile, RepositoryPolicy.CHECKSUM_POLICY_FAIL);

    Metadata metadata = new DefaultMetadata("gid", "aid", "ver", "maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT);
    MetadataUpload upM = new MetadataUpload(metadata, file);
    File downMFile = new File(dir, "downM.file");
    downMFile.deleteOnExit();
    MetadataDownload downM = new MetadataDownload(metadata, "", downMFile, RepositoryPolicy.CHECKSUM_POLICY_FAIL);

    DigestingTransferListener listener = new DigestingTransferListener();
    upA.setListener(listener);
    upM.setListener(listener);
    downA.setListener(listener);
    downM.setListener(listener);
    

    RepositoryConnector connector = connector();
    connector.put(Arrays.asList(upA), null);
    assertArrayEquals(hash, listener.getHash());
    listener.rewind();
    connector.put(null, Arrays.asList(upM));
    assertArrayEquals(hash, listener.getHash());
    listener.rewind();
    connector.get(Arrays.asList(downA), null);
    assertArrayEquals(hash, listener.getHash());
    listener.rewind();
    connector.get(null, Arrays.asList(downM));
    assertArrayEquals(hash, listener.getHash());
    listener.rewind();

    connector.close();
  }

  private final class DigestingTransferListener implements TransferListener {

    private MessageDigest digest;

    private synchronized void initDigest() throws NoSuchAlgorithmException {
      digest = MessageDigest.getInstance("SHA-1");
    }

    public DigestingTransferListener() throws NoSuchAlgorithmException {
      initDigest();
    }

    public void rewind() throws NoSuchAlgorithmException {
      initDigest();
    }

    public void transferSucceeded(TransferEvent event) {
    }

    public void transferStarted(TransferEvent event) throws TransferCancelledException {
    }

    public synchronized void transferProgressed(TransferEvent event) throws TransferCancelledException {
      digest.update(event.getDataBuffer());
    }

    public void transferInitiated(TransferEvent event) throws TransferCancelledException {
    }

    public void transferFailed(TransferEvent event) {
    }

    public void transferCorrupted(TransferEvent event) throws TransferCancelledException {
    }

    public synchronized byte[] getHash() {
      return digest.digest();
    }
  }

  //
  // Events testing
  //

  /**
   * Test the order of events and their properties for the successful up- and download of artifact and metadata.
   */
  public static void testSuccessfulTransferEvents(RepositoryConnectorFactory factory, DefaultRepositorySystemSession session, RemoteRepository repository) throws NoRepositoryConnectorException,
      IOException {
    RecordingTransferListener listener = new RecordingTransferListener(session.getTransferListener());

    RepositoryConnector connector = factory.newInstance(session, repository);

    byte[] pattern = "tmpFile".getBytes();
    File tmpFile = TestFileUtils.createTempFile(pattern, 10000);
    long expectedBytes = tmpFile.length();

    Collection<ArtifactUpload> artUps = createTransfers(ArtifactUpload.class, 1, tmpFile, listener);
    Collection<ArtifactDownload> artDowns = createTransfers(ArtifactDownload.class, 1, tmpFile, listener);
    Collection<MetadataUpload> metaUps = createTransfers(MetadataUpload.class, 1, tmpFile, listener);
    Collection<MetadataDownload> metaDowns = createTransfers(MetadataDownload.class, 1, tmpFile, listener);

    connector.put(artUps, null);
    LinkedList<TransferEvent> events = new LinkedList<TransferEvent>(listener.getEvents());
    checkEvents(events, expectedBytes);
    listener.clear();

    connector.get(artDowns, null);
    events = new LinkedList<TransferEvent>(listener.getEvents());
    checkEvents(events, expectedBytes);
    listener.clear();

    connector.put(null, metaUps);
    events = new LinkedList<TransferEvent>(listener.getEvents());
    checkEvents(events, expectedBytes);
    listener.clear();

    connector.get(null, metaDowns);
    events = new LinkedList<TransferEvent>(listener.getEvents());
    checkEvents(events, expectedBytes);

    connector.close();
    session.setTransferListener(null);
  }

  private static void checkEvents(Queue<TransferEvent> events, long expectedBytes) {
    TransferEvent currentEvent = events.poll();
    String msg = "initiate event is missing";
    assertNotNull(msg, currentEvent);
    assertEquals(msg, INITIATED, currentEvent.getType());
    checkProperties(currentEvent);

    TransferResource expectedResource = currentEvent.getResource();

    currentEvent = events.poll();
    msg = "start event is missing";
    assertNotNull(msg, currentEvent);
    assertEquals(msg, TransferEvent.EventType.STARTED, currentEvent.getType());
    assertEquals("bad content length", expectedBytes, currentEvent.getResource().getContentLength());
    checkProperties(currentEvent);
    assertResourceEquals(expectedResource, currentEvent.getResource());

    EventType progressed = TransferEvent.EventType.PROGRESSED;
    EventType succeeded = TransferEvent.EventType.SUCCEEDED;

    TransferEvent succeedEvent = null;

    int dataLength = 0;
    long transferredBytes = 0;
    while ((currentEvent = events.poll()) != null) {
      EventType currentType = currentEvent.getType();

      assertResourceEquals(expectedResource, currentEvent.getResource());

      if (succeeded.equals(currentType)) {
        succeedEvent = currentEvent;
        checkProperties(currentEvent);
        break;
      } else {
        assertTrue("event is not 'succeeded' and not 'progressed'", progressed.equals(currentType));
        assertTrue("wrong order of progressed events, transferredSize got smaller, last = " + transferredBytes + ", current = " + currentEvent.getTransferredBytes(),
            currentEvent.getTransferredBytes() >= transferredBytes);
        assertEquals("bad content length", expectedBytes, currentEvent.getResource().getContentLength());
        transferredBytes = currentEvent.getTransferredBytes();
        dataLength += currentEvent.getDataBuffer().remaining();
        checkProperties(currentEvent);
      }
    }

    // all events consumed
    assertEquals("too many events left: " + events.toString(), 0, events.size());

    // test transferred size
    assertEquals("progress events transferred bytes don't match: data length does not add up", expectedBytes, dataLength);
    assertEquals("succeed event transferred bytes don't match", expectedBytes, succeedEvent.getTransferredBytes());
  }

  private static void assertResourceEquals(TransferResource expected, TransferResource actual) {
    assertEquals("TransferResource: content length does not match.", expected.getContentLength(), actual.getContentLength());
    assertEquals("TransferResource: file does not match.", expected.getFile(), actual.getFile());
    assertEquals("TransferResource: repo url does not match.", expected.getRepositoryUrl(), actual.getRepositoryUrl());
    assertEquals("TransferResource: transfer start time does not match.", expected.getTransferStartTime(), actual.getTransferStartTime());
    assertEquals("TransferResource: name does not match.", expected.getResourceName(), actual.getResourceName());
  }

  private static void checkProperties(TransferEvent event) {
    assertNotNull("resource is null for type: " + event.getType(), event.getResource());
    assertNotNull("request type is null for type: " + event.getType(), event.getRequestType());
    assertNotNull("type is null for type: " + event.getType(), event.getType());

    if (PROGRESSED.equals(event.getType())) {
      assertNotNull("data buffer is null for type: " + event.getType(), event.getDataBuffer());
      assertTrue("transferred byte is not set/not positive for type: " + event.getType(), event.getTransferredBytes() > -1);
    } else if (SUCCEEDED.equals(event.getType())) {
      assertTrue("transferred byte is not set/not positive for type: " + event.getType(), event.getTransferredBytes() > -1);
    }
  }

  /**
   * Test the order of events and their properties for the unsuccessful up- and download of artifact and metadata.
   * Failure is triggered by setting the file to transfer to {@code null} for uploads, and asking for a non-existent
   * item for downloads.
   */
  public static void testFailedTransferEvents(RepositoryConnectorFactory factory, DefaultRepositorySystemSession session, RemoteRepository repository) throws NoRepositoryConnectorException, IOException {
    RecordingTransferListener listener = new RecordingTransferListener(session.getTransferListener());

    RepositoryConnector connector = factory.newInstance(session, repository);

    byte[] pattern = "tmpFile".getBytes("us-ascii");
    File tmpFile = TestFileUtils.createTempFile(pattern, 10000);

    Collection<ArtifactUpload> artUps = createTransfers(ArtifactUpload.class, 1, null, listener);
    Collection<ArtifactDownload> artDowns = createTransfers(ArtifactDownload.class, 1, tmpFile, listener);
    Collection<MetadataUpload> metaUps = createTransfers(MetadataUpload.class, 1, null, listener);
    Collection<MetadataDownload> metaDowns = createTransfers(MetadataDownload.class, 1, tmpFile, listener);

    connector.put(artUps, null);
    LinkedList<TransferEvent> events = new LinkedList<TransferEvent>(listener.getEvents());
    checkFailedEvents(events, null);
    listener.clear();

    connector.get(artDowns, null);
    events = new LinkedList<TransferEvent>(listener.getEvents());
    checkFailedEvents(events, null);
    listener.clear();

    connector.put(null, metaUps);
    events = new LinkedList<TransferEvent>(listener.getEvents());
    checkFailedEvents(events, null);
    listener.clear();

    connector.get(null, metaDowns);
    events = new LinkedList<TransferEvent>(listener.getEvents());
    checkFailedEvents(events, null);

    connector.close();
    session.setTransferListener(null);
  }

  private static void checkFailedEvents(Queue<TransferEvent> events, Class<? extends Throwable> expectedError) {
    if (expectedError == null) {
      expectedError = Throwable.class;
    }

    TransferEvent currentEvent = events.poll();
    String msg = "initiate event is missing";
    assertNotNull(msg, currentEvent);
    assertEquals(msg, INITIATED, currentEvent.getType());
    checkProperties(currentEvent);

    currentEvent = events.poll();
    msg = "fail event is missing";
    assertNotNull(msg, currentEvent);
    assertEquals(msg, TransferEvent.EventType.FAILED, currentEvent.getType());
    checkProperties(currentEvent);
    assertNotNull("exception is missing for fail event", currentEvent.getException());
    Exception exception = currentEvent.getException();
    assertTrue("exception is of wrong type, should be instance of " + expectedError + " but was " + exception.getClass(), expectedError.isAssignableFrom(exception.getClass()));

    // all events consumed
    assertEquals("too many events left: " + events.toString(), 0, events.size());
  }

  //
  // Utils
  //
  /**
   * Creates transfer objects according to the given class. If the file parameter is {@code null}, a new temporary
   * file will be created for downloads. Uploads will just use the parameter as it is.
   */
  public static <T extends Transfer> List<T> createTransfers(Class<T> cls, int count, File file, TransferListener listener) {
    ArrayList<T> ret = new ArrayList<T>();

    for (int i = 0; i < count; i++) {
      String context = null;
      String checksumPolicy = RepositoryPolicy.CHECKSUM_POLICY_IGNORE;

      Object obj = null;
      if (cls.isAssignableFrom(ArtifactUpload.class)) {
        Artifact artifact = new DefaultArtifact("testGroup", "testArtifact", "sources", "jar", (i + 1) + "-test");
        ArtifactUpload artifactUpload = new ArtifactUpload(artifact, file);
        artifactUpload.setListener(listener);
        obj = artifactUpload;
      } else if (cls.isAssignableFrom(ArtifactDownload.class)) {
        try {
          Artifact artifact = new DefaultArtifact("testGroup", "testArtifact", "sources", "jar", (i + 1) + "-test");
          ArtifactDownload artifactDownload = new ArtifactDownload(artifact, context, safeFile(file), checksumPolicy);
          artifactDownload.setListener(listener);
          obj = artifactDownload;
        } catch (IOException e) {
          throw new RuntimeException(e.getMessage(), e);
        }
      } else if (cls.isAssignableFrom(MetadataUpload.class)) {
        Metadata metadata = new DefaultMetadata("testGroup", "testArtifact", (i + 1) + "-test", "jar", Metadata.Nature.RELEASE_OR_SNAPSHOT, file);
        MetadataUpload metadataUpload = new MetadataUpload(metadata, file);
        metadataUpload.setListener(listener);
        obj = metadataUpload;
      } else if (cls.isAssignableFrom(MetadataDownload.class)) {
        try {
          Metadata metadata = new DefaultMetadata("testGroup", "testArtifact", (i + 1) + "-test", "jar", Metadata.Nature.RELEASE_OR_SNAPSHOT, file);
          MetadataDownload metadataDownload = new MetadataDownload(metadata, context, safeFile(file), checksumPolicy);
          metadataDownload.setListener(listener);
          obj = metadataDownload;
        } catch (IOException e) {
          throw new RuntimeException(e.getMessage(), e);
        }
      }

      ret.add(cls.cast(obj));
    }

    return ret;
  }
  public static <T extends Transfer> List<T> createTransfers(Class<T> cls, int count, File file) {
    return createTransfers(cls, count, file, null);
  }

  private static File safeFile(File file) throws IOException {
    return file == null ? TestFileUtils.createTempFile("") : file;
  }
}
