Software has become increasingly sophisticated and robust. The time allocated to develop it has decreased while its complexity has increased. To meet these conflicting demands, better tools for creating software were developed. Yet even with better tools, it is expensive and time-consuming to develop new software for every aspect of a project. Furthermore, often there are portions of a new project that are similar to work done on a preceding project. If the code from preceding projects is incorporated into the new project, time is saved not only on coding, but also on design and testing.
Because of the advantages of reusing code, modern languages have attempted to make code reuse easier. The object-oriented paradigm helps to encapsulate implementation details and promote reuse. Because Java is object-oriented, reuse of existing applets is an important consideration when creating new applets with similar features.
Java applets can be extended without modifying the existing code by merely extending the original classes and adding new ones. The advantage of adding code is the minimized chance of breaking any working applet. Although there is no guarantee that the extension will work the first time, there is no chance that the original applet will cease to operate or that other applets extending the original will be affected.
There are, however, times when it is advantageous to rewrite existing code to include new features rather than simply extending it, such as when there are fundamental changes to the understanding that led to the original design of the applet. The ramifications of such a change will depend on how widely the applet is used. Sometimes it is easier to release a completely new applet under a different name than simply leverage code from the existing applet.
The beginning of this chapter covers how to determine when reuse is appropriate based on the existing applet and the new requirements. Several examples of reusing code by extending applets are included. The chapter then presents an example in which rewriting is preferable to reuse.
One of the biggest advantages to an object-oriented programming language such as Java is the ease with which existing code can be reused. The capability to reuse code increases the programmer's ability to modify both applets and applications.
Applets can be extended by creating a new applet as an extension of an existing one. Adding functionality to existing applets decreases design and coding time, resulting in modifications being released faster. The decrease in time between requirements analysis and applet release increases the likelihood that the applet will meet user needs.
Adding functionality to existing applets makes it easier to have several related products or different versions of the same product. Each product or version can be the result of different extensions.
Reuse, in general, includes extending classes, adding new methods, overriding existing methods, or creating new classes to increase functionality. Reusing an applet implies that you will create a new class that is an extension of the original class. This extension is not a replacement; therefore, it must have a new name. However, because it is created by extending the original class, it will inherit portions of its functionality from that class.
Class reuse allows you to build larger, more complex, more sophisticated applets because you do not need to build the same pieces each time you start a new project. Benefits of class reuse include less code to develop, less code to maintain, and not having to redesign the same items repeatedly. As programmers learn more about the existing Java classes and increase their understanding of these classes, better ways to combine classes to provide the desired functionality will become apparent.
Using the same tools repeatedly, rather than switching regularly, enables the programmer to become thoroughly familiar with the classes. Reuse enables developers to create classes from a common base set so that many classes can be developed with the same features without having to recode the features.
Reuse is a benefit when it is easier to locate and include existing objects than it is to create new objects. Object-oriented programming languages in general make it easier to include existing code because objects are encapsulated and only interact with each other in well-defined ways. Now that you have learned how reusing and rewriting can be employed to add functionality to existing applets, you need to learn when it is appropriate to use each option.
Rewriting an applet means modifying one or more of the existing class definitions in the applet. This type of change can take many forms, including adding new class variables, changing the algorithm in a method, changing the interaction between methods of the class, or altering any underlying design decisions. When an applet is rewritten, it will have the same name as the original but will function differently. The original functionality may be enhanced or decreased, or may remain the same in the new applet. When you rewrite code, there is the potential of introducing errors in working applets and the danger of breaking classes that are an extension of the rewritten class.
Rewriting might not seem like a problem, but if the class has been used in several places, modifying it has the potential to affect several applets. Methods that are overridden in the extensions might no longer function as expected. Class-level variables used in the extension might be eliminated. New algorithms that improve the functioning of the class might be incompatible with the design of the class extensions.
The amount of challenge involved in rewriting an applet depends on how extensively the classes involved have been reused. For a single developer, there might be little risk in rewriting an existing applet. The developer can easily determine all the places where the applet has been used. However, for a multi-person development team with common object classes, changing a class can be a major undertaking. The team must locate all the places the class has been used and determine how the rewrite will affect those objects.
Changes that can be isolated and do not affect the overall design of the class are best made by extending the class. Changes that affect the core functionality of the class are best accomplished by rewriting. It may be prudent to rewrite using a new class name so that classes that are extensions of the existing one will continue to function correctly. Deciding between reusing an applet and rewriting it begins by accurately assessing the needs of the users.
Just as the first step in any development project is to determine the needs the program must fulfill, the beginning of any modification or enhancement is determining the requirements. This includes determining new functionality to be added, areas that could be improved, and perhaps some features that can be eliminated altogether. It is important to accurately understand the changes that are desired. A project that successfully implements unwanted changes is not truly successful.
Some questions to consider in determining the needs for a particular applet with regard to reusing or rewriting are
These questions are significant because they provide insight into the true requirements of the new applet. An understanding of all the existing functionality is needed so that all of it can be incorporated into any rewrite and tested in the test plan for the rewritten applet.
Studying ways to enhance the features of an applet to fulfill new needs can help determine which classes you need to extend. Asking about missing functionality indicates other ways the applet needs to be extended. Similarly, noting unused features allows for simplified redesign of an applet; unused features can be eliminated altogether.
Looking at these needs helps to determine if an applet should be extended or entirely rewritten. Suppose you have an applet that allows a user to enter two numbers, sums the two numbers, and displays the results. It is easy to imagine extending the applet to display the average of the two numbers as well. However, if you want an applet to sum a list of three or four numbers, it might be more appropriate to rewrite the applet because there will probably be modifications to the user input, code for checking when the input is complete, changes to the summation routine, and possibly changes to the display of the results to show how many numbers were added. As part of the rewrite, the applet could be made flexible enough to sum an arbitrarily long list of numbers as well.
Tip |
Although the coding effort involved in modifying an existing applet might not be as extensive as an initial development project, the analysis of requirements is as important and should be as extensive. |
When you have answered the questions about the current needs, you can begin to look at ways to design and implement the new features. Design is as important when extending an applet as it is when creating a new one. When making these changes, it is often helpful to find existing applets with functionality similar to the features you plan to add. This is why a working knowledge of a variety of Java objects is beneficial.
The best way to learn about the objects supplied with Java is to examine existing applets and applications. You can look at the ones supplied with this book, work through them, and then download more applets from the Internet. You should also review the API to determine existing classes that can make developing your applet easier.
Carefully examine existing classes and look at functionality from an object-oriented point of view. An applet that plays sound clips when a user presses a button is not very different from an applet that displays images or plays MPEG clips when the user presses a button. You might be able to convert an existing applet to a new task with just a few minor changes.
It is important to gain a thorough understanding of the classes that make up the existing applet to determine how to incorporate the changes. Be sure to examine how the objects in the applet communicate with one another and how a new object will affect that communication. For example, if you reduce the size of the applet, you might need to change the size of some of the objects it displays. Part of understanding the classes used in an applet is determining how objects are placed on the screen. Then you can see what must be done to add a new object or change the layout.
When adding a new method, look at all the objects it might encounter and think of all the ways it might be invoked. Consider the possible side effects of changes to class variables or objects. Although you can never take all possibilities into account, you might want to consider where future enhancements are likely to occur and how things can be structured now to incorporate them easily later.
You should consider whether parts of another existing applet are similar to parts of the enhancement. Perhaps the controls and user-input areas are similar to something that exists and only the display needs to change, or vice versa. If code can be leveraged from an existing applet, writing the extended applet will be faster and easier.
The challenge in analyzing existing objects lies in the sheer volume of objects from which to choose. You have all of the API at your disposal, plus all the applets you have written, plus the many applets that are available at no cost through the Internet. One way to simplify your first attempt at a development project is to search for an applet similar to the one you want to create and to modify it.
Appearance is the most obvious way to analyze existing objects. Does this object display the way I want? Does it have all the parts I need? This is somewhat deceiving because the actual appearance of the object is the easiest to change. If you like the functionality of an object but do not like the image it displays or the shape it has, you can extend the object and rewrite only the method(s) that affect the display. Normally, all of the display will be isolated in one or two methods, such as draw() or paint().
Besides appearance, consider the functionality of the object. Look for desired features an object possesses and determine if several objects working together are needed to create them. Study any features in the object that are unnecessary or unwanted to determine if they can be easily eliminated. Then determine any additional functionality you will need before the object can be used in the applet.
When studying the objects in an applet and considering new ones, you should also consider flexibility. Would it be better to rewrite an existing class at this time because it does not include enough options to allow for future expansion?
The hardest part in extending an existing object is being sure that your extensions are sufficient to bring about the changes you want without introducing any unwanted side effects. To do so, carefully examine the source code for the object. Pay close attention to when it creates its own methods and when it is using methods from a superclass. When you replace a method, you might want to use a call to the superclass to include the existing method as well as the one you create. Then again, it might be that you don't want the method of the object you are extending, but you do want the method of its superclass. Java is flexible enough to allow you to make that decision.
In most development efforts, even when creating a new applet, you will reuse existing classes. Indeed, it would be exhausting and pointless to create all the objects by extending the Object class directly. The capability to reuse existing objects makes Java a powerful language.
Rather than creating an object by extending the Object class, you should extend an existing object when it meets the following criteria:
Note |
Because applets are just a particular type of object, these same guidelines can be applied to them. |
There are four challenges to using existing objects:
Java assists with all four of these areas. It makes it easy to locate existing objects by supplying the developer with the API documentation in HTML form, including links to related objects and an easy-to-search index. Because the documentation is electronic, it can be extended to include new objects as they are developed. The widespread popularity of the Java language and its portability across platforms increase the possibility of a particular object existing. Searching for suitable objects available on the Net is simplified by the platform independence and object-oriented nature of the language.
Java's documentation features enable developers to supply comments that can help assess an object's applicability to a given situation. The encapsulation of objects makes it easier to determine if an existing object supplies all the functions needed or if new methods must be added to the object for the new applet. Java also makes it easy to create a simple applet and test the class to determine its robustness and suitability.
It is particularly easy to adapt existing Java classes to specific needs by extending them. There is no limit on the number of extensions to a class, so existing classes can be extended many times. Therefore, it is reasonable to create most new classes by extending existing ones rather than extending the Object class. This means that more code can be reused and customized to fit your needs.
Java's ability to extract documentation from the comments included in the class definition eases the task of maintaining a large collection of useful objects. Its packaging features enable developers to group related classes. Its object-oriented nature and encapsulation features mean that a particular class can be stored without worry that it will be "missing something" if it is used again somewhere else.
The Java language is clearly meant to assist with all four of the challenges of code reuse. Because it is so conducive to reusing code, you might wonder when it is appropriate to rewrite a Java object. This topic is discussed in the next section.
Because rewriting code is inherently more costly than reusing it, even more consideration should be given to the decision to rewrite an applet. Rewrite an applet in the following situations:
Rewriting is appropriate if the core functions of the applet have changed, if the assumptions on which the applet is based have changed, or if a better method of accomplishing the applet's task is found. Rewriting also may be necessary if the applet is communicating with another program and the interface is modified.
An applet is just a particular type of object, so it is not surprising that rewriting a single object is as challenging as rewriting an entire applet. Before rewriting an applet, it is important to isolate all the places that the object is used. Determine how the changes will affect each applet where the object appears and if the changes are desired in all of them. If the changes are desired only in some of the applets, you are better off copying the code to a new object class, making the changes there, and modifying only the applets that need the change so they point to the new class. This is the safest and often best way to modify any existing class.
Before presenting an example of rewriting an applet, the following sections illustrate several examples of applet extension. This demonstrates the ease with which applets can be extended. It also assists in establishing the circumstances when extending is better than rewriting and vice versa.
The steps for extending an applet are similar to the steps for constructing a new one. You must design the modification, code it, and test it. Testing is even more important when modifying existing applets because you must ensure that no existing functionality is lost, so it is discussed in detail in the "Testing the Extended Applet" section in this chapter.
You must take care when extending classes, and when creating them in the first place, to choose new class names that do not conflict with other classes in use. This potential can be reduced by carefully choosing names and by grouping classes into appropriate packages. Classes in the same package must have unique class names.
Note |
When considering the possibility of naming conflicts, keep in mind the five levels of the namespace used by Java. If two packages exist with classes having the same name, the package name is used to differentiate between the classes in an applet. |
This section presents three increasingly complex examples of extending existing applets. It concludes with an example of an applet rewrite. Notice that the amount of work involved in the extension does not necessarily depend on the complexity of the initial applet.
The first example shows how simple extending an applet can be. You will add another parameter to the ScrollText applet to allow the HTML document to specify the background color of the applet. With this enhancement, the applet can run with the same background color as the document and the text will appear to be scrolling without a rectangular box around it.
The design of this modification is straightforward. The extended applet will read the new parameter and set the new background color at initialization. If the background parameter is not passed from the HTML file, the applet will set the background to white. For simplicity, the applet accepts input as a single integer in base 10. However, the program could easily expect three integers, three floats, or a single hex number such as HTML uses.
To create this extension, you will extend the existing ScrollText class to create a new class called ScrollText2. You need to create a .java file containing the code for the new class. This file must go in the same directory as the existing ScrollText file so that the Java compiler can find the original ScrollText class. The Java compiler needs to read the ScrollText.class file before extending the class.
Technical Note |
When extending a class, the Java compiler needs to locate the original class file. If it cannot locate the original class file but can locate the original source file, it will attempt to compile the original source code before compiling the new source code file. Therefore, if the ScrollText.java file containing the original class and the ScrollText2.java file containing the extended class are in the same directory, the Java compiler will first compile the ScrollText.java file to obtain the necessary class file and then compile the ScrollText2.java file. The result of this is a ScrollText.class file and a ScrollText2.class file. |
In the new class, you will use the Color class, so you must import java.awt.Color. This is shown at the top of Listing 16.1. The new class will have two new class-level variables. The first will hold the string containing the input value of the new parameter, and the second is an object in the Color class.
The new class only has one method, init(). This method overrides the init() method in ScrollText. However, the first line of code in the new init() method calls the init() method of its superclass. This means the init() method in the original ScrollText class is used to read and store the parameters that are defined there. When that is completed, the init() method for ScrollText2 reads the parameter supplied under the name background. If the value of this parameter is null, the backColor object is set to a default background color of white. If the parameter is not null, it is converted to an integer and used to define a new color in the backColor object. In the final line of the method in Listing 16.1, the backColor object is used by the method setBackground(), thereby setting the new background color.
Note |
For simplicity's sake, the applet accepts color input as a single integer in base 10. This means the value for the background parameter may be any integer between 0 and 16,777,216, which is the range of colors on Java's 16.7 million-color palette. |
Listing 16.1. The ScrollText2 applet.
import java.awt.Color;
/**
* Peter Norton's Guide to Java Programming
* The ScrollText2 Applet
* This applet is used to scroll a text banner across the screen
* and is an extension of the ScrollText Applet
* The applet takes BACKGROUND, TEXT, WIDTH, and HEIGHT
* as parameters.
*/
public class ScrollText2 extends ScrollText {
String bs = null; // Input string containing background color
Color backColor; // Background color
/* Setup width, height, and display text */
public void init() {
super.init(); // Do original initialization
bs = getParameter("background");
if (bs == null){ // Read color as input, if not found use default
backColor = Color.white;
} else {
// Convert color string to color
backColor = new Color(Integer.parseInt(bs));
}
setBackground(backColor); // Set the background color according to input
}
}
As you can see, the code for this example is quite short; however, although the change is small, it is reasonable to extend the existing class rather than to modify it. By extending the class, you eliminate the risk of breaking working HTML pages that use the applet as defined. You also provide two different versions of the applet, so the Web-page designer can select the most appropriate version.
To compile the applet, save it to the file ScrollText2.java in the same directory as ScrollText.java. Then change to the directory containing the ScrollText2.java file and type the following at the command prompt:
javac ScrollText2.java
The new ScrollText2 class can be used in an HTML file by including the following:
<APPLET CODE=ScrollText2 WIDTH=400 HEIGHT=200></APPLET>
This example and the next both modify the screen displayed to the user. The third example is somewhat more complex because it involves multiple classes and requires that several methods be modified.
In this section, you will extend the RGB2Hex applet by adding a color wheel in the middle of the applet's canvas. Having a color wheel will provide a means of contrasting the color the user has entered with other existing colors and will assist your users in picking values for the color they want.
This new functionality meets the criteria for applet reuse because most of the existing applet remains the same. The core functionality of the applet is unchanged, as is its overall operation. Although you must make changes in several parts of the code, these sections are easily isolated and extended.
There are tradeoffs in any software-construction or -modification project. In this case, you could have created the color wheel as a separate class and included a component of that class on the RGBcanvas. Alternatively, you could modify the paint() method of the RGBcanvas class to paint the color wheel after it colors the canvas. In this case, because the color wheel is not required to perform any task, the second option is best. However, you will isolate the additions by creating a new method, paintwheel(), which will display the color wheel. The code for the new method follows:
public void paintwheel(Graphics g,int wheel_size,int wheel_x,int wheel_y){
g.setColor(Color.red);
g.fillArc(wheel_x, wheel_y, wheel_size, wheel_size, 0, 60);
g.setColor(Color.orange);
g.fillArc(wheel_x, wheel_y, wheel_size, wheel_size, 60, 60);
g.setColor(Color.yellow);
g.fillArc(wheel_x, wheel_y, wheel_size, wheel_size, 120, 60);
g.setColor(Color.green);
g.fillArc(wheel_x, wheel_y, wheel_size, wheel_size, 180, 60);
g.setColor(Color.blue);
g.fillArc(wheel_x, wheel_y, wheel_size, wheel_size, 240, 60);
g.setColor(new Color(150,0,150));
g.fillArc(wheel_x, wheel_y, wheel_size, wheel_size, 300, 60);
}
The paintwheel() method requires that the caller supply the Graphics object on which the wheel will be drawn. This works because the paint() method calling the paintwheel() method will have a Graphics object to pass. paintwheel() also requires that the caller supply the x and y coordinates at which to draw the wheel and the size of the wheel.
The method uses the setColor() and fillArc() methods from the Graphics class to draw a color wheel with six colors at the location specified. The setColor() method requires a Color object as a parameter. It sets the default color to the value it is passed. The fillArc() method creates an arc on the screen using the default color that was set by setColor(). The arcs are drawn in a circle using the same x,y coordinates. Six 60-degree angles are used to create a full circle. The colors used are all defined colors in the Color class of the Java API, except for the purple arc because purple is not part of the existing Color class. Purple is created as a new color in the paintwheel() method and then passed to the setColor() method.
Another alternative would be to create a Color class that includes a Color.Purple variable. To keep the extended applet as short as possible, however, this option was not chosen. It is sometimes faster and easier to define an instance of an object with a specific property, such as the color purple, than to extend the entire class to include that property. On the other hand, if you were to create several applets using a specific color scheme that included shades of purple, you might want to define a class called ShadesOfPurple that would include variables such as PalePurple, LightPurple, MediumPurple, and DarkPurple. The class ShadesOfPurple would have to be formed by extending the Object class because the Color class is declared as final in the API, meaning that it cannot be extended directly.
The paintwheel() method draws a color wheel, but it must be incorporated into the applet. The method is included in a new class formed by extending the RGBcanvas class. The new class is called RGBcanvas2 and is declared as follows:
class RGBcanvas2 extends RGBcanvas {
The new class's one new method is paintwheel(), which overrides the paint() method from RGBcanvas and inherits the remainder of its features. If you had created the color wheel as a separate object, you would have had to redraw the wheel each time you painted the canvas so that the wheel would remain on top of the canvas. This would have meant changing the controls to draw both the canvas and the wheel. Sometimes the best way to incorporate a change is the one that involves changing the fewest methods.
In this case, you want the color wheel to appear on the canvas. Furthermore, the color wheel should appear no matter how many times the canvas is redrawn. Otherwise, the paint() method of the RGBcanvas2 class should perform all the tasks the paint method in the original RGBcanvas class performs. Therefore, the paint() method in the new class contains the code from the paint() method in the RGBcanvas class. Following that, it will call the method defined previously to paint the color wheel on the display using the following line of code:
paintwheel(g, r.height/2, r.width/2 - r.height/4, r.height/4);
This is a complicated-looking line of code. Let's examine it more closely to see what it accomplishes. The paintwheel() method defined earlier takes four parameters. The first is the Graphics object on which to paint. This is supplied by the paint() method in the RGBcanvas class and is the first parameter passed on the code line.
The next parameter specified is the diameter of the color wheel. Because the area of the RGBcanvas object is wider than it is tall, use one-half the height as the diameter of the wheel. Conveniently, the paint() method of RGBcanvas already calls the bounds() method to determine its size. It stores this information in the rectangle object r that is referenced in the code line. The height of the rectangle is r.height. The wheel should have a diameter of half the height, so pass r.height/2 as the size of the wheel.
The last two parameters are the x and y coordinates for the lower-left corner of the region where you want the wheel drawn. This is required to accommodate the drawArc()method used in the paintwheel() method. You want the wheel to be centered from top to bottom and from left to right, and you know that its diameter is half the height of the box. Therefore, the y coordinate of the lower-left corner is set at one-quarter of the height of the box, or r.height/4.
The x coordinate is a little more complicated because the box is wider than it is tall. First, you determine the center of the box, r.width/2. Then subtract half the width of the circle you will draw. The total height of the circle is r.height/2, so half the height of the circle is r.height/4. Therefore, the x coordinate for the paintwheel() method is r.width/2-r.height/4.
The complete paint() method appears as follows:
public void paint(Graphics g) {
Rectangle r = bounds();
createColor =
new Color(super.RedValue, super.GreenValue, super.blueValue);
g.setColor(createColor);
g.fillRect(0,0,r.width,r.height);
paintwheel(g, r.height / 2, r.width/2 - r.height/4, r.height/4);
The method as defined raises two questions: First, why wasn't a call to the superclass used, as in the last example, to use the paint() method in RGBcanvas? Primarily because you needed the values r.height and r.width to pass to the paintwheel() method. Because you cannot access these values from the superclass, you had the option of re-creating the entire procedure or repeating the work of creating the rectangle. Second, why was this complex formula created rather than giving paintwheel explicit x and y coordinates? The formula ensures that the color wheel will be centered in the RGBcanvas2 no matter how the canvas is sized. Using the formula means that the class is flexible enough to handle small and large sizes.
Having added this new class, you can compile and test it. Notice that there is no resulting change to the applet. You still must use the new class somewhere in the existing code. To do this, you must extend the applet itself. The new applet will be an extension of RGB2Hex, just as the new canvas is an extension of RGBcanvas.
Select a new name for the extended applet, in this case, RGB2Hex2. This applet is stored in a separate file, and the class RGB2Hex2 is created as an extension of RGB2Hex. Remember, in extending the applet the existing class will remain the same. The declaration for the new class appears as follows:
class RGB2Hex2 extends RGB2Hex {
This completes the changes required to add the color wheel to the applet. Figure 16.1 shows the applet with the color wheel.
Figure 16.1 : The RGB2Hex2 applet.
The changes made in this example do not differ in most respects from adding a function in a conventional programming language. However, the original applet, without the color wheel, remains intact for others to use. To illustrate the size of the extended applet in comparison to the code for the original applet, the complete code listing for the extended applet is shown in Listing 16.2.
Listing 16.2. The RGB2Hex2 applet.
import java.awt.*;
import java.applet.*;
/**
* Peter Norton's Guide to Programming Java * The RGB2Hex2 Applet
* Converts Red, Green, and Blue values entered by the user
* to a hexidecimal color code and displays the color.
* This applet extends the RGB2Hex applet.
* To help you better correlate colors the extended applet
* displays a color wheel.
*/
public class RGB2Hex2 extends RGB2Hex {
public void init() {
setLayout(new BorderLayout());
RGBcanvas c = new RGBcanvas2();
add("Center", c);
add("North", controls = new RGBcontrols(c));
resize(300,300);
}
}
/* Provides an object to display a color and colorwheel*/
class RGBcanvas2 extends RGBcanvas {
public void paint(Graphics g) {
Rectangle r = bounds();
createColor =
new Color(super.RedValue, super.GreenValue, super.blueValue);
g.setColor(createColor);
g.fillRect(0,0,r.width,r.height);
paintwheel(g, r.height / 2, r.width/2 - r.height/4, r.height/4);
}
/* Paints color wheel on the graphics object at the location specified */
public void paintwheel(Graphics g,int wheel_size,int wheel_x,int wheel_y){
g.setColor(Color.red);
g.fillArc(wheel_x, wheel_y, wheel_size, wheel_size, 0, 60);
g.setColor(Color.orange);
g.fillArc(wheel_x, wheel_y, wheel_size, wheel_size, 60, 60);
g.setColor(Color.yellow);
g.fillArc(wheel_x, wheel_y, wheel_size, wheel_size, 120, 60);
g.setColor(Color.green);
g.fillArc(wheel_x, wheel_y, wheel_size, wheel_size, 180, 60);
g.setColor(Color.blue);
g.fillArc(wheel_x, wheel_y, wheel_size, wheel_size, 240, 60);
g.setColor(new Color(150,0,150));
g.fillArc(wheel_x, wheel_y, wheel_size, wheel_size, 300, 60);
}
}
The code for this applet should reside in the same directory as the code for the RGB2Hex applet so the Java compiler can locate the classes defined in RGB2Hex. The compiler must be able to locate those classes because all the classes defined in RGB2Hex2 are extensions of classes in RGB2Hex. The command to compile the new applet follows:
javac RGB2Hex2.java
The results of this compilation are two new files: RGB2Hex2.class and RGBcanvas2.class. The applet can be added to an HTML file as follows:
<HTML>
<HEAD>
<TITLE> RGB to Hex Converter 2</TITLE>
</HEAD>
<BODY>
<CENTER>
<H2> The RGB to Hex Converter 2</H2>
<H3> Written with JAVA <H3>
<HR>
<APPLET CODE="RGB2Hex2" WIDTH=300 HEIGHT=300>
</APPLET>
</CENTER>
</BODY>
</HTML>
Both of the examples so far have added to the display of the applet but have not significantly added to its capabilities. The next example increases the functionality of the applet so that it provides an interactive service that was not present in the original applet.
In this example, you will modify the QuizMaker applet to allow the user to specify the length of time for the timer. After all, not everyone wants to take a three-minute quiz. This is an example of extending an applet to increase its functionality without changing how the applet performs its task. Although the change may seem more extensive than in the first example, the basic functioning of the QuizMaker applet remains the same.
One of the key design concepts for the extended applet is the need for somewhere to store the time for which the timer will run. This storage place must be accessed by both the method checking for the expiration of the timer and the method that displays it to the user. Therefore, this value should be stored at a class level.
The applet will also need to display this value to the user and be able to read a new value from him or her. For now, let's assume the applet will read the new value at the same time the button is pressed to start the timer. You can add a text input field to display the initial value and allow the user to modify it. However, if the user sets it to zero or to an invalid value such as text, you do not need to start the timer when the button is pressed.
Should the timer length be stored as an object or as a variable? Well, the value has no meaning apart from the timer that is using it. It would not make sense to have a timer length without a timer. Therefore, this is a property of the timer and not a separate object. So you should store the timer length as a class-level variable named TimerLength. This will be an integer value to store the number of seconds the timer will run.
The text input field used to display and retrieve information from the user can be created using a class, supplied in the API, named LengthEntry. It must be present when the timer starts, so adding it to the applet will be the responsibility of the init() method. This is done using the same add() method used by other objects. The line of code to add the text field and initialize its value follows:
this.add(LengthEntry = new TextField("180"));
This example initializes the value in the display to three minutes. The StopTimer() method must be modified to check the class-level object instead of using the value 180 that was hard-coded originally. The modified line appears as follows:
if ((stopSec - startSec) > TimerLength) {
The most interesting design decision is to determine when and where to set the new value in the class variable. It must be done after the button is pressed but before the start time is calculated. It could be done as part of the event handler or as part of the start() method. It could be coded into the existing methods, or a new method could be created for this task.
To decide where to place the code that sets the timer length, consider how the timer works. The decision to set the length of the timer is really made by the user. The applet is reading in the value in response to an action by the user. You could even modify the timer at some point to provide a second button just to accept new values for the timer length. This means that setting the timer value is associated more with the user event than with the starting of the timer. You might even want to modify the timer to include an automatic restart, and you probably would not want to have to reset the timer length every time. Therefore, a good way to set this value is via the event handler. To keep the event handler from becoming too long or complicated, create a new method to handle this task and call the method from the event handler.
The new method appears as follows:
protected void setTimer() {
try {
TimerLength = Integer.parseInt(LengthEntry.getText());
} catch (NumberFormatException e) {
TimerLength = 0; // If error occurs set length to 0
}
if (TimerLength < 0)
TimerLength = 0; // If length is invalid set to 0
}
}
This method sets the timer if the value in the text field is a valid integer greater than zero. If it is not an integer or is less than zero, it will set the length to zero.
This completes all the modifications to the applet. Figure 16.2 shows how the resulting changes will appear to the user.
Figure 16.2 : The QuizMaker applet allowing the user to set the timer interval.
The complete code for the extension is shown in Listing 16.3. Notice how few lines of code are necessary to complete the entire extension.
Listing 16.3. The QuizMaker2 applet.
import java.awt.*;
import java.applet.*;
import java.util.*;
/**
* Peter Norton's Guide to Java Programming
* The QuizMaker2 Applet
* Acts as an arbitrary length timer. Plays an audio message to start and
* displays a start message. Plays an audio message at the end with
* another display message.
* This applet extends the QuizMaker applet.
*/
public class QuizMaker2 extends QuizMaker {
int TimerLength;
TextField LengthEntry;
public void init() {
this.add(LengthEntry = new TextField("180"));
super.init();
}
public void paint(Graphics g) {
super.paint(g);
LengthEntry.resize(50,20);
LengthEntry.move(30,70);
}
/* Checks the current system time to determine
if 180 seconds has elapsed since start */
protected boolean stopTimer(int startSec) {
int stopSec;
Date StopTime = new Date(); // Get the new time
stopSec = StopTime.getSeconds(); // Get the number of seconds
stopSec += (StopTime.getMinutes() * 60); // Add # of minutes times 60
if ((stopSec - startSec) > TimerLength) {
return true; // True means time has elapsed
} else {
return false; // False means time not elapsed
}
}
/* Event handler to detect when the button on the window is pressed */
public boolean handleEvent(Event e) {
// If the button is pressed start timer
if (e.target instanceof Button) {
setTimer(); // Set the length of time to run the timer
runTimer(); // Start the timer
return true; // Return true so super does not act
}
return false; // Return false so super can act on event
}
protected void setTimer() {
try {
TimerLength = Integer.parseInt(LengthEntry.getText());
} catch (NumberFormatException e) {
TimerLength = 0; // If error occurs set length to 0
}
if (TimerLength < 0)
TimerLength = 0; // If length is invalid set to 0
}
}
This applet is significantly more versatile than the original QuizMaker applet. However, there are always ways to improve software, and there are always additional features desired by users. Not all of these features are best added by extending an applet. The last example shows why it may be appropriate to rewrite this particular applet.
The QuizMaker applet is more flexible now that the user can set the timer, but it still stops all the other functions of the computer while waiting on the timer. On a modern multitasking computer, this is an inordinate waste of resources. The user might want to run another application while the timer is running. Therefore, it would be nice to have a timer that runs in a single thread and allows the computer to perform other functions.
This is a change to the basic underlying assumptions around which the QuizMaker applet was created, altering the basic algorithm by which the applet functions. Therefore, it would make sense to take the existing applet and rewrite it. The new QuizMaker applet will make use of multithreading so that the computer is free to perform other tasks while the timer is running.
To use threads in the applet, your applet must implement the interface Runnable. The resulting declaration appears as follows:
class QuizMaker extends Applet implements Runnable {
The applet is declared as implementing Runnable; therefore, it must include a run() method. The run() method that follows is short and basically just enables the applet to run while waiting on the user to shut it down:
/* Run the applet */
public void run() {
while (killme != null) {}
}
The run method enables the QuizMaker applet to run as long as the killme thread is not set to null. Now you need something to start the run() method and something else to set killme to null when it is time to end the applet. Because the start() method is called by the runtime environment each time a new instance of the applet is created, the start() method is used to create a new killme thread, passing the applet object as a parameter. start() will then start this new thread. Starting the new thread calls the run() method for the thread and-presto!-the applet is running in its own thread. The code for the start() method follows:
/* start new thread to run timer */
public void start() {
if (killme == null)
{
killme = new Thread(this);
killme.start();
}
}
Now you need a way to stop the thread when the applet is closed. The stop() method is perfect for this. The value of the class variable killme, which is checked in the run() method, is changed in the stop() method. The stop() method is called only when the user leaves the page. The code for this short method follows:
/* End thread containing timer */
public void stop() {
killme = null;
}
Could you have created a threaded timer by simply extending the existing QuizMaker class? Of course! After all, you are simply adding a variable, an object, and a few methods to the QuizMaker class. However, because you now have a thread for your timer, it would be convenient to use the sleep() method that is available as part of the thread class to implement the timer. This saves on computation by removing the empty while loop. Eliminating the date manipulation can also reduce computation costs and results in more readable code.
There are trade-offs here. If you use the sleep() method that is part of the Thread class, the code becomes easier to understand and you do not need the stopTimer() method. However, if you eliminate the stopTimer() method, the extension you built in the previous section will not work. You would have to rewrite the extension to provide the flexibility of allowing the user to enter the time. Of course, you are rewriting the applet anyway, so you could incorporate that flexibility into the timer.
Let's allow the user to select the length of the timer in the rewritten applet. Remove the StopTimer() method and replace the call to it with the following line:
try {Thread.sleep(TimerLength);} catch (InterruptedException e) {}
This calls the sleep() method, passing it the TimerLength. The value passed to sleep() is the time to suspend the thread in milliseconds. You must add the setTimer method you created to extend the applet but must modify it to convert the value the user enters from seconds into milliseconds. You also must include the LengthEntry object in the init() method, just as you did for the applet extension.
The final version of the QuizMaker class is shown in Listing 16.4. As you can see, the display resulting from this code is exactly the same as the display resulting from extending the class in the previous section. Could you have made all the changes as extensions and not had as much code in the class? You could have, but it would have been unreasonably hard to follow with methods being used in superclasses and then defined away in subclasses. This difficulty justifies the decision to rewrite the applet rather than extending it.
Listing 16.4. The revised QuizMaker applet with multithreading.
import java.awt.*;
import java.applet.*;
import java.util.*;
import java.net.*;
/**
* Peter Norton's Guide to Java Programming
* Revised QuizMaker Applet
* Acts as an arbitrary length timer. Plays an audio message to start and
* displays a start message. Plays an audio message at the end with
* another display message.
* This applet is a multithreaded version of the original QuizMaker applet.
*/
public class QuizMaker extends Applet implements Runnable{
Image HglassImage; // Displays a picture of an hour glass
Button RunButton; // Lets the user start the timer
Label StatusLabel; // Displays the status to the user
URL HglassURL; // URL of the applet
Thread killme = null; // Thread for timer processing
int TimerLength;// Number of seconds the timer should run
TextField LengthEntry;// User entry for number of seconds
public void init() {
// Get the picture to add to the screen
HglassImage = getImage(getCodeBase(),"Hglass.gif");
// Adds a label to display text which can not be altered by the user
add(StatusLabel = new Label("Press Run to start timer"));
// Adds a button to start the timer
add(RunButton = new Button("Run"));
this.add(LengthEntry = new TextField("180"));
resize(400,300); // Resizes the applet
paint(getGraphics()); // Displays applet
HglassURL = getCodeBase(); // Gets the URL for the applet
play(HglassURL,"intro.au"); // Play an intro
play(HglassURL,"startTimer.au"); // Play the start message
play(HglassURL,"endTimer.au"); // Play the end message
}
public void paint(Graphics g) {
g.drawImage(HglassImage,180,10,this); // Draw the hour glass
StatusLabel.resize(150,20); // Resizes the label
StatusLabel.move(10,10); // Moves the label
RunButton.resize(100,20); // Resizes the button
RunButton.move(20,40); // Moves the button
LengthEntry.resize(70,20); // Resizes the entry box
LengthEntry.move(30,70); // Moves the box
}
/* Lets the user know to start, saves the start time
and checks for time out */
protected void runTimer() {
Date StartTime; // Holds the time the timer started
int startSec; // Start time in seconds
// Lets the user know to get ready
StatusLabel.setText("Get the hour glass ready.");
play(HglassURL,"startTimer.au"); // Play the start message
StartTime = new Date(); // Get the starting time
// Lets the user know to start
StatusLabel.setText("Time is slipping away.");
// Calculate the start time in seconds
startSec = StartTime.getSeconds(); // Get the number of seconds
startSec += (StartTime.getMinutes() * 60);//Add # of minutes times 60
try {Thread.sleep(TimerLength);} catch (InterruptedException e) {}
// Informs the user the hour glass is empty
StatusLabel.setText("The hour glass is empty!");
play(HglassURL,"endTimer.au");
}
/* Event handler to detect when the button on the window is pressed */
public boolean handleEvent(Event e) {
int myint;
// If the window is closed end the program
if (e.id == Event.WINDOW_DESTROY) {
System.exit(0);
}
if (e.target instanceof Button) { // If the button is pressed start timer
setTimer(); // Set the length of time to run the timer
runTimer(); // Start the timer
return true; // Return true so super does not act
}
return false; // Return false so super can act
}
/* Reads value from user if valid multiply by 1000 to convert */
/* to seconds otherwise set timer to 0 */
protected void setTimer() {
try {
TimerLength = Integer.parseInt(LengthEntry.getText());
TimerLength = TimerLength * 1000;
} catch (NumberFormatException e) {
TimerLength = 0;
}
if (TimerLength < 0)
TimerLength = 0;
}
/* starts new thread to run timer */
public void start() {
if (killme == null)
{
killme = new Thread(this);
killme.start();
}
}
/* Ends thread containing timer */
public void stop() {
killme = null;
}
/* Runs the applet */
public void run() {
while (killme != null) {}
}
}
This concludes the coding examples for this chapter. The next section addresses testing the extended applets.
Software testing in general is one of the most important and yet most neglected aspects of software development. Testing code modifications is often even more hurried than initial software testing, generally due to time and budget constraints. Yet failure to adequately design and implement tests can increase the total amount of time and money spent on a project. Testing is also critical to providing high-quality software. Although Java increases the amount of code that can be reused, thereby decreasing the amount of new development, it does not eliminate the necessity of testing.
Regression testing the extended applet involves testing not only the new, but also the existing functionality. Your changes should not have affected any of the existing features; there is little risk of this if you are reusing an applet. However, you must verify that there are no unexpected side effects generated in the extension.
To ensure that you have tested all aspects of the applet and all the areas of change, you should have a written test plan to provide a methodical means of ensuring that all the tests are performed. It can also ensure the tests are run in an orderly fashion. If, while testing, you find a problem and make a correction, try to rerun all the tests from the beginning of the test plan.
If possible, it is best to run the entire test plan and document all the problem areas before making any changes. Often problems in one area will be related to problems in another. Addressing these problems as they occur in the test plan may result in an incomplete understanding of the problem. Waiting until all the problems are documented is best to reduce the amount of reworking and retesting.
Although the need for a documented testing strategy is obvious on large projects with several developers, it is just as helpful on small projects. However, the documentation does not need to be fancy binders with sophisticated forms. Yellow notepads can work well for individuals testing their own applets. The next sections present sample test plans for each of the modifications described in this chapter.
A suggested test plan for the ScrollText2 applet follows:
In reading this test plan, note that the expected outcome is listed for each test. In creating a test plan, you are trying to confirm that the software does what is expected, not determine expected behavior.
In the last test, black text on a black background may not be the most desirable behavior. If changes such as these are being made at the request of a user, it is reasonable to confirm with the user that this type of danger is acceptable. The user may request an additional change to create white text when the background color is dark. These considerations are best made at design time, but it is better to review them when testing than to surprise the user at delivery.
This example is short, yet includes some important features of test plans. The plan includes testing for error, normal, and boundary conditions. The next test plan, for the RGB2Hex2 applet, includes the same types of tests. Because the user has more options available at runtime from the RGB2Hex2 applet, a larger test plan is necessary to test each option.
Applets that receive numeric input such as the RGB2Hex2 applet must be tested for correct results using both random-input and specific test cases. The test cases should include boundary conditions. They also should include changes in each of the text entry fields. When retesting after making a change, all the original test cases should be performed again. In addition, new test cases must be added so that applets are not created specifically to meet the needs of the test case.
A suggested test plan for the RGB2Hex2 applet follows:
This test uses the same strategy used in the ScrollText2 example. You test first for the normal conditions of the applet, and then for the boundary conditions of 0 and 255. This strategy is also used to test the QuizMaker applet. However, in testing QuizMaker you must verify the timer accuracy as well as the display.
The test plan for the QuizMaker applet must include tests for invalid input and timer accuracy as well as verify that the original display and audio features were not affected by the enhancements. The following test plan incorporates all these situations:
This test plan is more extensive than the previous plans and will take more time to complete. The testing process may proceed faster if the test plan is divided between several people; however, the results from different testers should be compared before any corrections are made.
In addition to the tests listed previously, the test plan for the rewritten applet must test for problems resulting from the introduction of threads. The extended test plan will verify that the applet runs correctly even when other programs are loaded and unloaded:
If there were other pieces of functionality in the applet, you would want to test those as well. If you had a test plan from the original version of the applet, it would be reasonable to repeat all the tests on it to verify that they still run correctly. Clearly, there are more tests you could run for this particular applet, but those listed here should be enough to instill a basic level of confidence in the performance of the applet.
After looking at the test plans for each of the applets modified in this chapter, you now must consider how to create such test plans. The most important thing to remember is that it will require time to create a good plan, but you will reap more benefits. Well-crafted test plans can be modified and reused much like applets can be.
Take care in creating test plans to be sure they are as comprehensive and detailed as possible. The following list summarizes some of the items to include in a well-written test plan:
Often you can begin creating the test plan as soon as the requirements are understood. At that point, the plan should include basic tests for the functionality described in the requirements. The plan then can be modified during the design phase as new objects are added. The modifications would include specific tests for each object. The plan may be updated again during the coding phase to be sure all error handling is included and all boundary conditions are tested. A plan developed over time in this manner is generally more comprehensive than one developed after coding and before testing.
In general, remember that the more extensive the testing, the more likely it is that potential problems will appear during testing instead of after release. Testing is an important part of software construction and should not be taken lightly.
This chapter explores some of the ways you can extend existing applets. It covers when it is appropriate to extend an applet and when it is best to rewrite it. It provides several examples of extending applets and one example of rewriting an applet. You should now feel comfortable with modifying and extending existing applets to use in your own presentations.
The chapter also explains the importance of testing, providing many examples of test plans for applets and describing some important aspects of creating your test plans. You should now be able to create robust test plans for testing your own applets or those supplied to you.