// Purpose.  Flyweight design pattern
//
// Discussion.  Trying to use objects at very low levels of granularity is
// nice, but the overhead may be prohibitive.  Flyweight suggests removing the
// non-shareable state from the class, and having the client supply it when
// methods are called.  This places more responsibility on the client, but,
// considerably fewer instances of the Flyweight class are now created.
// Sharing of these instances is facilitated by introducing a Factory class
// that maintains a "cache" of existing Flyweights.

class Gazillion {
   private static int num = 0;
   private int        row, col;
   public Gazillion( int maxPerRow ) {
      row = num / maxPerRow;
      col = num % maxPerRow;
      num++;
   }
   void report() {
      System.out.print( " " + row + col );
}  }

public class FlyweightDemo {
   public static final int ROWS = 6, COLS = 10;

   public static void main( String[] args ) {
      Gazillion[][] matrix = new Gazillion[ROWS][COLS];
      for (int i=0; i < ROWS; i++)
         for (int j=0; j < COLS; j++)
            matrix[i][j] = new Gazillion( COLS );
      for (int i=0; i < ROWS; i++) {
         for (int j=0; j < COLS; j++)
            matrix[i][j].report();
         System.out.println();
}  }  }

// 00 01 02 03 04 05 06 07 08 09
// 10 11 12 13 14 15 16 17 18 19
// 20 21 22 23 24 25 26 27 28 29
// 30 31 32 33 34 35 36 37 38 39
// 40 41 42 43 44 45 46 47 48 49
// 50 51 52 53 54 55 56 57 58 59



// In this refactoring, the "row" state is considered shareable (within each
// row anyways), and the "col" state has been externalized (it is supplied by
// the client when report() is called).

class Gazillion {
   private int row;
   public Gazillion( int theRow ) {
      row = theRow;
      System.out.println( "ctor: " + row );
   }
   void report( int theCol ) {
      System.out.print( " " + row + theCol );
}  }

class Factory {
   private Gazillion[] pool;
   public Factory( int maxRows ) {
      pool = new Gazillion[maxRows];
   }
   public Gazillion getFlyweight( int theRow ) {
      if (pool[theRow] == null)
         pool[theRow] = new Gazillion( theRow );
      return pool[theRow];
}  }

public class FlyweightDemo {
   public static final int ROWS = 6, COLS = 10;

   public static void main( String[] args ) {
      Factory theFactory = new Factory( ROWS );
      for (int i=0; i < ROWS; i++) {
         for (int j=0; j < COLS; j++)
            theFactory.getFlyweight( i ).report( j );
         System.out.println();
}  }  }

// ctor: 0
//  00 01 02 03 04 05 06 07 08 09
// ctor: 1
//  10 11 12 13 14 15 16 17 18 19
// ctor: 2
//  20 21 22 23 24 25 26 27 28 29
// ctor: 3
//  30 31 32 33 34 35 36 37 38 39
// ctor: 4
//  40 41 42 43 44 45 46 47 48 49
// ctor: 5
//  50 51 52 53 54 55 56 57 58 59




// Purpose.  Flyweight design pattern

// 1. Identify shareable state (intrinsic) and non-shareable state (extrinsic)
// 2. Create a Factory that can return an existing object or a new object
// 3. The client must use the Factory instead of "new" to request objects
// 4. The client (or a third party) must provide/compute the extrinsic state

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

class FlyweightFactory {
   private static java.util.Map  ht = new java.util.TreeMap();
   private static int            sharedButtons = 0;
   private static ButtonListener bl = new ButtonListener();
   public static Button makeButton( String num ) {
      Button btn;
      if (ht.containsKey( num )) {
         // 1. Identify intrinsic state (Button label)
         // 2. Return an existing object   [The same Button cannot be added
         //    multiple times to a container, and, Buttons cannot be cloned.
         //    So - this is only simulating the sharing that the Flyweight
         //    pattern provides.]
         btn = new Button( ((Button)ht.get(num)).getLabel() );
         sharedButtons++;
      } else {
         // 2. Return a new object
         btn = new Button( num );
         ht.put( num, btn );
      }
      btn.addActionListener( bl );
      return btn;
   }
   public static void report() {
      System.out.print( "new Buttons - " + ht.size()
         + ", \"shared\" Buttons - " + sharedButtons + ", " );
      for (java.util.Iterator it = ht.keySet().iterator(); it.hasNext(); )
         System.out.print( it.next() + " " );
      System.out.println();
}  }

