/***********************************************************************
 * GarbageTracker.java - Source Code (requires java 1.1)               *
 * Version release date : May 23, 2001                                 *
 * Copyright (C) 1999-2001  Neural Semantics sprl, Belgium             *
 * Author : Michel Petre mpetre@neuralsemantics.com                    *
 *                                                                     *
 *     http://www.neuralsemantics.com/                                 *
 *                                                                     *
 * This code is released under GNU GPL license, version 2 or later.    *
 *                                                                     *
 *   This notice must remain intact in all copies of this code.        *
 *   This code is distributed WITHOUT ANY WARRANTY OF ANY KIND.        *
 *   The GNU GPL license can be found at :                             *
 *           http://www.gnu.org/copyleft/gpl.html                      *
 *                                                                     *
 * Feel free to use any portion of this code for your best purposes.   *
 ***********************************************************************/

import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class GarbageTracker extends Applet 
                            implements Runnable, ItemListener, 
                                       MouseListener, AdjustmentListener {

  // program name
  final static String TITLE = "The garbage tracker";

  // optimization modes
  final static int NONE = 0;
  final static int REUSE = 1;

  // number of color rectangles displayed on 1 line/1 column 
  // on the screen. The dynamic color area is divided into 
  // (MATRIX * MATRIX) rectangles.
  static int MATRIX = 20;

  // basic color increment at each pass
  static int COLOR_INCREMENT = 1;

  static String[] sPct = {"0%","20%","40%","60%","80%","100%"};

  // current optimization mode
  private int optimization;

  // width & height of the canvas
  private int cw, ch;
  // width & height of the gauge
  private int gw, gh;

  // array of colors (used in object recycling mode)
  private Color[][] colorRecycled;
  private Color[] colorReused;
  private int[] rgb;

  // current color index
  private int colorIndex;

  // current color increment (signed)
  private int colorIncrement;

  // current number of passes (for time calculation)
  private int nPass;

  // current start time for timer
  private long timerStart;

  // offscreen Image and Graphics objects
  private Image imgCanvas, imgGauge;
  private Graphics graphCanvas, graphGauge;

  // awt controls
  private Canvas canvas, gauge;
  private Panel linkPanel, control;
  private Checkbox checkGC = new Checkbox("Automatic GC");
  private Checkbox checkRecycle = new Checkbox("Optimized  ");
  private Scrollbar barSleep = 
          new Scrollbar(Scrollbar.HORIZONTAL, 1, 1, 3, 101);
  private Label labelSleep = new Label();
  private Label labelTime = new Label();
  private Label labelBar = new Label();

  // hyperlink objects
  private Label labelNS;
  private Color inactiveLinkColor = Color.yellow;
  private Color activeLinkColor = Color.white; 
  private Font inactiveLinkFont = new Font("Dialog", Font.PLAIN, 14);
  private Font activeLinkFont = new Font("Dialog", Font.ITALIC, 14);

  // flag automatic GC mode
  private boolean isAutomaticGC = true;

  // initial sleep time for thread
  private int sleepTime = 30;

  // thread
  private Thread runner;

  // this applet's Runtime object
  private Runtime runtime = Runtime.getRuntime();

//**********************************************************************
//                          Constructors
//**********************************************************************
  public GarbageTracker() { }

//**********************************************************************
//                             Methods
//**********************************************************************
  public void init() {
    setLayout(new BorderLayout());
    setBackground(Color.black);

    control = new Panel();
    this.composeControl();
    add("South", control);

    linkPanel = new Panel();
    this.composeLink();
    add("North", linkPanel);

    Panel west = new Panel();
    west.add(new Label("   "));
    add("West", west);

    gauge = new Canvas();
    gauge.setSize(60, 20);
    add("East", gauge);

    canvas = new Canvas();
    add("Center", canvas);
  }
//------------------------------------------------------------------------
  private void composeControl() {
    checkGC.addItemListener(this);
    checkGC.setState(isAutomaticGC);
    checkGC.setBackground(Color.darkGray);
    checkGC.setForeground(Color.white);

    checkRecycle.addItemListener(this);
    checkRecycle.setState(false);
    checkRecycle.setBackground(Color.darkGray);
    checkRecycle.setForeground(Color.white);

    labelBar.setText("    Sleep :");
    labelBar.setForeground(Color.white);

    barSleep.addAdjustmentListener(this);
    // assign fixed size to the scrollbar
    barSleep.setValue(sleepTime);
    barSleep.setSize(90, 16);
    Panel pSleep = new Panel();
    pSleep.setLayout(null);
    pSleep.add(barSleep);
    pSleep.setSize(90, 16);

    labelSleep.setText(sleepTime + " ms");
    labelSleep.setForeground(Color.white);

    labelTime.setText("    Cycle : ...          ");
    labelTime.setForeground(Color.yellow);

    Panel pControl = new Panel();
    pControl.setLayout(new FlowLayout(FlowLayout.CENTER));
    pControl.add(checkGC);
    pControl.add(checkRecycle);
    pControl.add(labelBar);
    pControl.add(pSleep);
    pControl.add(labelSleep);
    pControl.add(labelTime);

    control.setBackground(Color.darkGray);
    control.setLayout(new BorderLayout());
    control.add("South", pControl);
  }
//------------------------------------------------------------------------
  private void composeLink() {
    linkPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 5));
    linkPanel.setFont(inactiveLinkFont);
    linkPanel.setForeground(Color.yellow);

    // create a hand cursor for the hyperlink area
    Cursor linkCursor = new Cursor(Cursor.HAND_CURSOR);
    linkPanel.setCursor(linkCursor);

    Label labelName = new Label(TITLE + " \u00a9");
      labelName.setForeground(inactiveLinkColor);
      labelName.setAlignment(Label.RIGHT);
      labelName.addMouseListener(this);
    labelNS = new Label(" Neural Semantics   ");
      labelNS.setForeground(inactiveLinkColor);
      labelNS.setFont(inactiveLinkFont);
      labelNS.setAlignment(Label.LEFT);
      labelNS.addMouseListener(this);
    linkPanel.add(labelName);
    linkPanel.add(labelNS);
  }
