/*
 * JBoss, Home of Professional Open Source  
 * Copyright 2010, Red Hat Middleware LLC, and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.shrinkwrap.impl.base.exporter;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;
import java.util.logging.Logger;

import junit.framework.TestCase;

import org.jboss.shrinkwrap.api.Archives;
import org.jboss.shrinkwrap.api.Asset;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.impl.base.asset.ByteArrayAsset;
import org.jboss.shrinkwrap.impl.base.io.IOUtil;
import org.junit.Test;

/**
 * Stress test to ensure that archives exported as ZIPs which 
 * have size larger than available RAM can be processed, proving 
 * that we're buffering the encoding process. 
 * 
 * SHRINKWRAP-116
 *
 * @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
 * @version $Revision: $
 */
public class ZipExporterStressTest extends ExportTestBase
{
   //-------------------------------------------------------------------------------------||
   // Class Members ----------------------------------------------------------------------||
   //-------------------------------------------------------------------------------------||

   /**
    * Logger
    */
   private static final Logger log = Logger.getLogger(ZipExporterStressTest.class.getName());

   /**
    * 2^20
    */
   private static BigDecimal MEGA = new BigDecimal(1024 * 1024);

   //-------------------------------------------------------------------------------------||
   // Tests ------------------------------------------------------------------------------||
   //-------------------------------------------------------------------------------------||

   /**
    * Ensures that we can export archives of large sizes without
    * leading to {@link OutOfMemoryError}
    * 
    * SHRINKWRAP-116
    */
   @Test
   public void exportHugeArchive() throws IOException
   {
      // Log
      log.info("exportHugeArchive");
      log.info("This test may take awhile as it's intended to fill memory");

      // Get an archive instance
      final JavaArchive archive = Archives.create("hugeArchive.jar", JavaArchive.class);

      // Approximate the free memory to start
      final Runtime runtime = Runtime.getRuntime();
      final long startFreeMemBytes = totalFreeMemory(runtime);
      long beforeExportFreeMemBytes = startFreeMemBytes;
      int counter = 0;
      // Loop through and add a MB Asset
      final String pathPrefix = "path";

      // Fill up the archive until we've got only 30% of memory left
      while (beforeExportFreeMemBytes > (startFreeMemBytes * .3))
      {
         archive.add(MegaByteAsset.newInstance(), pathPrefix + counter++);
         System.gc(); // Signal to the VM to try to clean up a bit, not the most reliable, but makes this OK on my machine
         beforeExportFreeMemBytes = totalFreeMemory(runtime);
         log.info("Current Free Memory (MB): " + this.megaBytesFromBytes(beforeExportFreeMemBytes));
      }
      log.info("Wrote: " + archive.toString());
      log.info("Started w/ free memory (MB): " + this.megaBytesFromBytes(startFreeMemBytes));
      log.info("Free memory before export (MB): " + this.megaBytesFromBytes(beforeExportFreeMemBytes));

      // Export; at this point we have less than 50% available memory so 
      // we can't carry the whole archive in RAM twice; this
      // should ensure the ZIP impl uses an internal buffer
      final InputStream in = archive.as(ZipExporter.class).exportZip();
      final CountingOutputStream out = new CountingOutputStream();

      // Copy, counting the final size of the exported ZIP
      IOUtil.copyWithClose(in, out);

      // Ensure we've just exported a ZIP larger than our available memory (proving we've buffered the encoding process)
      TestCase.assertTrue("Test setup failed; we should be writing out more bytes than we have free memory",
            out.bytesWritten > beforeExportFreeMemBytes);
      log.info("Final ZIP export was: " + this.megaBytesFromBytes(out.bytesWritten) + " MB");
      final long afterExportFreeMemBytes = totalFreeMemory(runtime);
      log.info("Free memory after export (MB): " + this.megaBytesFromBytes(afterExportFreeMemBytes));
   }

   //-------------------------------------------------------------------------------------||
   // Internal Helper Methods ------------------------------------------------------------||
   //-------------------------------------------------------------------------------------||

   /**
    * Returns the number of MB the specified number of bytes represents
    * @param bytes
    * @return
    */
   private BigDecimal megaBytesFromBytes(final long bytes)
   {
      return new BigDecimal(bytes).divide(MEGA).setScale(2, RoundingMode.HALF_UP);
   }

   /**
    * Obtains an estimate of the total amount of free memory available to the JVM
    * @param runtime
    * @return
    */
   private static long totalFreeMemory(final Runtime runtime)
   {
      return runtime.maxMemory() - runtime.totalMemory() + runtime.freeMemory();
   }

   /**
    * An {@link Asset} which contains a megabyte of dummy data
    *
    * @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
    */
   private static class MegaByteAsset extends ByteArrayAsset implements Asset
   {
      /**
       * Dummy megabyte
       */
      private static int MEGA = 1024 * 1024;

      private static final Random random = new Random();

      private MegaByteAsset(final byte[] content)
      {
         super(content);
      }

      static MegaByteAsset newInstance()
      {
         /**s
          * Bytes must be random/distributed so that compressing these in ZIP
          * isn't too efficient
          */
         final byte[] content = new byte[MEGA];
         random.nextBytes(content);
         return new MegaByteAsset(content);
      }
   }

   /**
    * {@link OutputStream} which does nothing but count the bytes written
    * 
    * @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
    * @version $Revision: $
    */
   private static class CountingOutputStream extends OutputStream
   {
      long bytesWritten = 0;

      @Override
      public void write(int b) throws IOException
      {
         bytesWritten++;
      }
   }

}

@@ -6425,17 +6425,16 @@
      /**
-s
 %0A       

Delete stray character