class ButtonListener implements ActionListener {
   public void actionPerformed( ActionEvent e) {
      Button      btn  = (Button) e.getSource();
      java.awt.Component[] btns = btn.getParent().getComponents();
      int i = 0;
      for ( ; i < btns.length; i++)
         if (btn == btns[i]) break;
      // 4. A third party must compute the extrinsic state (x and y)
      //    (the Button label is intrinsic state)
      System.out.println( "label-" + e.getActionCommand()
         + "  x-" + i/10   + "  y-" + i%10 );  // 1. Identify extrinsic state
}  }                                           //    (Button location)

public class FlyweightDemo {
   public static void main( String[] args ) {
      java.util.Random rn = new java.util.Random();
      Frame frame = new Frame( "Flyweight Demo" );
      frame.addWindowListener( new WindowAdapter() {
        public void windowClosing( WindowEvent e ) {
          System.exit( 0 );
      } } );
      frame.setLayout( new GridLayout( 10, 10 ) );
      // 1. Identify shareable and non-shareable state
      //    shareable - Button label, non-shareable - Button location
      for (int i=0; i < 10; i++)
         for (int j=0; j < 10; j++)
            // 3. The client must use the Factory to request objects
            frame.add( FlyweightFactory.makeButton(
               Integer.toString( rn.nextInt(15) ) ) );
      frame.pack();
      frame.setVisible( true );
      FlyweightFactory.report();
}  }

// new Buttons - 15, "shared" Buttons - 85, 0 1 10 11 12 13 14 2 3 4 5 6 7 8 9
// label-0  x-1  y-1
// label-0  x-2  y-2
// label-0  x-6  y-5
// label-0  x-2  y-5
// label-0  x-9  y-0
// label-1  x-9  y-9
// label-1  x-2  y-8
// label-1  x-5  y-8
// label-1  x-3  y-1




// Purpose.  heavyweight ColorBoxes ==> ColorBox Flyweights and a Factory
//          (1 thread per ColorBox)         of pooled HandlerThreads
//
// Discussion.  Creating a thread for each ColorBox is a much more straight-
// forward approach, but it doesn't scale when dozens of ColorBoxes are
// created.  Sharing a "pool" of threads across the collection of ColorBoxes
// requires more thought to set-up, but does not saturate "system resources"
// like the former approach does.
//
// In the implementation below, each ColorBox "wraps" itself with a Thread
// object.  The Thread object provides all the "threading functionality magic"
// and simply calls ColorBox's run() method when it is promoted from the
// "ready" state to the "running" state.  When each Thread/ColorBox is swapped
// into the CPU, it causes the ColorBox part of itself to change its color and
// then graciously gives up the CPU [by calling sleep()] so that other Threads
// may run.
//
// In the ThreadPool implementation, after the ColorBoxes are set-up, the
// ThreadPool creates and starts 8 HandlerThreads.  When a HandlerThread is
// swapped into the CPU, it gets a random ColorBox object from ThreadPool's
// private Vector, tells the ColorBox to change its color, and graciously
// returns to the "asleep" state.
//
// "You can typically make your threaded applications run FASTER by inserting
// calls to sleep() (with reasonably long durations)."  This definitely contri-
// butes to the perception that Threads are a "black art".  Not enough calls:
// monopolization of the CPU.  Not enough duration: time expiration interrupt
// events interrupt the running thread before it can finish useful work.

import java.awt.*;

class ColorBox extends Canvas implements Runnable {
   private int    pause;
   private Color  curColor = getColor();
   private static Color[]  colors = { Color.black, Color.blue, Color.cyan,
      Color.darkGray, Color.gray, Color.green, Color.lightGray, Color.red,
      Color.magenta, Color.orange, Color.pink, Color.white, Color.yellow };

