Practice makes perfect

Experience is often your best defense against Java pitfalls

1 2 Page 2
Page 2 of 2
package com.javaworld.jpitfalls.article5;
import java.io.*;
public class BadFileRename
{
    public static void main(String args[])
    {       
        try
        {
            // Check if test file in current dir
            File f = new File("dummy.txt");
            String name = f.getName();
            if (f.exists())
                System.out.println(f.getName() + " exists.");
            else
                System.out.println(f.getName() + " does not exist.");
            // Attempt to rename to an existing file
            File f2 = new File("dummy.bin");
            // Issue 1: boolean status return instead of Exceptions
            if (f.renameTo(f2))
                System.out.println("Rename to existing File Successful.");
            else
                System.out.println("Rename to existing File Failed.");
            // Attempt to rename with a different extension
            int dotIdx = name.indexOf('.');
            if (dotIdx >= 0)
                name = name.substring(0, dotIdx);
            name = name + ".tst";
            String path = f.getAbsolutePath();
            int lastSep = path.lastIndexOf(File.separator);
            if (lastSep > 0)
                path = path.substring(0,lastSep);
            System.out.println("path: " + path);
            File f3 = new File(path + File.separator + name);
            System.out.println("new name: " + f3.getPath());
            if (f.renameTo(f3))
                System.out.println("Rename to new extension Successful.");
            else
                System.out.println("Rename to new extension failed.");
            
            // Delete the file
            // Issue 2: Is the File class a file?
            if (f.delete())
                System.out.println("Delete Successful.");
            else
                System.out.println("Delete Failed.");
            // Assumes program not run from c drive
            // Issue 3: Behavior across operating systems?
            File f4 = new File("c:\\" + f3.getName());
            if (f3.renameTo(f4))
                System.out.println("Rename to new Drive Successful.");
            else
                System.out.println("Rename to new Drive failed.");        
        } catch (Throwable t)
         {
            t.printStackTrace();   
         }       
    }        
}

When Listing 11.1 runs from a drive other than C and with the file dummy.txt in the current directory, it produces the following output:

E:\classes\com\javaworld\jpitfalls\article5>java com.javaworld.jpitfalls.article5.BadFileRename
dummy.txt exists.
Rename to existing File Failed.
path: E:\classes\com\javaworld\jpitfalls\article5
new name: E:\classes\com\javaworld\jpitfalls\article5\dummy.tst
Rename to new extension Successful.
Delete Failed.
Rename to new Drive Successful.

Listing 11.1 raises three specific issues, which are called out in the code comments. At least one is accurately characterized as a pitfall; the others should be considered poor design:

  1. 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 file name. Currently, we have no way of knowing.
  2. The second issue is the pitfall: attempting to use the initial File object 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 that you only want to change the filename. So why not just pass in a String? In that intuition lies the pitfall's source.

    The pitfall is the assumption that a File object represents a physical file and not a file's name. In the least, that should be considered poor class naming. For example, if the object merely represents a filename, then the object should be called Filename instead 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.

  3. The third issue is File.renameTo()'s different behavior on different 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). The Sun programmers verifying reported bugs contend that WORA simply 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 suffice. A WORA platform requires consistent behavior, otherwise "run anywhere" means "maybe run anywhere." To avoid this pitfall, check the os.name System property and code renameTo() differently for each platform.

Out of these three issues, we can currently fix only the proper way to delete a file after a successful rename, as Listing 11.2 demonstrates. Because the other two issues result from Java's design, only the Java Community Process (JCP) can initiate fixes for them.

Listing 11.2. GoodFileRename.java

package com.javaworld.jpitfalls.article5;
import java.io.*;
public class GoodFileRename
{
    public static void main(String args[])
    {       
        try
        {
            // Check if test file in current dir
            File f = new File("dummy2.txt");
            String name = f.getName();
            if (f.exists())
                System.out.println(f.getName() + " exists.");
            else
                System.out.println(f.getName() + " does not exist.");
            // Attempt to rename with a different extension
            int dotIdx = name.indexOf('.');
            if (dotIdx >= 0)
                name = name.substring(0, dotIdx);
            name = name + ".tst";
            String path = f.getAbsolutePath();
            int lastSep = path.lastIndexOf(File.separator);
            if (lastSep > 0)
                path = path.substring(0,lastSep);
            System.out.println("path: " + path);
            File f3 = new File(path + File.separator + name);
            System.out.println("new name: " + f3.getPath());
            if (f.renameTo(f3))
                System.out.println("Rename to new extension Successful.");
            else
                System.out.println("Rename to new extension failed.");
            
            // Delete the file
            // Fix 1: delete via the "Filename" not File
            if (f3.delete())
                System.out.println("Delete Successful.");
            else
                System.out.println("Delete Failed.");
        } catch (Throwable t)
         {
            t.printStackTrace();   
         }       
    }        
}

A run of Listing 11.2 produces the following output:

E:\classes\com\javaworld\jpitfalls\article5>java com.javaworld.jpitfalls.article5.GoodFileRename
dummy2.txt exists.
path: E:\classes\com\javaworld\jpitfalls\article5
new name: E:\classes\com\javaworld\jpitfalls\article5\dummy2.tst
Rename to new extension Successful.
Delete Successful.

Thus, you shouldn't use the File class as if it represents a file instead of the filename. With that in mind, once the file is renamed, operations such as delete() work only on the new filename.

Experience helps you escape Java traps

In summary, gaining familiarity in Java programming is key to avoiding Java pitfalls. We fixed the traps presented in this article by simply learning the proper functions of certain classes. First, remember to position and size components when adding them to a layer in a JLayeredPane. Second, remember that enumerating over a Vector does not work in conjunction with the remove() operation, as Iterator does. Finally, do not use the File class for operations after a successful renameTo().

I would like to thank everyone who emailed me about this column over the past year. The feedback and support has been wonderful. This column has been a rewarding experience, and I will return after a break.

Until then ... best wishes.

Michael C. Daconta is the director of Web and technology services for McDonald Bradley, where he conducts training seminars and develops advanced systems with Java, JavaScript, and XML. Over the past 15 years, Daconta has held every major development position, including chief scientist, technical director, chief developer, team leader, systems analyst, and programmer. He is a Sun-certified Java programmer and coauthor of Java Pitfalls (John Wiley & Sons, 2000; ISBN: 0471361747), Java 2 and JavaScript for C and C++ Programmers (John Wiley & Sons, 1999; ISBN: 0471327190), and XML Development with Java 2 (Sams Publishing, 2000; ISBN: 0672316536). In addition, he is the author of C++ Pointers and Dynamic Memory Management (John Wiley & Sons, 1995; ISBN: 0471049980).

Learn more about this topic

1 2 Page 2
Page 2 of 2