//------------------------------------------------------------------------
  private void setTimer(long baseTime) {
    if (baseTime > 0) {
      double time = (double)((int)(baseTime - nPass * sleepTime) / 10) / 100d;
      labelTime.setText("    Cycle : " + time + " sec");
    }
    else {
      labelTime.setText("    Cycle : .... sec");
      colorIncrement = COLOR_INCREMENT;
      colorIndex = MATRIX + 1;
    }
    timerStart = System.currentTimeMillis();
    nPass = 0;
    control.doLayout();
  }

//**********************************************************************
//                               Thread
//**********************************************************************
  public void start() {
    // start the runner thread
    if (runner == null) {
      runner = new Thread(this);
      runner.start();
    }
  }
//------------------------------------------------------------------------
  public void run() {
    setTimer(0L);
    int colorMin = MATRIX;
    int colorMax = 255 - MATRIX;
    while (true) {
      if ((colorIndex >= colorMax) || (colorIndex <= colorMin)) {
        colorIncrement = -colorIncrement;
        setTimer(System.currentTimeMillis() - timerStart);
        nPass = 0;
      }
      colorIndex += colorIncrement;

      repaint();

      // call the garbage collector in non-automatic GC mode
      if (! isAutomaticGC)
        runtime.gc();

      try { Thread.sleep(sleepTime); }
      catch (InterruptedException e) { break; }

      nPass += 1;
    }
  }
//------------------------------------------------------------------------
  public void stop() {
    this.cleanup();
  }
//------------------------------------------------------------------------
  public void destroy() {
    this.cleanup();
  }
//------------------------------------------------------------------------
  private synchronized void cleanup() {
    // kill the runner thread
    if (runner != null) {
      try {
        runner.stop();
        runner.join();
        runner = null;
      }
      catch(Exception e) { }
    }
  }

//**********************************************************************
//                     MouseListener Interface
//**********************************************************************
  public void mouseClicked(MouseEvent e) {
  }
//-------------------------------------------
  public void mouseEntered(MouseEvent e) {
    // text color rollover
    labelNS.setForeground(activeLinkColor);
    labelNS.setFont(activeLinkFont);
    invalidate();
    transferFocus();
    showStatus("Click to connect to Neural Semantics home page");
  }
//-------------------------------------------
  public void mouseExited(MouseEvent e) {
    // text color rollover
    labelNS.setForeground(inactiveLinkColor);
    labelNS.setFont(inactiveLinkFont);
    invalidate();
    transferFocus();
    showStatus("");
  }
//-------------------------------------------
  public void mousePressed(MouseEvent e) {
    // link to Neural Semantics website
    try {
      java.net.URL url = new java.net.URL("http://www.neuralsemantics.com/");
      AppletContext ac = getAppletContext();
      if (ac != null)
        ac.showDocument(url);
    }
    catch(Exception ex){ }
  }
