Sun Java Solaris Communities My SDN Account Join SDN
 
Technical Articles and Tips

Locking Files For Shared Access and Changing User Interface Attributes

 
View this issue as simple text September 24, 2002    

Welcome to the Core Java Technologies Tech Tips, September 24, 2002. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE).

Locking Files For Shared Access
Changing User Interface Attributes

These tips were developed using Java 2 SDK, Standard Edition, v 1.4.

This issue of the Core Java Technologies Tech Tips is written by John Zukowski, president of JZ Ventures, Inc.

Pixel

LOCKING FILES FOR SHARED ACCESS

Have you ever had the need to share an external file with non-Java applications or among multiple Java applications? Prior to the version 1.4 release of the J2SE platform, it couldn't be safely done without the use of native code. Now, thanks to the FileChannel/FileLock features of the New I/O (NIO) APIs in J2SE v 1.4, you have a safe way of sharing an external file, without having to resort to native code.

The FileLock class, which is part of the java.nio.channels package, represents a file lock. You can use a file lock to restrict access to a file from multiple processes. In addition, you have the option of restricting access to an entire file or just a small region of it. A file lock is either shared or exclusive. A shared lock supports read access from multiple processes, while an exclusive lock is typically used for writing. Because a lock is at the file level, you should not use it to restrict access to a file from multiple threads in a single process. If you use a file lock in multiple threads, none of the threads would be restricted from accessing the file. Only external processes are restricted.

To use file locking, you need to get a file channel, that is an instance of the FileChannel class. You can get a file channel through the getChannel method of any of the following I/O classes:

  • FileInputstream
  • FileOutputStream
  • RandomAccessFile

After you get a file channel, you can use either of two pairs of methods that it offers for working with locks: lock and tryLock.

  FileLock lock()
  FileLock tryLock()

The no-argument versions of the two methods try to get an exclusive lock on the entire file associated with the channel. The lock method does not return until the lock is acquired. The tryLock method returns null if another process has a region of the file currently locked.

  FileLock lock(long position, long size, boolean shared)
  FileLock tryLock(long position, long size, boolean shared)

The three-argument versions let you lock a piece of a file. The position argument is the starting position for the lock. The size argument represents the number of bytes to lock. A shared value of true means you want a shared lock, while a false value is for an exclusive lock. The size argument is not limited to the size of a file, this allows you to lock a region before you write to the file. For instance, the three argument version of lock and tryLock go from 0 to Long.MAX_VALUE.

All the file locking methods of FileChannel return a FileLock. You need this to release the lock. As with thread locking, the best approach is to keep the file locked for the smallest amount of time. FileLock offers the following methods:

  • channel - returns the source FileChannel for the lock
  • isShared - reports if the lock is shared
  • isValid - reports if the lock is still valid (the lock is no longer valid if the file channel is closed)
  • overlaps(long position, long size) - checks for overlap with the given region
  • position - returns the start position within locked region
  • release - releases the lock
  • size - returns the number of bytes within the locked region
  • toString - provides a string representation of the lock

Here's a program that demonstrates the file locking features. The program gets an exclusive lock on a file, reports when it has the lock, and then wait until you press the Enter key. Try running the program concurrently in two separate windows. You'll see that the program won't run a second time until you press Enter in the first run. (Note that if you have a file named junk.dat in the directory in which you run this program, the file will be overwritten.)

import java.io.*;
import java.nio.*;
import java.nio.channels.*;

public class Locking {
   public static void main(String arsg[])
       throws IOException {
     RandomAccessFile raf =
       new RandomAccessFile("junk.dat", "rw");
     FileChannel channel = raf.getChannel();
     FileLock lock = channel.lock();
     try {
       System.out.println("Got lock!!!");
       System.out.println("Press ENTER to continue");
       System.in.read(new byte[10]);
     } finally {
       lock.release();
     }
   }
}

