CS 4773 Object Oriented Systems
Assignment 3 comments

Step 2

In this step we use addStringShower to add a stringShower

Here is the code in PingPongActionThread.

private Vector listeners;     // List of StringShower listeners
...
public void addStringShower(StringShower ss) {
   listeners.addElement(ss);
}

public void run() {
try {
 for(int i=0;i < count-1;i++) {
    showAll(id,i+": "+what);
    sleep(delay);    // wait until next time
    checkSuspend();
 }
}  catch (InterruptedException e) {}
showAll(id,(count-1)+": "+what+"\n"+what+" done");
}

private void showAll(int id, String str) {
StringShower ss;
for (int i=0;i < listeners.size();i++) {
   ss = (StringShower)listeners.elementAt(i);
   ss.showString(id,str);
}  
}   

Step 3

In this step the TextAreas are extended to TextAreaStringShower which implements StringShower.
Here is the change in the applet:
TextAreaStringShower outputCommon;
TextAreaStringShower outputPing;
TextAreaStringShower outputPong;
...
outputCommon = new TextAreaStringShower(18,18);
outputPing = new TextAreaStringShower(18,18);
outputPong = new TextAreaStringShower(18,18);
...
private void createPing() {
   ping = new PingPongActionThread(PINGID,"ping", 2000, 10);
   ping.addStringShower(this);
   ping.addStringShower(outputPing);
   ping.addStringShower(outputCommon);
}
...
void suspendThreads() {
   outputPing.showString(-1,"ping suspended");
   outputPong.showString(-1,"PONG suspended");
   ping.setSuspend(true);
   pong.setSuspend(true);
}
...
etc.
Here is what the TextAreaStringShower looks like:
import java.awt.*;

public class TextAreaStringShower extends TextArea
			  implements StringShower {

public TextAreaStringShower(int n, int m) {
super(n,m);
}

public void showString(int id, String str) {
append(str+"\n");
}
}

Step 4

In this step we make the canvas an external class and have it handle the resizing.

Here is the StringCanvas class

import java.awt.*;

public class StringCanvas extends Canvas {
private Image buffer;
private int currentWidth = 0;
private int currentHeight = 0;
private int imageWidth;
private int imageHeight;
private Graphics gc;
private Color foreColor = Color.black;
private Color backColor = Color.orange;

public void drawStrings(String[] strs) {
setupImages();
if (buffer == null) return;
gc.setColor(backColor);
gc.fillRect(0,0,imageWidth,imageHeight);
gc.setColor(foreColor);
if (strs == null) {
 repaint();
 return;
}  
for (int i=0;i < strs.length;i++)
 gc.drawString(strs[i],10,15*(i+1));
repaint();
}

private void setupImages() {
if ( (buffer != null) && (getBounds().width == currentWidth) &&
   (getBounds().height == currentHeight) ) return;
imageWidth = getBounds().width;
imageHeight = getBounds().height;
if ( (imageWidth <= 0) || (imageHeight <= 0) ) return;
buffer = createImage(imageWidth,imageHeight);
currentWidth = getBounds().width;
currentHeight = getBounds().height;
gc = buffer.getGraphics();
gc.setColor(Color.black);
}

public void update(Graphics g) {
paint(g);
}

public void paint(Graphics g) {
setupImages();
if (buffer == null) return;
g.drawImage(buffer,0,0,null);
}
}
The applet keeps track of the ping and pong counts. It ignores the String passed to it in showString and just uses the id to determine whether it was called by ping or pong.

Here is the applet's showString method:

public void showString(int id, String str) {
if (id == PINGID) {
 pingCount++;
 pingClip.play();
}  
else if (id == PONGID) {
 pongCount++;
 pongClip.play();
}  
repaint();
}
Here is the applet's paint method:
public void paint(Graphics g) {
strs[0] = "ping count = "+pingCount;
strs[1] = "pong count = "+pongCount;
if (useActive)
 strs[2] = "thread count"+getActiveCount();
else
 strs[2] = "";
mc.drawStrings(strs);
}

Step 5

In this step the canvas implements StringShower andgets strings directly from ping and pong. It ignores the strings and just uses the method to keep track of the ping and pong counts.

Here is the showString method of the StringCanvas class:

public void showString(int id, String str) {
if (id == PingPongActionThread.PINGID)
 pingCount++;
else if (id == PingPongActionThread.PONGID)
 pongCount++;
fillBuffer();
repaint();
}
And here is the paint method:
public void paint(Graphics g) {
fillBuffer();
if (buffer == null) return;
g.drawImage(buffer,0,0,null);
}
All of the work is in the fillBuffer method:
private synchronized void fillBuffer() {
setupImages();
if (buffer == null) return;
gc.setColor(backColor);
gc.fillRect(0,0,imageWidth,imageHeight);
gc.setColor(foreColor);
gc.drawString("ping count = "+pingCount,10,15);
gc.drawString("pong count = "+pongCount,10,30);
if (useActive)
 gc.drawString("thread count: "+Thread.activeCount(),10,45);
}
Since more than one thread can call showString, we make fileBuffer a synchronized method.

Why does this need to be synchronized?


Step 6

In this step we make the applet just contain a button and everything else is in a frame which pops up.

