HW2 Solutions Page

Return to homework page.

UML Diagram of Clock. My solution will probably look different from
yours. I used inner classes (represented by nesting in UML) and I
made a class that extended JPanel on which to draw my clock.
As I tried to do it using chararacter graphics I kept my
diagram simple. It would be nice (but not mandoatory) to also include 
methods and fields.

________                      ______________________________________________
|JFrame|<|--------------------| cs151sec3hw2file1                          |
|______|             _ _ _ _ _|____________________________________________|
___________________  |        |                                            |
|Runnable         |<|-        |                                            |
|_________________|           |                   _______________________  |
                              |                   |DiskResource         |  |
                 ________     |  _____________    |_____________________|  |
                 |JPanel|<|------|:ClockPanel|            ^                |
                 |______|     |  |___________|    ________|____________    |
                              | __|     |         |                   |    |
                              | | ______|_________|___  ______________|__  |   
                              | | |:FortuneHandler  1|  |:Preferences  1|  |
                              | | |__________________|  |_______________|  |
                              | |______________________________|           |
                              |____________________________________________|                               

// cs151sec3hw2file - a clock program.

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import java.util.*;

/**
   This class is the main driver class for this application. It opens a 
   window with a clock on it. The type of clock is determined by the file 
   clock.pf as described on the homework page. Every 30 seconds a new 
   fortune is draw on the clock. Fortunes come from the file fortunes.

   @author Chris Pollett
   @version 1.0
*/
public class cs151sec3hw2file1 extends JFrame implements Runnable
   //implementing Runnable means has a run() method.
{
   protected Thread clockThread = null;  // thread used to redraw clock.
   protected Color color = Color.green; // color of

   /**
      Constructor launches the thread use to redraw the clock every second then
      adds a ClockPanel to the ContentPane. You were not required to create a
      subclass of JPanel on which to draw the clock. You could have directly 
      overidden this JFrame's paint method (you would have gotten a flickering
      animation, however). 
   */
   public cs151sec3hw2file1()
   {
      super("Clock");

      if(clockThread == null)
      {
         clockThread = new Thread(this);
                                  //uses this class' run method
         clockThread.start();
      }

      Container c= getContentPane();
      c.add(new ClockPanel());      
   }

   /**
      This is the method that is executed by the clockThread. It redraws
      the screen every second.
   */
   public void run()
   {
      while(Thread.currentThread() == clockThread)
      {
         repaint();
         try
         {
            Thread.currentThread().sleep(1000);
            
         }
         catch(InterruptedException ie)
         {
            System.err.println(ie.toString());
            System.exit(1);
         }
      }
   }

   /**
       Instantiates the two singleton classes Preferences and FortuneHandler.
       (i.e., so their data from disk is then read in.) Then shows the frame.
   */
   public static void main(String[] args) 
   {
      
      cs151sec3hw2file1 app= new cs151sec3hw2file1();

      Preferences pref = Preferences.getInstance();
      FortuneHandler.getInstance();

      app.setSize(pref.getWidth(),  pref.getHeight());   

      app.show();
      
      app.addWindowListener( //anonymous inner class
          new WindowAdapter() {
         public void windowClosing( WindowEvent e)
         {
            System.exit(0);
         }
          }
      );   
   }

/**
   This kind of GUI object should be added to a JFrame or subclass.
   Its purpose is to provide an area into which to draw a flicker
   free image of a clock.

   Notice it is defined within the definition of the class 
   cs151sec3hw2file1 so it is an inner class. That is why we
   are allowed to make it in the same file and still be public.
   This will mean the javadoc comments below will work.
   Inner classes provide another means to partition up namespaces 
   and can be used to fake packages in the case of these homeworks.
*/   
public class ClockPanel extends JPanel
{

   static final double PI = Math.PI;  // beloved constant
   static final int offsetFactor = 16; 	// used in centering clock
   static final int startNumber = 3; // 0 radians is 3 o'clock position
   static final int numbersOnFace = 12; // numbers on face of clock

   // lengths of ticks and of hand; radius to draw numbers

   static final double tickScaling =.95;
   static final double secScaling = .85;
   static final double minScaling = .75;
   static final double hourScaling = .5;
   static final double numberScaling = .85;


   String currentFortune;

   /**
       Constructor gets global references to resources used to determine 
       what the clock looks like then sets the panels color, and size.
   */
   public ClockPanel()
   {
      Preferences p = Preferences.getInstance();
      FortuneHandler f = FortuneHandler.getInstance();

      currentFortune = f.getFortune();
   
      setSize(p.getWidth(), p.getHeight());
      setBackground(Color.black);
   }

   /**
      This method is called whenever the ClockPanel is drawn. In our case,
      when the clockThread calls repaint on the cs151sec3hw2file1 object 
      that object tries to redraw all the components that have been added 
      to it and so it call this method.    

      @param g graphics context used to do drawing with. 
   */
   public void paintComponent(Graphics g) 
                          //called by browser when wants to redraw
   {
      Calendar calendar= Calendar.getInstance(); //is Singleton
      int hour = calendar.get(Calendar.HOUR_OF_DAY);
      int minute = calendar.get(Calendar.MINUTE);
      int second = calendar.get(Calendar.SECOND);

      Preferences p = Preferences.getInstance(); 
      FortuneHandler f = FortuneHandler.getInstance();

      super.paintComponent(g);
      g.setColor(color);
      g.setFont(p.getFont());

     // get a fortune and draw it

      if(second%30 == 0) currentFortune = f.getFortune();

      drawCenter(g, currentFortune, (int)(getSize().getWidth()/2),
                                    g.getFontMetrics().getHeight()/4);
   
      // draw clock

      if(p.isClockTypeAnalog()) paintAnalog(g, hour, minute,second);
      else paintDigital(g, hour, minute, second );   
   }

   /**
      This method draws an digital clock on the ClockPanel

      @param g the graphics content
      @param hour what hour it is
      @param minute what minute it is
      @param second what second it is
   */
   public void paintDigital(Graphics g, int hour, int minute, int second)
   {

      Preferences p = Preferences.getInstance();

      String timeString = ":" + minute/10 + minute % 10
                  + ":" + second/10 + second % 10;

      if(p.getMode().equals("24"))
         timeString = hour+timeString;
      else
      {
         timeString = (hour>numbersOnFace) ? 
                                 (hour-numbersOnFace) + timeString+"pm" 
                                : hour + timeString + "am"; 
      }      

      Dimension size = getSize();
      drawCenter(g, timeString, (int)(size.getWidth()/2), 
                                (int)(size.getHeight()/2));
   
   }

   /**
      This method draws an analog clock on the ClockPanel

      @param g the graphics content
      @param hour what hour it is
      @param minute what minute it is
      @param second what second it is
   */
   public void paintAnalog(Graphics g, int hour, int minute, int second)
   {

      //time to angle conversions   

      double secondAng = PI*second/30-PI/2;
      double minuteAng = PI*minute/30+(secondAng+PI/2)/60-PI/2;
      double hourAng = PI*hour/6+(minuteAng+PI/2)/12-PI/2;
   
      //calculate border around clock and clock dimensions

      Dimension dimension= getSize();
      int currentWidth = (int)dimension.getWidth();
      int currentHeight = (int)dimension.getHeight();
      int offSetX = currentWidth/offsetFactor;
      int offSetY = currentHeight/offsetFactor;
      int diameterX = currentWidth-2*offSetX;
      int diameterY = currentHeight-2*offSetY;
      int radiusX = diameterX/2;
      int radiusY = diameterY/2;
      int centerX = radiusX +offSetX;
      int centerY = radiusY + offSetY;      

      /*
        Draw face
      */

      g.setColor(Color.white);
      g.drawOval(offSetX, offSetY, diameterX, diameterY);

      drawFace(g, Color.white, centerX, centerY, radiusX, radiusY);

      /*
        Draw Hour, Minutes, Seconds 
      */

      drawHand(g, Color.blue, hourScaling, hourAng, centerX, centerY, 
               radiusX, radiusY);
      
      drawHand(g, Color.blue, minScaling, minuteAng, centerX, centerY, 
               radiusX, radiusY);

      drawHand(g, Color.red, secScaling, secondAng, centerX, centerY, 
               radiusX, radiusY);
            
   }

   /*
      Draws tickmarks and numbers on clock face
   */
   private void drawFace(Graphics g, Color c, 
                          int centerX, int centerY, int radiusX, int radiusY)
   {
      int clockNumber = startNumber;
      for(double theta=0 ; theta< 2*PI;  theta += PI/6)
      {
         //starting and ending positions of tick marks

         int endTickX = (int)(radiusX*Math.cos(theta));
         int endTickY = (int)(radiusY*Math.sin(theta));
         int startTickX =  (int)(tickScaling*endTickX);
         int startTickY = (int)(tickScaling*endTickY);

         g.drawLine(centerX+startTickX, centerY+startTickY,
                    centerX+endTickX, centerY+endTickY);

         Preferences p = Preferences.getInstance();
         if(p.getMode().equals("NumOn"))
            drawCenter(g,"" + clockNumber, 
              centerX + (int)(startTickX*numberScaling),
              centerY + (int)(startTickY*numberScaling));

         clockNumber++; //get set to draw next number
         clockNumber = ( clockNumber > numbersOnFace) ? 1: clockNumber;
      }

   }


   /*
      Draws a hand of the clock.
   */
   private void drawHand(Graphics g, Color c, double scaling, double angle, 
                         int centerX, int centerY, int radiusX, int radiusY)	
   {
      g.setColor(c);

      g.drawLine((int)(centerX+scaling*radiusX*Math.cos(angle)), 
                 (int)(centerY+scaling*radiusY*Math.sin(angle)),
                       centerX, centerY);	
   }

   /*
      Draws onto the context g the String string at location x, y.
   */
   private void drawCenter(Graphics g, String string, int x, int y)
   {
      FontMetrics fontMetrics = g.getFontMetrics();
      int charWidth = fontMetrics.charWidth('0');
      int charHeight = fontMetrics.getHeight();
      
      g.drawString(string, x - charWidth*string.length()/2, y +charHeight/4);
   }
}




/**
  This is a singleton class used to store data from the file clock.pf 
  concerning what the clock should look like.

  Again this is an inner class of cs151sec3hw2file1. Notice that
  it is static. Inner classes may use vars from object of the
  outer class type in their definition. So unless the inner class
  is static its definition might change depending on the value
  of these variables. This means inner classes are only allowed 
  to have static variables if they themselves are static and have only
  one definition.
*/
public static class Preferences extends DiskResource
{
   private boolean clockType;
   private String mode; 
   private int width;
   private int height;
   private Font font;
   private static Preferences theInstance = null;

   /**
      Method to get the only instance of this class.

      @return single instance of Preferences
   */
   public static Preferences getInstance()
   {
      if(theInstance == null)
         theInstance = new Preferences();
      return theInstance;
   }

   /*
      The default constructor calls DiskResource's constructor to read in
      the file clock.pf.
   */
   protected Preferences()
   {
      super("clock.pf");
   }

   /*
      Data in a clock.pf file is stored all on one line with items
      separated by `:'. We use a StringTokenizer to get at these
      items and store them in the appropriate internal variables
   */
   protected boolean parseResource(String resourceLine)
              throws Exception
   {
      StringTokenizer tokens = new StringTokenizer(resourceLine,":");

      clockType = (tokens.nextToken().equals("a")) ? true: false;
        mode = tokens.nextToken();
        width= Integer.parseInt(tokens.nextToken());
        height= Integer.parseInt(tokens.nextToken());
        String fontName= tokens.nextToken();
        String fontS= tokens.nextToken();

      int fontStyle=0;
      if(fontS.equals("PLAIN")) fontStyle = Font.PLAIN;
       if(fontS.equals("BOLD")) fontStyle = Font.BOLD;
      if(fontS.equals("ITALIC")) fontStyle = Font.ITALIC;

      int fontSize = Integer.parseInt(tokens.nextToken());

      font = new Font( fontName, fontStyle, fontSize);
      return true;
   }

   /**
      @return true if clock type is analog rather than digital
   */
   public boolean isClockTypeAnalog()
   {
      return clockType;
   }

   /**
      The mode of the clock is either 24 hours or 12 hours in the
      case of digital clock and either NumOn or NumOff in the case
      of analog clocks. NumOn means numbers should be draw on teh clock
      face.

      @return mode of clock
   */
   public String getMode()
   {
      return mode;
   }

   /**
      @return width of ClockPanel in pixels
   */
   public int getWidth()
   {
      return width;
   }

   /**
      @return height of ClockPanel in pixels
   */
   public int getHeight()
   {
      return height;
   }

   /**
      @return font to be used to draw letters on clock
   */
   public Font getFont()
   {
      return font;
   }
}



/**
   This class is used to manage access to a list of fortune Strings stored in the
   file fortunes.
*/

public static class FortuneHandler extends DiskResource
{
   private Vector fortunes;
   private static FortuneHandler theInstance = null;

   /**
      Method to get the only instance of this class.

      @return single instance of FortuneHandler
   */
   public static FortuneHandler getInstance()
   {
      if(theInstance == null)
         theInstance = new FortuneHandler();
      return theInstance;
   }

   /**
      Returns a randomly chosen fortune from the Vector of fortunes.

      @return a random fortune.
   */
   public String getFortune()
   {
      int size =fortunes.size();

      if(size == 0) return new String("No fortunes");

      else return (String)fortunes.get((int)(size*Math.random()));   
   }

   /*
      The default constructor calls DiskResource's constructor to read in
      the file fortune.
   */
   protected FortuneHandler()
   {
      super("fortunes");
   }

   /*
      resourceLine is a viewed as  a fortune. We just add it to
      our Vector of fortunes. Could have used an array instead of a Vector.
   */
   protected boolean parseResource(String resourceLine)
           throws Exception
   {
      if(fortunes == null)
         fortunes = new Vector();

      fortunes.add(resourceLine);
      return false;
   }

}

/**
   Classes that extend this class read data from a file in their constructor.
   How that data is parsed is determined by overriding parseResource.
*/
public static abstract class DiskResource
{

   /**
      Reads data from the file named name. Uses parseResource to try to 
      interpret it.

      @param name filename of file to be read.
   */
   public DiskResource(String name)
   {
      String line;
      boolean done = false;

      try
      {
         BufferedReader buf= new BufferedReader(new FileReader(name));

         while(!done && (line = buf.readLine()) != null)
         {
            done = parseResource(line);
         }
         buf.close();
      }
      catch(FileNotFoundException fe)
      {
         System.err.println("File not found");
         System.exit(1);
      }
      catch(IOException ie)
      {
         System.err.println("An I/O Exception occurred:"+ie.toString());
         System.exit(1);
      }
      catch(Exception ee)
      {
         System.err.println("DiskResource not parseble:"+ee.toString());
         System.exit(1);
      } 
   }

   /*
      When a concrete class is create from this class this method is
      overriden to determine how the data should be parsed. 
   */
   abstract protected boolean parseResource(String resourceLine)
                                                       throws Exception;
}

} //end cs151sec3hw2file1 class