"We have a bit of a dead code problem," writes R.S., "most of the time, it's different versions (sometimes older, sometimes newer) of the same class that were created as part of a good-intentioned refactoring that was never quite completed."

"And then we have mysterious classes like these."

package com.initech.test;

import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.log4j.Logger;


/**
 * 0 475,734
 * <p>
 * 1 475,828
 * <p>
 * 10 476,250
 * <p>
 * 100 480,469
 * <p>
 * 1,000 522,656
 * <p>
 * 10,000 953,359
 * <p>
 * 100,000 5,073,000
 * <p>
 * 1,000,000 46,406,000
 * <p>
 * 10,000,000 458,456,000
 * <p>
 * ~45 bytes per rubbish count
 *
 * @author 
 */
public final class RubbishGenerator
    implements RubbishGeneratorMBean, Runnable
{
  private final static Logger LOG            = Logger.getLogger(RubbishGenerator.class);

  private final AtomicInteger workerCount    = new AtomicInteger(1);
  private final AtomicInteger rubbishPerLoop = new AtomicInteger(10000000);
  private final AtomicInteger loopDelayMs    = new AtomicInteger(1000);
  private final AtomicBoolean enabled        = new AtomicBoolean(false);

  private final AtomicInteger changeSerialNo = new AtomicInteger(0);

  private final AtomicLong    loopCount      = new AtomicLong(0);

  private ExecutorService     workerExecutor;
  private ExecutorService     controllerExecutor;

  public RubbishGenerator ()
  {
  }

  public static void main (final String[] args)
  {
    RubbishGenerator g = new RubbishGenerator();
    synchronized (g)
    {
      try
      {
        final Object o = g.generateRubbish();
        System.out.println("Rubbish creation completed");
        System.gc();
        g.wait(1000000);
        // so its not optimized away
        System.out.println(o);
      }
      catch (InterruptedException e)
      {
        e.printStackTrace();
      }
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see com.initech.test.RubbishGeneratorMBean#getRubbishPerLoop()
   */
  @Override
  public int getRubbishPerLoop ()
  {
    return rubbishPerLoop.get();
  }

  /*
   * (non-Javadoc)
   *
   * @see com.initech.test.RubbishGeneratorMBean#getLoopDelayMs()
   */
  @Override
  public int getLoopDelayMs ()
  {
    return loopDelayMs.get();
  }

  /*
   * (non-Javadoc)
   *
   * @see com.initech.test.RubbishGeneratorMBean#getThreadCount()
   */
  @Override
  public int getWorkerCount ()
  {
    return workerCount.get();
  }

  /*
   * (non-Javadoc)
   *
   * @see com.initech.test.RubbishGeneratorMBean#setRubbishPerLoop(int)
   */
  @Override
  public void setRubbishPerLoop (final int rubbishPerLoop)
  {
    synchronized (this)
    {
      if (rubbishPerLoop == this.rubbishPerLoop.get())
      {
        return;
      }
      if (rubbishPerLoop < 1)
      {
        throw new IllegalArgumentException("byteCount must be > 0, was: " + rubbishPerLoop);
      }
      this.rubbishPerLoop.set(rubbishPerLoop);
      changeSerialNo.incrementAndGet();

      this.notifyAll();
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see com.initech.test.RubbishGeneratorMBean#setLoopDelayMs(int)
   */
  @Override
  public void setLoopDelayMs (final int delayMs)
  {
    synchronized (this)
    {
      if (delayMs == loopDelayMs.get())
      {
        return;
      }
      if (delayMs < 1)
      {
        throw new IllegalArgumentException("delayMs must be > 0, was: " + delayMs);
      }
      loopDelayMs.set(delayMs);
      changeSerialNo.incrementAndGet();

      this.notifyAll();
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see com.initech.test.RubbishGeneratorMBean#setThreadCount(byte)
   */
  @Override
  public void setWorkerCount (final int workerCount)
  {
    synchronized (this)
    {
      if (workerCount == this.workerCount.get())
      {
        return;
      }
      if (workerCount < 1)
      {
        throw new IllegalArgumentException("workerCount must be > 0, was: " + workerCount);
      }
      this.workerCount.set(workerCount);
      changeSerialNo.incrementAndGet();

      this.notifyAll();
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see com.initech.test.RubbishGeneratorMBean#getEnabled()
   */
  @Override
  public boolean getEnabled ()
  {
    return enabled.get();
  }

  /*
   * (non-Javadoc)
   *
   * @see com.initech.test.RubbishGeneratorMBean#getLoopCount()
   */
  @Override
  public long getLoopCount ()
  {
    return loopCount.get();
  }

  /*
   * (non-Javadoc)
   *
   * @see com.initech.test.RubbishGeneratorMBean#setEnabled(boolean)
   */
  @Override
  public void setEnabled (final boolean enabled)
  {
    synchronized (this)
    {
      if (enabled == this.enabled.get())
      {
        return;
      }
      this.enabled.set(enabled);
      //changeSerialNo.incrementAndGet();

      // a notifyAll wouldn't matter here since we are synchronized and
      // there aren't any executor services or we are going to shutdown all the
      // executor services immediately.
      // this.notifyAll();

      if (!enabled)
      {
        shutdown();
      }
      else
      {
        startup();
      }
    }
  }

  private void startup ()
  {
    LOG.info("Starting up");
    synchronized (this)
    {
      if (controllerExecutor != null)
      {
        throw new IllegalStateException("Logic error: Startup should not be called while a controllerExecutor exists");
      }
      controllerExecutor = Executors.newSingleThreadExecutor();
      controllerExecutor.execute(this);
    }
  }

  public void shutdown ()
  {
    LOG.info("Shutting down");
    synchronized (this)
    {
      if (workerExecutor != null && !workerExecutor.isShutdown())
      {
        workerExecutor.shutdownNow();
      }
      if (controllerExecutor != null && !controllerExecutor.isShutdown())
      {
        controllerExecutor.shutdownNow();
      }
      workerExecutor = null;
      controllerExecutor = null;
    }
  }

  /**
   * Controller reconfigures rubbish collector on changes.
   */
  @Override
  public void run ()
  {
    synchronized (this)
    {
      while (true)
      {
        if (workerExecutor != null)
        {
          throw new IllegalStateException("Logic error workerExecutor was not null");
        }
        LOG.info("Starting new workers");
        LOG.info(String.format("workerCount=%d, rubbishCountPerLoop=%d, loopDelayMs=%d", workerCount.get(), rubbishPerLoop.get(), loopDelayMs.get()));
        workerExecutor = Executors.newFixedThreadPool(workerCount.get());
        for (int i = workerCount.get(); i > 0; i--)
        {
          workerExecutor.execute(new Worker());
        }
        waitForDisableOrConfigChange();
        if (!enabled.get())
        {
          LOG.info("Disabled, shutting down controller and workers");
          shutdown();
          return;
        }
        LOG.info("Config changed detected shutting down old workers");
        if (workerExecutor != null && !workerExecutor.isShutdown())
        {
          workerExecutor.shutdownNow();
        }
        workerExecutor = null;
      }
    }
  }

  /**
   * Must be called from block synchronized on RubbishGenerator instance.
   */
  private void waitForDisableOrConfigChange ()
  {
    final int serialNo = changeSerialNo.get();
    while (enabled.get() && serialNo == changeSerialNo.get())
    {
      try
      {
        this.wait();
      }
      catch (InterruptedException e)
      {
        LOG.info("Controller interrupted, shutting workers and controller down", e);
        shutdown();
        return;
      }
    }
  }

  /**
   * Generates rubbish
   */
  class Worker
      implements Runnable
  {
    final int serialNo = RubbishGenerator.this.changeSerialNo.get();

    Object    rubbishRef;

    @Override
    public void run ()
    {
      // if the serialNo changes, exit
      while (serialNo == changeSerialNo.get())
      {
        // null rubbish so don't exhaust memory with large rubbish
        rubbishRef = null;
        try
        {
          rubbishRef = generateRubbish();
          loopCount.incrementAndGet();
        }
        catch (final Exception e)
        {
          final String mesg = "Error generating rubbish, shutting worker down";
          LOG.error(mesg, e);
          throw new RuntimeException(mesg, e);
        }
        synchronized (RubbishGenerator.this)
        {
          if (!enabled.get() || serialNo != changeSerialNo.get()) {
            break;
          }
          try
          {
            RubbishGenerator.this.wait(loopDelayMs.get());
          }
          catch (final Exception e)
          {
            LOG.info("Exception, exiting", e);
            return;
          }
        }
      }
      LOG.info("Configuration changes occurred, shutting down");
    }
  }

  /**
   * For each count of rubbish should consume about 40 bytes, 24 for LinkedList
   * Entry and 16 for Rubbish instance.
   * <p>
   * Each entry consumes 24 bytes, 8 bytes for header, 4 bytes for prev, next,
   * and object, and 4 bytes to pad up to multiple of 8 bytes.
   *
   * @return
   */
  private Object generateRubbish ()
  {
    final LinkedList<Rubbish> rubbish = new LinkedList<Rubbish>();
    Rubbish prev = new Rubbish(null);
    Rubbish cur = new Rubbish(prev);
    prev.next = cur;
    for (int i = rubbishPerLoop.get(); i > 0; i--)
    {
      rubbish.add(prev);
      prev = cur;
      cur = new Rubbish(prev);
      prev.next = cur;
    }
    return rubbish;
  }

  /**
   * 8 byte header + 4 bytes for each pointer = 16 bytes
   * <p>
   * The extra linking is to make the rubbish collector have to do some work.
   */
  class Rubbish
  {
    Rubbish prev;
    Rubbish next;

    Rubbish (final Rubbish prev)
    {
      this.prev = prev;
    }
  }
}