Friday 31 March 2017

Source Code Overview - Java Tutorials ( Page 1 of 4 )

Robert designed the applet with a fast load time in mind. He tries to keep the size of the applet to a minimum so that there is less code to send across the network. He also attempts to delay some of the loading and initializing of the applet until after the first image is displayed. As far as the user is concerned, the applet is running after the first image is fully displayed, even though there is a lot more work to be done.

The applet consists of three main classes and any number of transition classes. The three main classes are DynamicBillboard, BillData, and BillTransition. The DynamicBillboard class is a top-level Applet subclass that uses all of the other classes. The BillData class encapsulates a number of billboard attributes, including the image and the URL associated with the image. The BillTransition class is an abstract class that contains methods and attributes common to all transitions. The three main classes are described next, along with five popular transitions.


DynamicBillboard.java

This is the main applet class. It implements Runnable to include a thread that controls the continuous process of creation and animation of the transitions. The transition_classes array stores the names of the transition classes as strings. It uses strings because it loads these classes dynamically using the method java.lang.Class.forName(String). This allows the applet to put off the loading of these classes until they are first instantiated.

init( )
The init( ) method is called automatically when the applet is first loaded. Most applets use this method to perform all of their necessary initialization. Robert, however, decided to separate his initialization into two methods: init( ) and finishInit( ). The idea behind splitting up the initialization is to try to display the first image within the applet in the least amount of time, minimizing the time that the applet is showing a blank rectangle while it is loading and initializing. The only processing that is done in this init( ) method is that which is absolutely necessary to get initial content to the screen, because the browser will not call paint( ) until after init( ) returns.

The first thing that Robert does with init( ) is to change the background color of the applet and the parent frame in which the applet is embedded. In the past, the space that an applet uses on the screen was shown as a solid gray box while the applet was loading and initializing. This box would tend to stand out on pages that use a background color other than gray, which is just about every page created since 1994. Robert discovered a way around this problem. He found that applets always have a parent container in which they are embedded. Under both Netscape Navigator and Internet Explorer, this container is derived from the core Java class: java.awt.Container. Robert uses the methods inherited from java.awt.Component—setBackground( ) and repaint( )—to change the background color to the value of a bgcolor applet parameter. This makes the applet space stand out less than it does when it is gray. All this is done even before the applet begins to load the first image.

With newer browsers, this frame no longer defaults to gray but rather uses the background color of the page. Thus, today applets will not benefit from changing the background color. However, this appoach still illustrates an interesting technique, which you can adapt for other purposes.

After changing background colors, Robert’s applet reads in a parameter that tells how many different billboards there will be and then allocates an array of BillData objects based on this parameter. With the help of Math.random( ), a random billboard is chosen to start. parseBillData( ) is called to parse the parameters for this billboard.

parseBillData( )
This method creates and initializes the next billboard (BillData) object that the applet will use. It only gets called if the billboard object has not been created yet (the element corresponding to the next billboard object in the billboard array will be null).

Normally, parseBillData( ) calls the BillData method initPixels( ) after creating the new object to initialize a pixel array within the BillData object. The first time this method is called, however, the applet is still concentrating on getting the first image to the screen as fast as possible. It knows this because the reference to the image that is used to paint the applet is still null. So instead it sets the image variable and waits to call the processor-intensive initPixel( ) method until after the first image is loaded.

finishInit( )
After the first image is displayed on the screen, the applet can finish the rest of its initialization. This includes initializing the names of all the transition classes and initializing the pixels array for the first billboard and reading the target parameter.

finishInit( ) is called from the run( ) method of the applet. The run( ) method restarts from the top each time the user leaves and comes back to the page. When this happens, finishInit( ) will be called again. Since the applet has already finished its initialization, Robert does not want it to reinitialize everything. This is why the applet checks to see if the delay variable has already been initialized. If it has, then the applet can skip the rest of the initialization.

start( ) and stop( )
The start( ) and stop( ) methods respectively are called when the user comes to or leaves the page. They ensure that the applet thread that runs the transitions is on or off. If stop( ) is called while the applet is in the middle of running a transition, some data might be left in an improper state. Some variables are reset in start( ) to make sure the applet restarts with a new transition. In start( ), the mouse cursor is changed to a hand so that when the mouse cursor is over the applet, it will appear to be a link.

run( )
The run( ) method starts with a loop that waits for the first image to be fully loaded before proceeding. It then finishes the initialization of the applet by calling finishInit( ). From there, it enters the main loop of the program.