It's important to understand that it doesn't matter if a file is locked from a C/C++ program or through a FileChannel in another Java program. Waiting and getting the lock is still the same no matter who might lock a file.

Also note that the file locking API has many platform dependencies. For instance, if a particular locking feature, such as shared locking, is not available on a platform, then exclusive locking is used. The use of exclusive locking instead of shared locking won't necessarily cause a program to fail. However, it will serialize all access to a file, instead of permitting simultaneous reads or access to different sections of a file.

For more information about file locking, as well as other features of the NIO APIs, see "New I/O APIs."

Pixel
Pixel

CHANGING USER INTERFACE ATTRIBUTES

There are two sets of user interface components with the Java 2 Platform: AWT and Swing. The first generation set of components is called the Abstract Window Toolkit, or AWT for short. These components rely on what are called peers to interact with the platform-specific windowing environment (such as Motif or Microsoft Windows). This gives you access to the actual native component for interacting with such objects as buttons and labels. If a particular component wasn't available natively on all the supported platforms, the component wasn't available as part of the AWT component set.

The second generation set of user interface components is the Swing component set. The significant difference between Swing and AWT components is that where the AWT components rely on the native windowing environment to provide the peer-based component to the Java platform, the Swing components are completely hosted within the Java runtime environment. This means that all the drawing of the components is done internally, as is the event management aspect of the different components. When you click on a Swing button, it paints with a different background color and draws a different border to simulate the behavior of pressing the button. What this means is that new components can be created that aren't available natively on all Java runtime platforms.

Because Swing keeps everything internal to the Java runtime environment, it offers various ways to customize the look and feel of different Swing components:

For lightweight changes to colors, borders, fonts, and icons, Swing maintains a series of default settings in javax.swing.UIManager. By placing a new setting in the UIManager's lookup table, all future components that are created use the new settings. Previously created components do not change unless directed.

For example, Swing's JButton component has the following set of UI default properties:

  • Button.background
  • Button.border
  • Button.darkShadow
  • Button.focusInputMap
  • Buton.font
  • Button.foreground
  • Button.highlight
  • Button.light
  • Button.margin
  • Button.shadow
  • Button.textIconGap
  • Button.textShiftOffset

Note that some look and feels have additional properties available. Also, while this is the current set of properties, these are considered undocumented -- there is no assurance that future look and feels will use this mechanism or these keys.

To change a specific setting, you associate the string from the list above with the appropriate datatype. This is done by calling the put method of UIManager. For instance, to change the background color of all future buttons to red, you add the following line of code to your program, before creating any JButton components.

  UIManager.put("Button.background", Color.RED);

Here's a simple program that demonstrates this behavior:

  import java.awt.*;
  import javax.swing.*;

  public class RedButton {
    public static void main(String args[]) {
      JFrame frame = new JFrame("Red");
      frame.setDefaultCloseOperation(
        JFrame.EXIT_ON_CLOSE);
      Container c = frame.getContentPane();
      JButton defaultColor = 
        new JButton("Default Colors");
      c.add(defaultColor, BorderLayout.NORTH);
      UIManager.put("Button.background", 
        Color.RED);
      JButton red =
        new JButton("Red");
      c.add(red, BorderLayout.SOUTH);
      frame.setSize(200, 100);
      frame.show();
    }
  }

When you run the program, here's what you should see:

While you can set individual properties as shown in the RedButton example, the default Swing look and feel, called Metal, provides a way to group common property settings into a theme. The abstract javax.swing.plaf.metal.MetalTheme class offers about fifty settings that let you customize the fonts and colors of the Swing components. By creating different subclasses, you can swap many of the properties at once when you change the look and feel.

  MetalTheme theme = new MyCustomTheme();
  MetalLookAndFeel.setCurrentTheme(theme);
  UIManager.setLookAndFeel(new MetalLookAndFeel());