//-------------------------------------------
  public void mouseReleased(MouseEvent e) {
  }

//**********************************************************************
//                       ItemListener Interface
//**********************************************************************
  public void itemStateChanged(ItemEvent e) {
    Object source = e.getSource();
    if (source == checkGC) {
 	isAutomaticGC = checkGC.getState();
      setTimer(0L);
    }
    else if (source == checkRecycle) {
      if (checkRecycle.getState()) {
        optimization = REUSE;
        colorRecycled = null;
        if (colorReused == null)
          colorReused = new Color[256];
        for (int i=0;i<256;i++)
          colorReused[i] = new Color(i, i, i);
      }
      else
        optimization = NONE;
      setTimer(0L);
    }
  }

//**********************************************************************
//                     AdjustmentListener Interface
//**********************************************************************
  public void adjustmentValueChanged(AdjustmentEvent e) {
    Object source = e.getSource();

    // sleepTime range : 3 ms - 100 ms
    if (source == barSleep) {
      sleepTime = barSleep.getValue();
      labelSleep.setText(sleepTime + " ms");
      setTimer(0L);
    }
  }

//**********************************************************************
//                             Painter
//**********************************************************************
  public void update (Graphics g) {
    Graphics canvasGraph = canvas.getGraphics();
    Graphics gaugeGraph = gauge.getGraphics();
    if (imgCanvas == null) {
      Dimension dim = canvas.getSize();
      cw = dim.width;
      ch = dim.height;
      if ((cw > 0) && (ch > 0)) {
        imgCanvas = createImage(cw, ch);
        graphCanvas = imgCanvas.getGraphics();
      }
    }
    if (imgGauge == null) {
      Dimension dim = gauge.getSize();
      gw = dim.width;
      gh = dim.height;
      if ((gw > 0) && (gh > 0)) {
        imgGauge = createImage(gw, gh);
        graphGauge = imgGauge.getGraphics();
      }
    }
    if ((graphCanvas == null) || (graphGauge == null))
      return;

    graphCanvas.clearRect(0, 0, cw, ch);
    int cx = (int)(cw / MATRIX);
    int cy = (int)(ch / MATRIX);

    int idx;

    switch (optimization) {
      case NONE:
        // create (MATRIX * MATRIX) new Color objects.
        // previously created Color objects can be garbage collected
        for (int i=0; i<MATRIX; i++) {
          idx = colorIndex + colorIncrement*i;
          for (int j=0; j<MATRIX; j++) {
            graphCanvas.setColor(new Color(idx, idx, idx));
            graphCanvas.fillRect(i*cx, j*cy, cx, cy);
          }
        }
        break;
      case REUSE:
        // use the previously created Color objects, switching
        // their location
        for (int i=0; i<MATRIX; i++) {
          idx = colorIndex + colorIncrement*i;
          for (int j=0; j<MATRIX; j++) {
            graphCanvas.setColor(colorReused[idx]);
            graphCanvas.fillRect(i*cx, j*cy, cx, cy);
          }
        }
    }

    // draw gauge scale
    graphGauge.clearRect(0, 0, gw, gh);
    graphGauge.setColor(Color.lightGray);

    int gy = gh - 3;
    int dgy = (int)((gh - 60) / 10);
    graphGauge.drawLine(0, gy, gw, gy);
    graphGauge.drawLine(0, gy-dgy*10, gw, gy-dgy*10);
    for (int i=1;i<10;i++)
      graphGauge.drawLine(35, gy-dgy*i, gw, gy-dgy*i);
    for (int i=1;i<5;i++)
      graphGauge.drawString(sPct[i], 0, gy-dgy*i*2);

    // estimate JVM's current memory state (gauge height)
    int memlevel = (int)(runtime.freeMemory() * gh / runtime.totalMemory());

    // draw gauge
    graphGauge.setColor(Color.green);
    graphGauge.fillRect (45, gy-memlevel, 10, memlevel);

    graphGauge.drawString("      %", 0, 15);
    graphGauge.drawString("    free", 0, 30);
    graphGauge.drawString("memory", 0, 45);

    // paint canvas
    if (canvasGraph != null) {
      canvasGraph.drawImage(imgCanvas, 0, 0, canvas);
      canvasGraph.dispose();
    }
    // paint gauge
    if (gaugeGraph != null) {
      gaugeGraph.drawImage(imgGauge, 0, 0, gauge);
      gaugeGraph.dispose();
    }
  }
}