An Alternative Open Source HTTP Client
24: lp.add(jpnl, new Integer(2));
25:
26: // put a Button on top 27: Button b = new Button(“Hi!”); 28: lp.add(b, new Integer(1)); 29:
30: // Part of Fix 1
31: getContentPane().add(lp);
32: } 33:
// main method() Identical to BadLayeredPane.java 50: }
Listing 19.2 (continued)
We’ll first study the fixes applied and then the results. There are two fixes in Listing 19.2 ( called out in the comments):
■■ First, we create a new JLayeredPane, which we add to the ContentPane. The RootLayoutmanager uses the ContentPaneto calculate the frame’s size, so now the JFrameis packed properly.
■■ Second, we correctly add components to the JLayeredPaneusing an Integer
object to specify the layer.
Figure 19.3 shows the result of these fixes.
Figure 19.3 clearly demonstrates that we have not yet accomplished our goal. Though the colored panel displays, the button fails to appear on the layer above the panel. Why? Because we assume we add components to a JLayeredPanethe same way we add components to Frames and Panels. This assumption is our third error and the JLayeredPanepitfall. Contrary to Frameand Panel, the JLayeredPane
lacks a default LayoutManager; thus, the components have no sizes or positions pro- vided for them by default. Instead, a component’s size and position must be explicitly set before adding them to the JLayeredPane, which Fix 1 achieves in Listing 19.3.
01: package org.javapitfalls.item19; 02: 03: import java.awt.*; 04: import javax.swing.*; 05: import java.awt.event.*; 06:
07: public class GoodLayeredPane extends JFrame 08: {
09: public GoodLayeredPane() 10: {
11: JLayeredPane lp = new JLayeredPane(); 12:
13: // set the size of this pane
14: lp.setPreferredSize(new Dimension(100,100)); 15:
16: // add a Colored Panel 17: JPanel jpnl = new JPanel(); 18: jpnl.setSize(100,100); 19: jpnl.setOpaque(true);
20: jpnl.setBackground(Color.red); 21:
22: lp.add(jpnl, new Integer(1)); 23:
24: // put a Button on top 25: Button b = new Button(“Hi!”); 26: // Fix 1: set the size and position
27: b.setBounds(10,10, 80, 40);
28: lp.add(b, new Integer(2)); 29:
30: getContentPane().add(lp); 31: }
32:
// main() method Identical to BadLayeredPane.java 49: }
Listing 19.3 GoodLayeredPane.java
When run, Listing 19.3 produces the correct result, shown in Figure 19.4.
In summary, the key pitfall in our JLayeredPaneexample is wrongly assuming that the JLayeredPanehas a default LayoutManagerlike JFrame and JPanel. Experience tells us to eliminate that assumption and position and size the components for each layer. Once we do so, the JLayeredPaneworks fine.
Item 20: When File.renameTo() Won’t
10The Fileclass and specifically the File.renameTo()method suffers from pitfalls in both design and implementation. Many pitfalls stem from confusion regarding the expected behavior of classes in the Java libraries. Unfortunately, the input/output (IO) classes in Java have been prone to significant revision as the Java platform has evolved. An early overhaul added readers and writers to the input and output streams to distin- guish between character-based IO and byte-based IO. With JDK 1.4, another overhaul has taken place, adding a lower layer of high-performance access via Channelclasses. Figure 20.1 displays the major file-related classes in the java.ioand java.niopackages.
Unfortunately, just the fact that there are five classes and two interfaces all pertain- ing to different facets of a file increases the complexity of using these classes properly. That is especially true if the distinction between classes is small or if the role of the class is ill defined. Let’s examine each class and its purpose:
Figure 20.1 File-related classes in the Java class libraries. java.io <<interface>> FileFilter <<interface>> FilenameFilter RandomAccessFile File FileDescriptor FilePermission java.nio.channels FileLock FileChannel
10This pitfall was first published by JavaWorld(www.javaworld.com) in the article, “Practice
makes perfect” November 2001 (http://www.javaworld.com/javaworld/jw-11-2001 /jw-1116-traps-p2.html) and is reprinted here with permission. The pitfall has been updated from reader feedback.
File.Present since JDK 1.0, a class that represents a file or directory pathname. This class contains a wide array of methods to test characteristics of a file, delete a file, create directories, and, of course, rename a file. Unfortunately, this class suffers from a vague scope in that it incorporates behaviors of a directory entry, like isFile(), isDirectory(), and lastModified(), and behaviors of a physical file like createNewFile(), delete(), and renameTo(). We will discuss this more later.
FilenameFilter.Present since JDK 1.0, an interface to test the occurrence of a list of
Fileobjects via the File.list()method and FileDialog.setFilename- Filter()method. The confusion over scope stated above is evident in the con- tradiction between this interface and the next one (FileFilter) in terms of their names. This interface has a single method called accept()that receives a
Fileobject representing a directory and a Stringrepresenting the name of the file to filter on.
FileFilter.Added in JDK 1.2, an interface to filter in the same manner as File- nameFilterexcept that the accept()method receives only a single File
object. Unfortunately, this interface is a prime example of a superfluous conve- nience interface that does more harm than good because of the new name. It would have been far better to follow the precedent of the awtpackage where
LayoutManager2extends LayoutManagerto add methods. The difference between the two design strategies is that the LayoutManagerinterfaces are clearly semantically congruent, whereas FilenameFilterand FileFilter
are not.
FileDescriptor.A class to provide an opaque handle to the operating system- specific File data structure. As its name implies, this class is a very thin abstrac- tion over an operating system structure. The class only has two methods. As a general design rule, it would be preferable to combine our abstractions of a physical file into a single class. Unfortunately, the requirements of backward compatibility cause future developers to suffer with multiple abstractions. Since the New IO package (NIO), split IO operations at the package level (which also spoils the platform’s cohesiveness), there is an opportunity to start from scratch with new classes in the NIO package.
FilePermission.A class to represent access to a file directory. This was part of the 1.2 fine-grained security mechanisms—again, a nice candidate for conceptual consolidation.
RandomAccessFile.Present since JDK 1.0, a class that represents the characteris- tics and behaviors (taken from the C standard library) of a binary file. This allows low-level reading and writing of bytes to a file from any random file position. This class stands on its own and does not fit in to the IO stream metaphor and does not interact with those classes. It is interesting to note that the word “File” in this class name actually refers to a physical file and not just its name. Unfortunately, this package lacks such consistency.
FileChannel.Added to JDK 1.4, a class to provide a high-performance pathway for reading, writing, mapping, and manipulating a file. You get a FileChannel
from a FileInputStream, FileOutputStream, or RandomAccessFile
examined the NIO performance improvements. Lastly, a region of the file (or the whole file) may be locked to prevent access or modification by other programs via methods that return a FileLockobject.
FileLock.Added to JDK 1.4, a class to represent a lock on a region of a file. A lock can be either exclusive or shared. These objects are safe for use by multiple threads but only apply to a single virtual machine.
Now let’s narrow our focus to the Fileclass. Listing 20.1 demonstrates some File
class behaviors and pitfalls.
01: package org.javapitfalls.item20; 02:
03: import java.io.*; 04:
05: public class BadFileRename 06: {
07: public static void main(String args[]) 08: {
09: try 10: {
11: // check if test file in current dir 12: File f = new File(“dummy.txt”); 13: String name = f.getName(); 14: if (f.exists())
15: System.out.println(f.getName() + “ exists.”); 16: else
17: System.out.println(f.getName() + Æ “ does not exist.”);
18:
19: // Attempt to rename to an existing file 20: File f2 = new File(“dummy.bin”);
21: // Issue 1: boolean status return instead of Exceptions 22: if (f.renameTo(f2))
23: System.out.println(
24: “Rename to existing File Successful.”); 25: else
26: System.out.println(
27: “Rename to existing File Failed.”); 28:
29: // Attempt to rename with a different extension 30: int dotIdx = name.indexOf(‘.’);
31: if (dotIdx >= 0)
32: name = name.substring(0, dotIdx); 33: name = name + “.tst”;
34: String path = f.getAbsolutePath();
35: int lastSep = path.lastIndexOf(File.separator); 36: if (lastSep > 0)
37: path = path.substring(0,lastSep); 38: System.out.println(“path: “ + path);
39: File f3 = new File(path + File.separator + name); 40: System.out.println(“new name: “ + f3.getPath()); 41: if (f.renameTo(f3))
42: System.out.println(
43: “Rename to new extension Successful.”); 44: else
45: System.out.println(
46: “Rename to new extension failed.”); 47:
48: // delete the file
49: // Issue 2: Is the File class a file? 50: if (f.delete())
51: System.out.println(“Delete Successful.”); 52: else
53: System.out.println(“Delete Failed.”); 54:
55: // assumes program not run from c drive 56: // Issue 3: Behavior across operating systems? 57: File f4 = new File(“c:\\” + f3.getName()); 58: if (f3.renameTo(f4))
59: System.out.println( “Rename to new Drive Successful.”); 60: else
61: System.out.println(“Rename to new Drive failed.”); 62: } catch (Throwable t) 63: { 64: t.printStackTrace(); 65: } 66: } 67: } 68: Listing 20.1 (continued)
When this code is run from a drive other than C, and with the file dummy.txt in the current directory, it produces the following output:
E:\classes\org\javapitfalls\Item20>java Æ org.javapitfalls.item20.BadFileRename
dummy.txt exists.
Rename to existing File Failed.
path: E:\classes\org\javapitfalls\Item20
new name: E:\classes\org\javapitfalls\Item20\dummy.tst Rename to new extension Successful.
Delete Failed.
Listing 20.1 raises three specific issues, which are called out in the code comments. At least one is accurately characterized as a pitfall, and the others fall under poor design:
■■ First, returning a Boolean error result does not provide enough information about the failure’s cause. That proves inconsistent with exception use in other classes and should be considered poor design. For example, the failure above could have been caused by either attempting to renameTo()a file that already exists or attempting to renameTo()an invalid filename. Currently, we have no way of knowing.
■■ The second issue is the pitfall: attempting to use the initial Fileobject after a successful rename. What struck me as odd in this API is the use of a File
object in the renameTo()method. At first glance, you assume you only want to change the filename. So why not just pass in a String? In that intuition lies the source of the pitfall. The pitfall is the assumption that a Fileobject repre- sents a physical file and not a file’s name. In the least, that should be consid- ered poor class naming. For example, if the object merely represents a filename, then it should be called Filenameinstead of File. Thus, poor naming directly causes this pitfall, which we stumble over when trying to use the initial File
object in a delete()operation after a successful rename.
■■ The third issue is File.renameTo()’s different behavior between operating systems. The renameTo()works on Windows even across filesystems (as shown here) and fails on Solaris (reported in Sun’s Bug Parade and not shown here). The debate revolves around the meaning of “Write Once, Run Anywhere” (WORA). Sun programmers verifying reported bugs contend that WORA sim- ply means a consistent API. That is a cop-out. A consistent API does not deliver WORA; there are numerous examples in existing APIs where Sun went beyond a consistent API to deliver consistent behavior. The best-known example of this is Sun’s movement beyond the Abstract Windowing Toolkit’s consistent API to Swing’s consistent behavior. If you claim to have a platform above the operating system, then a thin veneer of an API over existing OS functionality will not suf- fice. A WORA platform requires consistent behavior; otherwise, “run anywhere” means “maybe run anywhere.” To avoid this pitfall, you check the “os.name” System property and code renameTo()differently for each platform.
Out of these three issues, we can currently only fix the proper way to delete a file after a successful rename, as Listing 20.2 demonstrates. Because the other two issues result from Java’s design, only the Java Community Process (JCP) can initiate these fixes.
01: package org.javapitfalls.item20; 02:
03: import java.io.*; 04:
05: public class GoodFileRename 06: {
07: public static void main(String args[]) 08: {
09: try 10: {
11: // check if test file in current dir 12: File f = new File(“dummy2.txt”); 13: String name = f.getName(); 14: if (f.exists())
15: System.out.println(f.getName() + “ exists.”); 16: else
17: System.out.println(f.getName() + Æ “ does not exist.”);
18:
19: // Attempt to rename with a different extension 20: int dotIdx = name.indexOf(‘.’);
21: if (dotIdx >= 0)
22: name = name.substring(0, dotIdx); 23: name = name + “.tst”;
24: String path = f.getAbsolutePath();
25: int lastSep = path.lastIndexOf(File.separator); 26: if (lastSep > 0)
27: path = path.substring(0,lastSep); 28: System.out.println(“path: “ + path);
29: File f3 = new File(path + File.separator + name); 30: System.out.println(“new name: “ + f3.getPath()); 31: if (f.renameTo(f3))
32: System.out.println(
33: “Rename to new extension Successful.”); 34: else
35: System.out.println(
36: “Rename to new extension failed.”); 37:
38: // delete the file
39: // Fix 1: delete via the “Filename” not File 40: if (f3.delete()) 41: System.out.println(“Delete Successful.”); 42: else 43: System.out.println(“Delete Failed.”); 44: } catch (Throwable t) 45: { 46: t.printStackTrace(); 47: } 48: } 49: } 50: Listing 20.2 (continued)
A run of Listing 20.2 produces the following output:
E:\classes\org\javapitfalls\Item20> java org.javapitfalls.item20 Æ .GoodFileRename
dummy2.txt exists.
path: E:\classes\org\javapitfalls\Item20
new name: E:\classes\org\javapitfalls\Item20\dummy2.tst Rename to new extension Successful.
Delete Successful.
Thus, don’t use the Fileclass as if it represents a file instead of the filename. With that in mind, once the file is renamed, operations such as delete()only work on the new filename.
Item 21: Use Iteration over Enumeration
11Enumeration is the original interface, available since JDK 1.0, to iterate over (step through) all the elements in a collection. In terms of semantics, it would have been bet- ter to call the interface “Enumerator,” as it expresses the role a class is “putting on” by implementing the interface, instead of “Enumeration,” which specifies an occurrence of the activity. This is in line with all the more recent interfaces in the java.utilpackage like Observer, Comparator, and Iterator. Table 21.1 compares the Enumeration
interface to the Iteratorinterface. Table 21.1 Enumeration versus Iterator