For an example of theme usage, try out the Metalworks demonstration that comes with the Java 2 SDK distribution. Simply change the current directory to the demo/jfc/Metalworks directory, and run the demonstration with the following command:

  java -jar Metalworks.jar

After opening a few windows, select a different theme under the Theme menu.

For example, here's what you should see using the Steel theme:

And here's what you should see using the Low Vision theme:

Swing also maintains a lookup mechanism for how to actually draw the whole component, including the "feel" aspect as well. The associated property for each component is a subclass of the abstract javax.swing.plaf.ComponentUI class. For the JButton class, the lookup key is ButtonUI, and javax.swing.plaf.ButtonUI is the specific subclass of ComponentUI that you must subclass. Place the class name in the lookup table, and then make the class file available from the classpath. Now all components of the appropriate type will look different. The actual key can be obtained from the JComponent subclass by using the getUIClassID method, and the setUI method dictates the expected ComponentUI subclass.

To demonstrate, the following is a custom ButtonUI implementation that draws little squares in the corners of a button. The source includes both the UI class and a program that demonstrates its usage.

  import java.awt.*;
  import javax.swing.*;
  import javax.swing.plaf.*;
  import javax.swing.plaf.metal.*;

  public class CornerButtonUI 
      extends MetalButtonUI {
    private final static CornerButtonUI 
      cornerButtonUI = new CornerButtonUI(); 

    public static ComponentUI createUI(JComponent c) {
      return cornerButtonUI;
    }

    public void paint(Graphics g, JComponent c) {
      super.paint(g, c);
      g.setColor(c.getForeground());
      int width = c.getWidth();
      int height = c.getHeight();
      int drawWidth = (int)(width * .15);
      int drawHeight = (int)(height * .10);
      // top left
      g.fillRect(0, 0, drawWidth, drawHeight);
      // top right
      g.fillRect(width-drawWidth, 0, width, 
        drawHeight);
      // bottom left
      g.fillRect(0, height-drawHeight, drawWidth, 
        height);
      // bottom right
      g.fillRect(width-drawWidth, height-drawHeight, 
        width, height);
   }

   public static void main(String args[]) {
      JFrame frame = new JFrame("Corners");
      frame.setDefaultCloseOperation(
        JFrame.EXIT_ON_CLOSE);
      Container c = frame.getContentPane();
      UIManager.put("ButtonUI", "CornerButtonUI");
      JButton corners = new JButton("Corners");
      c.add(corners, BorderLayout.CENTER);
      frame.setSize(200, 100);
      frame.show();
   }
  }

When you run the program, here's what you should see:

Note that the corner button UI example could have also included calculations to take the size of the border into consideration.

Installing the ComponentUI in the UIManager lookup table changes the user interface for all future instances of a specific component type. However, you can change the user interface for a single instance of a Swing component by calling its setUI method, passing in the appropriate new user interface there. This changes the appearance of only the single instance, instead of all instances of the component type.

Yet another mechanism to change the look and feel of a component is to change the installed look and feel for the application. Swing applications work with a pluggable look and feel architecture. This is similar to changing themes. Unlike themes though, which just change the color and font settings, changing the installed look and feel for a Swing application affects all the settings for the different components. Each look and feel relies on a preconfigured table of settings for things such as colors, borders, fonts, icons, component UI elements for each component, and input maps for what keystrokes do with each component. Because everything associated with a Swing component is driven from within the Java runtime environment, everything is customizable, that is, if you know where to look. The ability to change the look and feel gives you the most flexibility, but it also requires the most work.

Before creating your own look and feel, let's look at how to change the current look and feel to a preexisting one. In addition to the default Metal look and feel used by Swing, the standard runtime offers a Microsoft Windows-like and Motif look and feel. (Apple also offers an Aqua look and feel, and others are available from third parties.) With these other look and feels (Windows and Motif), the Swing components take on the look of a native application for those platforms.