The applet still implements StringShower so that it can play the audio clips and start and stop the threads when necessary.

When the threads are created, the StringShowers need to be set up and this now has to be done by the frame, so we add methods to PintPongActionFrame to do this:

public void setupPingListeners(PingPongActionThread ppt) {
ppt.addStringShower(outputCommon);
ppt.addStringShower(outputPing);
ppt.addStringShower(mc);
}

public void setupPongListeners(PingPongActionThread ppt) {
ppt.addStringShower(outputCommon);
ppt.addStringShower(outputPong);
ppt.addStringShower(mc);
}
These are used in the applet as follows:
private void createPing() {
ping =
 new PingPongActionThread(PingPongActionThread.PINGID,"ping", 2000, 10);
ping.addStringShower(this);
ppf.setupPingListeners(ping);
}

private void createPong() {
pong =
 new PingPongActionThread(PingPongActionThread.PONGID,"PONG", 3000, 5);
pong.addStringShower(this);
ppf.setupPongListeners(pong);
}
Here is the entire frame code:
import java.awt.*;

public class PingPongActionFrame extends Frame implements StringShower {
TextAreaStringShower outputCommon;
TextAreaStringShower outputPing;
TextAreaStringShower outputPong;
Button startButton;
PingPongActionThread ping;
PingPongActionThread pong;
int pingCount = 0;
int pongCount = 0;
Color backColor = Color.lightGray;
Color foreColor = Color.black;
StringCanvas mc;
boolean useActive = true;
String[] strs;


public PingPongActionFrame(boolean useAcitve, int w, int h) {
super("Step 6 written by S. Robbins");
this.useActive = useActive;
setupLayout(w,h);
setVisible(true);
}

private void setupLayout(int w, int h) {
setBackground(backColor);
setForeground(foreColor);
Panel outputPanel = new Panel();
setLayout(new BorderLayout());
outputPanel.setLayout(new GridLayout(1,3));
outputCommon = new TextAreaStringShower(18,18);
outputPing = new TextAreaStringShower(18,18);
outputPong = new TextAreaStringShower(18,18);
outputCommon.setBackground(Color.yellow);
outputPing.setBackground(Color.cyan);
outputPong.setBackground(Color.cyan);
outputCommon.setForeground(foreColor);
outputPing.setForeground(foreColor);
outputPong.setForeground(foreColor);
outputPanel.add(outputPing);
outputPanel.add(outputCommon);
outputPanel.add(outputPong);
add(BorderLayout.NORTH,outputPanel);
mc = new StringCanvas(useActive);
add(BorderLayout.CENTER,mc);
setSize(w,h);
}

public void setupPingListeners(PingPongActionThread ppt) {
ppt.addStringShower(outputCommon);
ppt.addStringShower(outputPing);
ppt.addStringShower(mc);
}

public void setupPongListeners(PingPongActionThread ppt) {
ppt.addStringShower(outputCommon);
ppt.addStringShower(outputPong);
ppt.addStringShower(mc);
}

public void paint(Graphics g) {
mc.repaint();
}

public void showString(int id, String str) {
if (id == PingPongActionThread.PINGID)
 outputPing.showString(id,str);
else if (id == PingPongActionThread.PONGID)
 outputPong.showString(id,str);
outputCommon.showString(id,str);
}

}


CSC Cards for Step 6:

None of the classes from the standard Java distribution are included.

Interface: StringShower
Responsibilities: showString
Collaborations: none

Class: TextAreaStringShower
Responsibilities: showString
Collaborations: none

Class: StringCanvas
Responsibilities: showString
	  update
	  paint
Collaborations: PingPongActionThread
	StringShower

Class: PingPongActionThread
Responsibilities: addStringShower
	  run
	  setSuspend
Collaborations: StringShower


Class: PingPongActionFrame
Responsibilities: setupPingListeners
	  setupPongListeners
	  paint
	  showString
Collaborations: StringShower
	TextAreaStringShower
	PingPongActionThread
	StringCanvas

Class: PingPongActionApplet
Responsibilities: init
	  start
	  stop
	  showString
	  actionPerformed
Collaborations: StringShower
	PingPongActionThread
	PingPongActionFrame

The only problem with this is that the StringCanvas class should not have to know about the PingPongActionThread.

We can fix this by having StringCanvas display a line at a position in the canvas based on the id from the showString.

Here is what we can do to the StringCanvas

   private Vector strs;
...
   public StringCanvas() {
      super();
      strs = new Vector();
      repaint();
   }

   public void showString(int id, String str) {
      for (int i=strs.size();i < =id;i++)
         strs.addElement("");
      strs.setElementAt(str,id);
      fillBuffer();
      repaint();
   }

   private synchronized void fillBuffer() {
      setupImages();
      if (buffer == null) return;
      gc.setColor(backColor);
      gc.fillRect(0,0,imageWidth,imageHeight);
      gc.setColor(foreColor);
      for (int i=0;i < strs.size();i++)
         gc.drawString((String)strs.elementAt(i),10,15*(i+1));
   }
Note the initiali for loop in showString which makes sure we are not trying to put something past the end of the vector.

This implementation assumes that the ping and pong ids are set to the line numbers we want them to display at.