This main loop drives the transitions between billboards. Using the delay parameter passed in from the HTML to the applet, the applet calculates when the next transition is supposed to be run. While it is waiting, it prepares for the transition. It starts the preparation by determining which billboard is to be displayed next, parsing the billboard data from HTML parameters if this has not been done yet for this billboard. Then it randomly chooses which transition to run next, being careful not to let the applet run the same transition consecutively.

Once the applet has determined what transition will be run next, it creates a new instance of this transition class by dynamically loading the class using the String name and then creating a new instance of the class. The dynamic loading of the transition classes has a big impact on the loading time of the applet as a whole. Instead of every single class having to be downloaded before the applet starts, only three classes are sent initially: DynamicBillboard, BillData, and BillTransition. The other transition classes are only downloaded by the applet the first time they are needed. This reduces the initial download of the applet significantly. Some class files might not even need to be sent if the user leaves the page quickly.

Finally, the applet calls the init( ) method on the transition object, passing the applet and image pixels for the current and next billboard as parameters. This creates all the cell frames that are used to animate a transition. With the transition ready to go, the applet only need wait for the proper time to start the transition.

The applet performs the transition by using simple frame animation—drawing each cell in order onto the screen, with a short delay between each frame. The applet calls the toolkit method sync( ) just to be sure that the drawing of one cell does not take place before the previous cell has been shown on the screen. After the last cell is displayed, the applet draws the image from the next billboard onto the screen to complete the transition.

Following this, the mouse_over_applet flag is checked to see if the mouse cursor is currently over the applet. If so, the URL of the previous billboard is showing on the status bar and must be updated to reflect the URL of the new billboard. This is done with a call to the applet method showStatus( ). The applet has completed this transition and is now ready to begin the next one.

mouseMoved( ) and mouseExited( )
mouseMoved( ) and mouseExited( ) are used to change the text that appears on the status bar. When the mouse cursor is over the applet, the status bar is supposed to show the URL that the current billboard links to. So when mouseMoved( ) gets called, the applet shows the URL on the status bar. When mouseExited( ) is called, the URL text is removed from the status bar. Both methods also set the Boolean mouse_inside_ applet to the appropriate value. This variable is used in the run( ) method after a transition is run. If the mouse is positioned over the applet when the transition completes, then the applet knows to show the URL of the new billboard on the status bar.

mouseReleased( )
When the mouse button is pressed with the cursor over the applet and then released, the mouseReleased( ) method is called. The applet uses getAppletContext( ).show- Document( ) to send the browser to the URL that the current billboard points to. As Robert found out, sometimes browsers take a long time to display this new page. To keep the applet from running more transitions while the new page is waiting to load, stop( ) is called to force the main thread to quit. To let users know that the applet is loading the new page, the applet changes the mouse cursor to the wait cursor.

It is important to remember that users can come back to this page after going to a new page. The wait cursor will still be present on the applet when users come back. The start( ) method is always called when the user comes back to a page with an applet, so the applet resets the cursor to the hand cursor there.