To change the look and feel of an application, simply pass the class name into the setLookAndFeel method of the UIManager. You can also ask the UIManager for the name of the look and feel for the native platform.

  UIManager.setLookAndFeel(
    UIManager.getSystemLookAndFeelClassName());

After the look and feel has been changed, all new components created will use the defaults associated with the new look and feel. To change existing components, you need to invoke updateUI for a single component or updateComponentTreeUI for a set of components in a window.

  import java.awt.*;
  import javax.swing.*;

  public class NativeLNF {
    public static void main(String args[]) {
      try {
        UIManager.setLookAndFeel(
          UIManager.getSystemLookAndFeelClassName());
      } catch (Exception e) {
        System.err.println("Unable to change");
      }
      JFrame frame = new JFrame("Native");
      frame.setDefaultCloseOperation(
        JFrame.EXIT_ON_CLOSE);
      Container c = frame.getContentPane();
      JButton top = new JButton("Top");
      c.add(top, BorderLayout.NORTH);
      JButton bottom = new JButton("Bottom");
      c.add(bottom, BorderLayout.SOUTH);
      frame.setSize(200, 100);
      frame.show();
    }
  }

When you run the program, here's what you should see on a UNIX platform (Windows users will see a Microsoft Windows look):

You can create your own look and feel objects, in addition to using the system defined look and feel objects. To create your own objects, you need to define the complete table of lookup settings for every available component. More simply (and more typically), you subclass an existing look and feel, and just customize the handful of settings you don't like. These settings can grow until you customize all the settings. But to start, it is easier to subclass and override the handful you need to than start from scratch and fill in all the settings (of which there are approximately 600). Here's an example that uses this approach.

  import java.awt.*;
  import javax.swing.*;
  import javax.swing.UIDefaults;
  import javax.swing.plaf.metal.MetalLookAndFeel;

  public class CornerButtonsLookAndFeel 
      extends MetalLookAndFeel {
    public String getID() {
      return "CornerButtons";
    }

    public String getName() {
      return "Corner Buttons Look and Feel";
    }

    public String getDescription() {
      return "The Corner Buttons Look and Feel";
    }

    public boolean isNativeLookAndFeel() {
      return false;
    }

    public boolean isSupportedLookAndFeel() {
      return true;
    }

    protected void initClassDefaults(
        UIDefaults table) {
      super.initClassDefaults(table);
      table.put("ButtonUI", "CornerButtonUI");
    }

    public static void main(String args[]) {
      try {
        UIManager.setLookAndFeel(
          "CornerButtonsLookAndFeel");
      } catch (Exception e) {
        System.err.println("Unable to change");
      }
      JFrame frame = new JFrame("CornersLNF");
      frame.setDefaultCloseOperation(
        JFrame.EXIT_ON_CLOSE);
      Container c = frame.getContentPane();
      JButton corners = new JButton("Corners");
      c.add(corners, BorderLayout.CENTER);
      frame.setSize(200, 100);
      frame.show();
   }
  }

When you run the program, here's what you should see:

The look and feel architecture of Swing is very flexible. However, just because the features are available doesn't mean they have to be used. For instance, if all the buttons in your application need to be red, but there is only one button, you're probably better off calling the setBackground method of the component. In that situation, it's easier to do that than worry about the name of the button background property so you can change the button settings in the UIManager.

For more information about changing user interface attributes, see chapter 7 "Pluggable Look and Feel" in "Graphic Java: Mastering the JFC, 3rd Edition Volume II, Swing" by David Geary.

Pixel
Pixel

IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html


Comments? Send your feedback on the Core Java Technologies Tech Tips to: jdc-webmaster@sun.com

Subscribe to the following newsletters for the latest information about technologies and products in other Java platforms:

- Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).

To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".


ARCHIVES: You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html


Copyright 2002 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.


This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html


Sun, Sun Microsystems, Java, Java Developer Connection, J2SE, J2EE, and J2ME are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.