   public ColorBox( int p ) {
      pause = p;
      new Thread( this ).start();
   }
   private static Color getColor() {
      return colors[ (int)(Math.random() * 1000) % colors.length ];
   }
   public void run() {
      while (true) {
         curColor = getColor();
         repaint();
         try { Thread.sleep( pause ); } catch( InterruptedException e ) { }
   }  }
   public void paint( Graphics g ) {
      g.setColor( curColor );
      g.fillRect( 0, 0, getWidth(), getHeight() );
}  }

public class ColorBoxes {
   public static void main( String[] args ) {
      int size = 8, pause = 10;
      if (args.length > 0) size  = Integer.parseInt( args[0] );
      if (args.length > 1) pause = Integer.parseInt( args[1] );
      Frame f = new FrameClose( "ColorBoxes - 1 thread per ColorBox" );
      f.setLayout( new GridLayout( size, size ) );
      for (int i=0; i < size*size; i++) f.add( new ColorBox( pause ) );
      f.setSize( 500, 400 );
      f.setVisible( true );
}  }

// D:> java ColorBoxes 18 50
//         produces 324 boxes/threads and 50 millisecond sleep()



// Purpose.  8 shared HandlerThreads in a ThreadPool.  The ColorBox class
// has now become a Flyweight: the color changing and painting capability
// remains "intrinsic", and the threaded behavior has been made "extrinsic".
// The ThreadPool class plays the role of the Factory.  As ColorBox objects
// are created, they register themselves with the ThreadPool object.  The
// latter launches 8 "handler" threads.  When each thread is swapped into
// the CPU, it selects a random Flyweight from the ThreadPool's cache, and
// asks the object to changeColor().

import java.awt.*;
import java.util.Vector;

class ColorBox2 extends Canvas {
   private Color  curColor = getColor();
   private static Color[]  colors = { Color.black, Color.blue, Color.cyan,
      Color.darkGray, Color.gray, Color.green, Color.lightGray, Color.red,
      Color.magenta, Color.orange, Color.pink, Color.white, Color.yellow };

   public ColorBox2( ThreadPool2 tp ) {
      tp.register( this );
   }
   private static Color getColor() {
      return colors[ (int)(Math.random() * 1000) % colors.length ];
   }
   public void changeColor() {
      curColor = getColor();
      repaint();
   }
   public void paint( Graphics g ) {
      g.setColor( curColor );
      g.fillRect( 0, 0, getWidth(), getHeight() );
}  }

class ThreadPool2 {
   private final int NUM_THREADS = 8;
   private Vector cboxes = new Vector();
   private int    pause;

   class HandlerThread extends Thread {
      public void run() {
         while (true) {
            ((ColorBox2) cboxes.elementAt(
               (int)(Math.random()*1000) % cboxes.size() )).changeColor();
            try { Thread.sleep( pause ); } catch( InterruptedException e ) { }
   }  }  }

   public ThreadPool2( int p ) {
      pause = p;
   }
   public void register( ColorBox2 r ) {
      cboxes.addElement( r );
   }
   public void start() {
      for (int i=0; i < NUM_THREADS; i++) new HandlerThread().start();
}  }

public class ColorBoxes2 {
   public static void main( String[] args ) {
      int size = 8, pause = 10;
      if (args.length > 0) size  = Integer.parseInt( args[0] );
      if (args.length > 1) pause = Integer.parseInt( args[1] );

      ThreadPool2 tp = new ThreadPool2( pause );

      Frame f = new FrameClose( "ColorBoxes2 - 8 shared HandlerThreads" );
      f.setLayout( new GridLayout( size, size ) );
      for (int i=0; i < size*size; i++)
         f.add( new ColorBox2( tp ) );
      f.setSize( 500, 400 );
      f.setVisible( true );
      tp.start();
}  }

// D:> java ColorBoxes 18 50
//         produces 324 boxes, 8 threads, and 50 millisecond sleep()
//             performance is very much improved