The Code
Here is the source code for the DynamicBillboard class:

  import java.awt.*;
  import java.awt.event.*;
  import java.net.*;
  import java.awt.*;
  import java.awt.image.*;

  public class DynamicBillboard
         extends java.applet.Applet
         implements Runnable {

    BillData[] billboards;
    int current_billboard;
    int next_billboard;

    String[] transition_classes;
    Thread thread = null;
    Image image = null;
    long delay = -1;
    boolean mouse_inside_applet;
    String link_target_frame;
    boolean stopFlag;

    public void init() {
      String s = getParameter("bgcolor");
      if(s != null) {
        Color color = new Color(Integer.parseInt(s.substring(1),
                                16));
        setBackground(color);
        getParent().setBackground(color);
        getParent().repaint();
      }
      billboards = new
        BillData[Integer.parseInt(getParameter("billboards"))];
      current_billboard = next_billboard
                        = (int)(Math.random() *billboards.length);
      parseBillData();
    }

    void parseBillData() {
      String s = getParameter("bill" + next_billboard);
      int field_end = s.indexOf(",");
      Image new_image = getImage(getDocumentBase(),
                                 s.substring(0, field_end));
      URL link;
      try {
        link = new URL(getDocumentBase(),
                       s.substring(field_end + 1));
      }
      catch (java.net.MalformedURLException e) {
        e.printStackTrace();
        link = getDocumentBase();
      }
      billboards[next_billboard] = new BillData(link, new_image);
      if(image == null) {
        image = new_image;
      }
      else {
        prepareImage(new_image, this);
        billboards[next_billboard].initPixels(getSize().width,
                                              getSize().height);
      }
    }

    void finishInit() {
      if(delay != -1) {
        return;
      }
      delay = Long.parseLong(getParameter("delay"));

      link_target_frame = getParameter("target");
      if(link_target_frame == null) {
        link_target_frame = "_top";
      }

      String s = getParameter("transitions");
      int field_end = s.indexOf(",");

      int trans_count = Integer.parseInt(s.substring(0, field_end));
      transition_classes = new String[trans_count];
      for(--trans_count; trans_count > 0; --trans_count) {
        s = s.substring(field_end + 1);
        field_end = s.indexOf(",");
        transition_classes[trans_count] = s.substring(0, field_end);
      }
      transition_classes[0] = s.substring(field_end + 1);
      billboards[next_billboard].initPixels(getSize().width,
                                            getSize().height);
      mouse_inside_applet = false;
    }

    public void paint(Graphics g) {
      g.drawImage(image, 0, 0, this);
    }

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

    public void start() {
      next_billboard = current_billboard;
      image = billboards[current_billboard].image;
      setCursor(new Cursor(Cursor.HAND_CURSOR));
      if(thread == null) {
        thread = new Thread(this);
        thread.start();
      }
    }  

    public void stop() {
      if(thread != null) {
        stopFlag = true;
      }
    }

    public void run() {
      while((checkImage(image, this) & ImageObserver.ALLBITS) == 0){
        try { Thread.sleep(600); } catch (InterruptedException e) {}
      }
      finishInit();

      addMouseListener(new MyMouseAdapter());
      addMouseMotionListener(new MyMouseMotionAdapter());

      int last_transition_type = -1;
      BillTransition transition;
      long next_billboard_time;
      while(true) {
        if(stopFlag)
          return;
        next_billboard_time = System.currentTimeMillis() + delay;
        current_billboard = next_billboard;
        if(++next_billboard >= billboards.length) {
          next_billboard = 0;
        }
        if(billboards[next_billboard] == null) {
          parseBillData();
          try {Thread.sleep(120);} catch (InterruptedException e){}
        }
        int transition_type = (int)(Math.random() *
                                   (transition_classes.length - 1));
        if(transition_type >= last_transition_type) {
          ++transition_type;
        }
        last_transition_type = transition_type;

        try {
          String trans = transition_classes[last_transition_type];
          transition = (BillTransition)Class.forName(trans)
                                            .newInstance();
        }
        catch(Exception e) {
          e.printStackTrace();
          continue;
        }

        transition.init(this,billboards[current_billboard].
                                                   image_pixels,
          billboards[next_billboard].image_pixels);

        if(System.currentTimeMillis() < next_billboard_time) {
          try {
            Thread.sleep(next_billboard_time -
                         System.currentTimeMillis());
          } catch (InterruptedException e) { };
        }
        Graphics g = getGraphics();
        for(int c = 0; c < transition.cells.length; ++c) {
          image = transition.cells[c];
          g.drawImage(image, 0, 0, null);
          getToolkit().sync();
          try { Thread.sleep(transition.delay); }
          catch(InterruptedException e) { };
        }
        image = billboards[next_billboard].image;
        g.drawImage(image, 0, 0, null);
        getToolkit().sync();
        g.dispose();
        if(mouse_inside_applet == true) {
          showStatus(billboards[next_billboard].link.toExternalForm
                                                               ());
        }
        transition = null;
        try { Thread.sleep(120); } catch (InterruptedException e) {}
      }
    }
    public class MyMouseAdapter extends MouseAdapter {
      public void mouseExited(MouseEvent me) {
        mouse_inside_applet = false;
        showStatus("");
      }
      public void mouseReleased(MouseEvent me) {
        stop();
        setCursor(new Cursor(Cursor.WAIT_CURSOR));
        getAppletContext().showDocument(billboards
                       [current_billboard].link, link_target_frame);
      }
    }
    public class MyMouseMotionAdapter extends MouseMotionAdapter {
      public void mouseMoved(MouseEvent me) {
        mouse_inside_applet = true;
        showStatus(billboards[current_billboard].link.toExternalForm
                                                                ());
      }
    }
  }

No comments:

Post a Comment