JavaBeans Persistence


JavaBeans persistence uses JavaBeans properties to save beans to a stream and to read them back at a later time or in a different virtual machine. In this regard, JavaBeans persistence is similar to object serialization. (See Volume 1, Chapter 12 for more information on serialization.) However, there is an important difference: JavaBeans persistence is suitable for long-term storage.

When an object is serialized, its instance fields are written to a stream. If the implementation of a class changes, then its instance fields may change. You cannot simply read files that contain serialized objects of older versions. It is possible to detect version differences and translate between old and new data representations. However, the process is extremely tedious and should only be applied in desperate situations. Plainly, serialization is unsuitable for long-term storage. For that reason, all Swing components have the following message in their documentation: "Warning: Serialized objects of this class will not be compatible with future Swing releases. The current serialization support is appropriate for short term storage or RMI between applications."

The long-term persistence mechanism was invented as a solution for this problem. It was originally intended for drag-and-drop GUI design tools. The design tool saves the result of mouse clicksa collection of frames, panels, buttons, and other Swing componentsin a file, using the long-term persistence format. The running program simply opens that file. This approach cuts out the tedious source code for laying out and wiring up Swing components. Sadly, it has not been widely implemented.

NOTE

The Bean Builder at http://bean-builder.dev.java.net is an experimental GUI builder with support for long-term persistence.


The basic idea behind JavaBeans persistence is simple. Suppose you want to save a JFrame object to a file so that you can retrieve it later. If you look into the source code of the JFrame class and its superclasses, then you see dozens of instance fields. If the frame were to be serialized, all of the field values would need to be written. But think about how a frame is constructed:

 JFrame frame = new JFrame(); frame.setTitle("My Application"); frame.setVisible(true); 

The default constructor initializes all instance fields, and a couple of properties are set. If you archive the frame object, the JavaBeans persistence mechanism saves exactly these statements in XML format:

 <object >    <void property="title">      <string>My Application</string>    </void>    <void property="visible">      <boolean>true</boolean>    </void> </object> 

When the object is read back, the statements are executed: A JFrame object is constructed, and its title and visible properties are set to the given values. It does not matter if the internal representation of the JFrame has changed in the meantime. All that matters is that you can restore the object by setting properties.

Note that only those properties that are different from the default are archived. The XMLEncoder makes a default JFrame and compares its property with the frame that is being archived. Property setter statements are generated only for properties that are different from the default. This process is called redundancy elimination. As a result, the archives are generally smaller than the result of serialization. (When serializing Swing components, the difference is particularly dramatic since Swing objects have a lot of state, most of which is never changed from the default.)

Of course, there are minor technical hurdles with this approach. For example, the call

 frame.setSize(600, 400); 

is not a property setter. However, the XMLEncoder can cope with this: It writes the statement

 <void property="bounds">    <object >        <int>0</int>        <int>0</int>        <int>600</int>        <int>400</int>    </object> </void> 

To save an object to a stream, use an XMLEncoder:

 XMLEncoder out = new XMLEncoder(new FileOutputStream(. . .)); out.writeObject(frame); out.close(); 

To read it back, use an XMLDecoder:

 XMLDecoder in = new XMLDecoder(new FileInputStream(. . .)); JFrame newFrame = (JFrame) in.readObject(); in.close(); 

The program in Example 8-12 shows how a frame can load and save itself (see Figure 8-15). When you run the program, first click the Save button and save the frame to a file. Then move the original frame to a different position and click Load to see another frame pop up at the original location. Have a look inside the XML file that the program produces.

Figure 8-15. The PersistentFrameTest program


If you look closely at the XML output, you will find that the XMLEncoder carries out an amazing amount of work when it saves the frame. The XMLEncoder produces statements that carry out the following actions:

  • Set various frame properties: size, layout, defaultCloseOperation, title, and so on.

  • Add buttons to the frame.

  • Add action listeners to the buttons.

Here, we had to construct the action listers with the EventHandler class. The XMLEncoder cannot archive arbitrary inner classes, but it knows how to handle EventHandler objects.

Example 8-12. PersistentFrameTest.java

[View full width]

  1. import java.awt.*;  2. import java.awt.event.*;  3. import java.beans.*;  4. import java.io.*;  5. import javax.swing.*;  6.  7. /**  8.    This program demonstrates the use of an XML encoder and decoder to save and restore  a frame.  9. */ 10. public class PersistentFrameTest 11. { 12.    public static void main(String[] args) 13.    { 14.       chooser = new JFileChooser(); 15.       chooser.setCurrentDirectory(new File(".")); 16.       PersistentFrameTest test = new PersistentFrameTest(); 17.       test.init(); 18.    } 19. 20.    public void init() 21.    { 22.       frame = new JFrame(); 23.       frame.setLayout(new FlowLayout()); 24.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 25.       frame.setTitle("PersistentFrameTest"); 26.       frame.setSize(400, 200); 27. 28.       JButton loadButton = new JButton("Load"); 29.       frame.add(loadButton); 30.       loadButton.addActionListener(EventHandler.create(ActionListener.class, this,  "load")); 31. 32.       JButton saveButton = new JButton("Save"); 33.       frame.add(saveButton); 34.       saveButton.addActionListener(EventHandler.create(ActionListener.class, this,  "save")); 35. 36.       frame.setVisible(true); 37.    } 38. 39.    public void load() 40.    { 41.       // show file chooser dialog 42.       int r = chooser.showOpenDialog(null); 43. 44.       // if file selected, open 45.       if(r == JFileChooser.APPROVE_OPTION) 46.       { 47.          try 48.          { 49.             File file = chooser.getSelectedFile(); 50.             XMLDecoder decoder = new XMLDecoder(new FileInputStream(file)); 51.             JFrame newFrame = (JFrame) decoder.readObject(); 52.             decoder.close(); 53.          } 54.          catch (IOException e) 55.          { 56.             JOptionPane.showMessageDialog(null, e); 57.          } 58.       } 59.    } 60. 61.    public void save() 62.    { 63.       // show file chooser dialog 64.       int r = chooser.showSaveDialog(null); 65. 66.       // if file selected, save 67.       if(r == JFileChooser.APPROVE_OPTION) 68.       { 69.          try 70.          { 71.             File file = chooser.getSelectedFile(); 72.             XMLEncoder encoder = new XMLEncoder(new FileOutputStream(file)); 73.             encoder.writeObject(frame); 74.             encoder.close(); 75.          } 76.          catch (IOException e) 77.          { 78.             JOptionPane.showMessageDialog(null, e); 79.          } 80.       } 81.    } 82. 83.    private static JFileChooser chooser; 84.    private JFrame frame; 85. } 

Using JavaBeans Persistence for Arbitrary Data

JavaBeans persistence is not limited to the storage of Swing components. You can use the mechanism to store any collection of objects, provided you follow a few simple rules. In the following sections, you learn how you can use JavaBeans persistence as a long-term storage format for your own data.

Writing a Persistence Delegate to Construct an Object

Using JavaBeans persistence is trivial if one can obtain the state of every object by setting properties. But in real programs, there are always classes that don't work that way. Consider, for example, the Employee class of Volume 1, Chapter 4. Employee isn't a well-behaved bean. It doesn't have a default constructor, and it doesn't have methods setName, setSalary, setHireDay. To overcome this problem, you install a persistence delegate into the XMLWriter that knows how to write Employee objects:

 out.setPersistenceDelegate(Employee.class, delegate); 

The persistence delegate for the Employee class overrides the instantiate method to produce an expression that constructs an object.

   PersistenceDelegate delegate = new       DefaultPersistenceDelegate()       {          protected Expression instantiate(Object oldInstance, Encoder out)          {             Employee e = (Employee) oldInstance;             GregorianCalendar c = new GregorianCalendar();             c.setTime(e.getHireDay());             return new Expression(oldInstance, Employee.class, "new",                new Object[]                {                   e.getName(),                   e.getSalary(),                   c.get(Calendar.YEAR),                   c.get(Calendar.MONTH),                   c.get(Calendar.DATE)                });          }       }; 

This means: "To re-create oldInstance, call the new method (i.e., the constructor) on the Employee.class object, and supply the given parameters." The parameter name oldInstance is a bit misleadingthis is simply the instance that is being saved.

Once the delegate is installed, you can save Employee objects. For example, the statements

 Object myData = new Employee("Harry Hacker", 50000, 1989, 10, 1); out.writeObject(myData); 

generate the following output:

 <object >    <string>Harry Hacker</string>    <double>50000.0</double>    <int>1989</int>    <int>9</int>    <int>1</int> </object> 

NOTE

You only need to tweak the encoding process. There are no special decoding methods. The decoder simply executes the statements and expressions that it finds in its XML input.


Constructing an Object from Properties

Often, you can use this shortcut: If all constructor parameters can be obtained by accessing properties of oldInstance, then you need not write the instantiate method yourself. Instead, simply construct a DefaultPersistenceDelegate and supply the property names.

For example, the following statement sets the persistence delegate for the Rectangle2D.Double class:

 out.setPersistenceDelegate(Rectangle2D.Double.class,    new DefaultPersistenceDelegate(new String[] { "x", "y", "width", "height" })); 

This tells the encoder: "To encode a Rectangle2D.Double object, get its x, y, width, and height properties and call the constructor with those four values." As a result, the output contains an element such as the following:

 <object >    <double>5.0</double>    <double>10.0</double>    <double>20.0</double>    <double>30.0</double> </object> 

Constructing an Object with a Factory Method

Sometimes, you need to save objects that are obtained from factory methods, not constructors. Consider, for example, how you get an InetAddress object:

 byte[] bytes = new byte[] { 127, 0, 0, 1}; InetAddress address = InetAddress.getByAddress(bytes); 

The instantiate method of the PersistenceDelegate produces a call to the factory method.

 protected Expression instantiate(Object oldInstance, Encoder out) {    return new Expression(oldInstance, InetAddress.class, "getByAddress",       new Object[] { ((InetAddress) oldInstance).getAddress() }); } 

A sample output is

 <object  method="getByAddress">    <array  length="4">       <void index="0">          <byte>127</byte>       </void>       <void index="3">          <byte>1</byte>       </void>    </array> </object> 

CAUTION

You must install this delegate with the concrete subclass, such as Inet4Address, not with the abstract InetAddress class!


Enumerations

To save an enum value, you supply a very simple delegate:

 enum Mood { SAD, HAPPY }; . . . out.setPersistenceDelegate(Mood.class, new EnumDelegate()); 

You find the EnumDelegate class in Example 8-14 on page 680. For example, if you save Mood.SAD, the delegate writes an expression that is equivalent to Enum.valueOf(Mood.class, "SAD"):

 <object  method="valueOf">    <class>Mood</class>    <string>SAD</string> </object> 

Post-Construction Work

The state of some classes is built up by calls to methods that are not property setters. You can cope with that situation by overriding the initialize method of the DefaultPersistenceDelegate. The initialize method is called after the instantiate method. You can generate a sequence of statements that are recorded in the archive.

For example, consider the BitSet class. To re-create a BitSet object, you set all the bits that were present in the original. The following initialize method generates the necessary statements:

 protected void initialize(Class type, Object oldInstance, Object newInstance, Encoder out) {    super.initialize(type, oldInstance, newInstance, out);    BitSet bs = (BitSet) oldInstance;    for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1))    out.writeStatement(new Statement(bs, "set", new Object[] { i, i + 1, true } )); } 

A sample output is

 <object >    <void method="set">       <int>1</int>       <int>2</int>       <boolean>true</boolean>    </void>    <void method="set">       <int>4</int>       <int>5</int>       <boolean>true</boolean>    </void> </object> 

NOTE

It would make more sense to write new Statement(bs, "set", new Object[] { i } ), but then the XMLWriter produces an unsightly statement that sets a property with an empty name.


Predefined Delegates

You do not have to provide your own delegates for every class. The XMLEncoder has built-in delegates for the following types:

  • null

  • All primitive types and their wrappers

  • String

  • Arrays

  • Collections and maps

  • The reflection types Class, Field, Method, and Proxy

  • The AWT types Color, Cursor, Dimension, Font, Insets, Point, Rectangle, and ImageIcon

  • AWT and Swing components, borders, layout managers, and models

  • Event handlers

Transient Properties

Occasionally, a class has a property with a getter and setter that the XMLDecoder discovers, but you don't want to include the property value in the archive. To suppress archiving of a property, mark it as transient in the property descriptor. For example, the following statement tells the GregorianCalendar class not to archive the gregorianChange property:

 BeanInfo info = Introspector.getBeanInfo(GregorianCalendar.class); for (PropertyDescriptor desc : info.getPropertyDescriptors())    if (desc.getName().equals("gregorianChange"))       desc.setValue("transient", Boolean.TRUE); 

The setValue method can store arbitrary information with a property descriptor. The XMLEncoder queries the TRansient attribute before generating a property setter statement.

The program in Example 8-13 shows the various persistence delegates at work. Keep in mind that this program shows a worst-case scenarioin actual applications, many classes can be archived without the use of delegates.

Example 8-13. PersistenceDelegateTest.java

[View full width]

   1. import java.awt.*;   2. import java.awt.geom.*;   3. import java.beans.*;   4. import java.io.*;   5. import java.net.*;   6. import java.util.*;   7.   8. /**   9.    This program demonstrates various persistence delegates.  10. */  11. public class PersistenceDelegateTest  12. {  13.    public enum Mood { SAD, HAPPY };  14.  15.    public static void main(String[] args) throws Exception  16.    {  17.       XMLEncoder out = new XMLEncoder(System.out);  18.       out.setExceptionListener(new  19.          ExceptionListener()  20.          {  21.             public void exceptionThrown(Exception e)  22.             {  23.                e.printStackTrace();  24.             }  25.          });  26.  27.       PersistenceDelegate delegate = new  28.          DefaultPersistenceDelegate()  29.          {  30.             protected Expression instantiate(Object oldInstance, Encoder out)  31.             {  32.                Employee e = (Employee) oldInstance;  33.                GregorianCalendar c = new GregorianCalendar();  34.                c.setTime(e.getHireDay());  35.                return new Expression(oldInstance, Employee.class, "new",  36.                   new Object[]  37.                   {  38.                      e.getName(),  39.                      e.getSalary(),  40.                      c.get(Calendar.YEAR),  41.                      c.get(Calendar.MONTH),  42.                      c.get(Calendar.DATE)  43.                   });  44.             }  45.          };  46.  47.       out.setPersistenceDelegate(Employee.class, delegate);  48.  49.       out.setPersistenceDelegate(Rectangle2D.Double.class,  50.          new DefaultPersistenceDelegate(new String[] { "x", "y", "width", "height" }));  51.  52.       out.setPersistenceDelegate(Inet4Address.class, new  53.          DefaultPersistenceDelegate()  54.          {  55.             protected Expression instantiate(Object oldInstance, Encoder out)  56.             {  57.                return new Expression(oldInstance, InetAddress.class, "getByAddress",  58.                   new Object[] { ((InetAddress) oldInstance).getAddress() });  59.             }  60.          });  61.  62.       out.setPersistenceDelegate(BitSet.class, new  63.          DefaultPersistenceDelegate()  64.          {  65.             protected void initialize(Class type, Object oldInstance, Object newInstance,  66.                Encoder out)  67.             {  68.                super.initialize(type, oldInstance, newInstance, out);  69.                BitSet bs = (BitSet) oldInstance;  70.                for(int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1))  71.                   out.writeStatement(new Statement(bs, "set", new Object[]{ i, i + 1,  true }));  72.             }  73.          });  74.  75.       out.setPersistenceDelegate(Mood.class, new EnumDelegate());  76.  77.       out.writeObject(new Employee("Harry Hacker", 50000, 1989, 10, 1));  78.       out.writeObject(new java.awt.geom.Rectangle2D.Double(5, 10, 20, 30));  79.       out.writeObject(InetAddress.getLocalHost());  80.       out.writeObject(Mood.SAD);  81.       BitSet bs = new BitSet(); bs.set(1, 4); bs.clear(2, 3);  82.       out.writeObject(bs);  83.       out.writeObject(Color.PINK);  84.       out.writeObject(new GregorianCalendar());  85.       out.close();  86.    }  87.  88.    static  89.    {  90.       try  91.       {  92.          BeanInfo info = Introspector.getBeanInfo(GregorianCalendar.class);  93.          for (PropertyDescriptor desc : info.getPropertyDescriptors())  94.             if (desc.getName().equals("gregorianChange"))  95.                desc.setValue("transient", Boolean.TRUE);  96.       }  97.       catch (IntrospectionException e)  98.       {  99.          e.printStackTrace(); 100.       } 101.    } 102. } 

Example 8-14. EnumDelegate.java
  1. import java.beans.*;  2.  3. /**  4.    This class can be used to save any enum type in a JavaBeans archive.  5. */  6. public class EnumDelegate extends DefaultPersistenceDelegate  7. {  8.    protected Expression instantiate(Object oldInstance, Encoder out)  9.    { 10.       return new Expression(Enum.class, 11.          "valueOf", 12.          new Object[] { oldInstance.getClass(), ((Enum) oldInstance).name() }); 13.    } 14. } 

A Complete Example for JavaBeans Persistence

We end the description of JavaBeans persistence with a complete example (see Figure 8-16). This application writes a damage report for a rental car. The rental car agent enters the rental record, selects the car type, uses the mouse to click on damaged areas on the car, and saves the report. The application can also load existing damage reports. Example 8-15 contains the code for the program.

Figure 8-16. The DamageReporter application


The application uses JavaBeans persistence to save and load DamageReport objects (see Example 8-16). It illustrates the following aspects of the persistence technology:

  • Properties are automatically saved and restored. Nothing needs to be done for the rentalRecord and carType properties.

  • Post-construction work is required to restore the damage locations. The persistence delegate generates statements that call the click method.

  • The Point2D.Double class needs a DefaultPersistenceDelegate that constructs a point from its x and y properties.

  • An EnumDelegate is required to handle the enumerated type CarType.

  • The removeMode property (which specifies whether mouse clicks add or remove damage marks) is transient since it should not be saved in damage reports.

Here is a sample damage report:

 <?xml version="1.0" encoding="UTF-8"?> <java version="1.5.0" >    <object >       <object  method="valueOf">          <class>DamageReport$CarType</class>          <string>SEDAN</string>       </object>       <void property="rentalRecord">          <string>12443-19</string>       </void>       <void method="click">          <object >             <double>181.0</double>             <double>84.0</double>          </object>       </void>       <void method="click">          <object >             <double>162.0</double>             <double>66.0</double>          </object>       </void>    </object> </java> 

NOTE

The sample application does not use JavaBeans persistence to save the GUI of the application. That may be of interest to creators of development tools, but here we are focusing on how to use the persistence mechanism to store application data.


CAUTION

At the time of this writing, JavaBeans persistence is not compatible with Java Web Startsee bug 4741757 in the "bug parade" at http://bugs.sun.com.


This example ends our discussion of JavaBeans persistence. In summary, JavaBeans persistence archives are

  • Suitable for long-term storage;

  • Small and fast;

  • Easy to create;

  • Human editable; and

  • A part of standard Java.

Example 8-15. DamageReporter.java

[View full width]

   1. import java.awt.*;   2. import java.awt.event.*;   3. import java.awt.geom.*;   4. import java.beans.*;   5. import java.io.*;   6. import java.util.*;   7. import javax.swing.*;   8.   9. /**  10.    This program demonstrates the use of an XML encoder and decoder.  11.    All GUI and drawing code is collected in this class. The only  12.    interesting pieces are the action listeners for openItem and  13.    saveItem. Look inside the DamageReport class for encoder customizations.  14. */  15. public class DamageReporter extends JFrame  16. {  17.    public static void main(String[] args)  18.    {  19.       JFrame frame = new DamageReporter();  20.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  21.       frame.setVisible(true);  22.    }  23.  24.    public DamageReporter()  25.    {  26.       setTitle("DamageReporter");  27.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);  28.  29.       chooser = new JFileChooser();  30.       chooser.setCurrentDirectory(new File("."));  31.  32.       report = new DamageReport();  33.       report.setCarType(DamageReport.CarType.SEDAN);  34.  35.       // set up the menu bar  36.       JMenuBar menuBar = new JMenuBar();  37.       setJMenuBar(menuBar);  38.  39.       JMenu menu = new JMenu("File");  40.       menuBar.add(menu);  41.  42.       JMenuItem openItem = new JMenuItem("Open");  43.       menu.add(openItem);  44.       openItem.addActionListener(new  45.          ActionListener()  46.          {  47.             public void actionPerformed(ActionEvent evt)  48.             {  49.                // show file chooser dialog  50.                int r = chooser.showOpenDialog(null);  51.  52.                // if file selected, open  53.                if(r == JFileChooser.APPROVE_OPTION)  54.                {  55.                   try  56.                   {  57.                      File file = chooser.getSelectedFile();  58.                      XMLDecoder decoder = new XMLDecoder(new FileInputStream(file));  59.                      report = (DamageReport) decoder.readObject();  60.                      decoder.close();  61.                      repaint();  62.                   }  63.                   catch (IOException e)  64.                   {  65.                      JOptionPane.showMessageDialog(null, e);  66.                   }  67.                }  68.             }  69.          });  70.  71.       JMenuItem saveItem = new JMenuItem("Save");  72.       menu.add(saveItem);  73.       saveItem.addActionListener(new  74.          ActionListener()  75.          {  76.             public void actionPerformed(ActionEvent evt)  77.             {  78.                report.setRentalRecord(rentalRecord.getText());  79.                chooser.setSelectedFile(new File(rentalRecord.getText() + ".xml"));  80.  81.                // show file chooser dialog  82.                int r = chooser.showSaveDialog(null);  83.  84.                // if file selected, save  85.                if(r == JFileChooser.APPROVE_OPTION)  86.                {  87.                   try  88.                   {  89.                      File file = chooser.getSelectedFile();  90.                      XMLEncoder encoder = new XMLEncoder(new FileOutputStream(file));  91.                      report.configureEncoder(encoder);  92.                      encoder.writeObject(report);  93.                      encoder.close();  94.                   }  95.                   catch (IOException e)  96.                   {  97.                      JOptionPane.showMessageDialog(null, e);  98.                   }  99.                } 100.             } 101.          }); 102. 103.       JMenuItem exitItem = new JMenuItem("Exit"); 104.       menu.add(exitItem); 105.       exitItem.addActionListener(new 106.          ActionListener() 107.          { 108.             public void actionPerformed(ActionEvent event) 109.             { 110.                System.exit(0); 111.             } 112.          }); 113. 114.       // combo box for car type 115.       rentalRecord = new JTextField(); 116.       carType = new JComboBox(); 117.       carType.addItem(DamageReport.CarType.SEDAN); 118.       carType.addItem(DamageReport.CarType.WAGON); 119.       carType.addItem(DamageReport.CarType.SUV); 120. 121.       carType.addActionListener(new 122.          ActionListener() 123.          { 124.             public void actionPerformed(ActionEvent event) 125.             { 126.                DamageReport.CarType item = (DamageReport.CarType) carType .getSelectedItem(); 127.                report.setCarType(item); 128.                repaint(); 129.             } 130.          }); 131. 132.       // panel for showing car 133.       carPanel = new 134.          JPanel() 135.          { 136.             public void paintComponent(Graphics g) 137.             { 138.                super.paintComponent(g); 139.                Graphics2D g2 = (Graphics2D) g; 140.                g2.draw((Shape) shapes.get(report.getCarType())); 141.                report.drawDamage(g2); 142.             } 143.          }; 144.       carPanel.addMouseListener(new 145.          MouseAdapter() 146.          { 147.             public void mousePressed(MouseEvent event) 148.             { 149.                report.click(new Point2D.Double(event.getX(), event.getY())); 150.                repaint(); 151.             } 152.          }); 153.       carPanel.setBackground(new Color(0.9f, 0.9f, 0.45f)); 154. 155.       // radio buttons for click action 156.       addButton = new JRadioButton("Add"); 157.       removeButton = new JRadioButton("Remove"); 158.       ButtonGroup group = new ButtonGroup(); 159.       JPanel buttonPanel = new JPanel(); 160.       group.add(addButton); 161.       buttonPanel.add(addButton); 162.       group.add(removeButton); 163.       buttonPanel.add(removeButton); 164.       addButton.setSelected(!report.getRemoveMode()); 165.       removeButton.setSelected(report.getRemoveMode()); 166.       addButton.addActionListener(new 167.          ActionListener() 168.          { 169.             public void actionPerformed(ActionEvent event) 170.             { 171.                report.setRemoveMode(false); 172.             } 173.          }); 174.       removeButton.addActionListener(new 175.          ActionListener() 176.          { 177.             public void actionPerformed(ActionEvent event) 178.             { 179.                report.setRemoveMode(true); 180.             } 181.          }); 182. 183.       // layout components 184.       JPanel gridPanel = new JPanel(); 185.       gridPanel.setLayout(new GridLayout(0, 2)); 186.       gridPanel.add(new JLabel("Rental Record")); 187.       gridPanel.add(rentalRecord); 188.       gridPanel.add(new JLabel("Type of Car")); 189.       gridPanel.add(carType); 190.       gridPanel.add(new JLabel("Operation")); 191.       gridPanel.add(buttonPanel); 192. 193.       add(gridPanel, BorderLayout.NORTH); 194.       add(carPanel, BorderLayout.CENTER); 195.    } 196. 197.    private JTextField rentalRecord; 198.    private JComboBox carType; 199.    private JPanel carPanel; 200.    private JRadioButton addButton; 201.    private JRadioButton removeButton; 202.    private DamageReport report; 203.    private JFileChooser chooser; 204. 205.    private static final int DEFAULT_WIDTH = 400; 206.    private static final int DEFAULT_HEIGHT = 400; 207. 208.    private static Map<DamageReport.CarType, Shape> shapes 209.       = new EnumMap<DamageReport.CarType, Shape>(DamageReport.CarType.class); 210. 211.    static 212.    { 213.       int width = 200; 214.       int height = 100; 215.       int x = 50; 216.       int y = 50; 217.       Rectangle2D.Double body = new Rectangle2D.Double(x, y + width / 6, width - 1,  width / 6); 218.       Ellipse2D.Double frontTire = new Ellipse2D.Double(x + width / 6, y + width / 3, 219.          width / 6, width / 6); 220.       Ellipse2D.Double rearTire = new Ellipse2D.Double(x + width * 2 / 3, y + width / 3, 221.          width / 6, width / 6); 222. 223.       Point2D.Double p1 = new Point2D.Double(x + width / 6, y + width / 6); 224.       Point2D.Double p2 = new Point2D.Double(x + width / 3, y); 225.       Point2D.Double p3 = new Point2D.Double(x + width * 2 / 3, y); 226.       Point2D.Double p4 = new Point2D.Double(x + width * 5 / 6, y + width / 6); 227. 228.       Line2D.Double frontWindshield = new Line2D.Double(p1, p2); 229.       Line2D.Double roofTop = new Line2D.Double(p2, p3); 230.       Line2D.Double rearWindshield = new Line2D.Double(p3, p4); 231. 232.       GeneralPath sedanPath = new GeneralPath(); 233.       sedanPath.append(frontTire, false); 234.       sedanPath.append(rearTire, false); 235.       sedanPath.append(body, false); 236.       sedanPath.append(frontWindshield, false); 237.       sedanPath.append(roofTop, false); 238.       sedanPath.append(rearWindshield, false); 239.       shapes.put(DamageReport.CarType.SEDAN, sedanPath); 240. 241.       Point2D.Double p5 = new Point2D.Double(x + width * 11 / 12, y); 242.       Point2D.Double p6 = new Point2D.Double(x + width, y + width / 6); 243.       roofTop = new Line2D.Double(p2, p5); 244.       rearWindshield = new Line2D.Double(p5, p6); 245. 246.       GeneralPath wagonPath = new GeneralPath(); 247.       wagonPath.append(frontTire, false); 248.       wagonPath.append(rearTire, false); 249.       wagonPath.append(body, false); 250.       wagonPath.append(frontWindshield, false); 251.       wagonPath.append(roofTop, false); 252.       wagonPath.append(rearWindshield, false); 253.       shapes.put(DamageReport.CarType.WAGON, wagonPath); 254. 255.       Point2D.Double p7 = new Point2D.Double(x + width / 3, y - width / 6); 256.       Point2D.Double p8 = new Point2D.Double(x + width * 11 / 12, y - width / 6); 257.       frontWindshield = new Line2D.Double(p1, p7); 258.       roofTop = new Line2D.Double(p7, p8); 259.       rearWindshield = new Line2D.Double(p8, p6); 260. 261.       GeneralPath suvPath = new GeneralPath(); 262.       suvPath.append(frontTire, false); 263.       suvPath.append(rearTire, false); 264.       suvPath.append(body, false); 265.       suvPath.append(frontWindshield, false); 266.       suvPath.append(roofTop, false); 267.       suvPath.append(rearWindshield, false); 268.       shapes.put(DamageReport.CarType.SUV, suvPath); 269.    } 270. } 

Example 8-16. DamageReport.java

[View full width]

   1. import java.awt.*;   2. import java.awt.geom.*;   3. import java.beans.*;   4. import java.util.*;   5.   6. /**   7.    This class describes a vehicle damage report that will be   8.    saved and loaded with the long-term persistence mechanism.   9. */  10. public class DamageReport  11. {  12.    public enum CarType { SEDAN, WAGON, SUV }  13.  14.    // this property is saved automatically  15.    public void setRentalRecord(String newValue)  16.    {  17.       rentalRecord = newValue;  18.    }  19.  20.    public String getRentalRecord()  21.    {  22.       return rentalRecord;  23.    }  24.  25.    // this property is saved automatically  26.    public void setCarType(CarType newValue)  27.    {  28.       carType = newValue;  29.    }  30.  31.    public CarType getCarType()  32.    {  33.       return carType;  34.    }  35.  36.    // this property is set to be transient  37.    public void setRemoveMode(boolean newValue)  38.    {  39.       removeMode = newValue;  40.    }  41.  42.    public boolean getRemoveMode()  43.    {  44.       return removeMode;  45.    }  46.  47.    public void click(Point2D p)  48.    {  49.       if (removeMode)  50.       {  51.          for (Point2D center : points)  52.          {  53.             Ellipse2D circle = new Ellipse2D.Double(  54.                center.getX() - MARK_SIZE, center.getY() - MARK_SIZE,  55.                2 * MARK_SIZE, 2 * MARK_SIZE);  56.             if (circle.contains(p))  57.             {  58.                points.remove(center);  59.                return;  60.             }  61.          }  62.       }  63.       else points.add(p);  64.    }  65.  66.    public void drawDamage(Graphics2D g2)  67.    {  68.       g2.setPaint(Color.RED);  69.       for (Point2D center : points)  70.       {  71.          Ellipse2D circle = new Ellipse2D.Double(  72.             center.getX() - MARK_SIZE, center.getY() - MARK_SIZE,  73.             2 * MARK_SIZE, 2 * MARK_SIZE);  74.          g2.draw(circle);  75.       }  76.    }  77.  78.    public void configureEncoder(XMLEncoder encoder)  79.    {  80.       // this step is necessary to save Point2D.Double objects  81.       encoder.setPersistenceDelegate(  82.          Point2D.Double.class,  83.          new DefaultPersistenceDelegate(new String[]{ "x", "y" }) );  84.  85.       // this step is necessary to save the enumerated Type CarType  86.       encoder.setPersistenceDelegate(CarType.class, new EnumDelegate());  87.  88.       // this step is necessary because the array list of points is not  89.       // (and should not be) exposed as a property  90.       encoder.setPersistenceDelegate(  91.          DamageReport.class, new  92.             DefaultPersistenceDelegate()  93.             {  94.                protected void initialize(Class type, Object oldInstance, Object  newInstance,  95.                   Encoder out)  96.                {  97.                   super.initialize(type, oldInstance, newInstance, out);  98.                   DamageReport r = (DamageReport) oldInstance;  99. 100.                   for (Point2D p : r. points) 101.                      out.writeStatement(new Statement(oldInstance,"click", new  Object[]{ p }) ); 102.                } 103.             }); 104. 105.    } 106. 107.    // this step is necessary to make the removeMode property transient 108.    static 109.    { 110.       try 111.       { 112.          BeanInfo info = Introspector.getBeanInfo(DamageReport.class); 113.          for (PropertyDescriptor desc : info.getPropertyDescriptors()) 114.             if (desc.getName().equals("removeMode")) 115.                desc.setValue("transient", Boolean.TRUE); 116.       } 117.       catch (IntrospectionException e) 118.       { 119.          e.printStackTrace(); 120.       } 121.    } 122. 123.    private String rentalRecord; 124.    private CarType carType; 125.    private boolean removeMode; 126.    private ArrayList<Point2D> points = new ArrayList<Point2D>(); 127. 128.    private static final int MARK_SIZE = 5; 129. } 


 java.beans.XMLEncoder 1.4 

  • XMLEncoder(OutputStream out)

    constructs an XMLEncoder that sends its output to the given stream.

  • void writeObject(Object obj)

    archives the given object.

  • void writeStatement(Statement stat)

    writes the given statement to the archive. This method should only be called from a persistence delegate.


 java.beans.Encoder 1.4 

  • void setPersistenceDelegate(Class<?> type, PersistenceDelegate delegate)

  • PersistenceDelegate getPersistenceDelegate(Class<?> type)

    set or get the delegate for archiving objects of the given type.

  • void setExceptionListener(ExceptionListener listener)

  • ExceptionListener getExceptionListener()

    set or get the exception listener that is notified if an exception occurs during the encoding process.


 java.beans.ExceptionListener 1.4 

  • void exceptionThrown(Exception e)

    is called when an exception was thrown during the encoding or decoding process.


 java.beans.XMLDecoder 1.4 

  • XMLDecoder(InputStream in)

    constructs an XMLDecoder that reads an archive from the given input stream.

  • Object readObject()

    reads the next object from the archive.

  • void setExceptionListener(ExceptionListener listener)

  • ExceptionListener getExceptionListener()

    set or get the exception listener that is notified if an exception occurs during the encoding process.


 java.beans.PersistenceDelegate 1.4 

  • protected abstract Expression instantiate(Object oldInstance, Encoder out)

    returns an expression for instantiating an object that is equivalent to oldInstance.

  • protected void initialize(Class<?> type, Object oldInstance, Object newInstance, Encoder out)

    writes statements to out that turn newInstance into an object that is equivalent to oldInstance.


 java.beans.DefaultPersistenceDelegate 1.4 

  • DefaultPersistenceDelegate()

    constructs a persistence delegate for a class with a zero-parameter constructor.

  • DefaultPersistenceDelegate(String[] propertyNames)

    constructs a persistence delegate for a class whose construction parameters are the values of the given properties.

  • protected Expression instantiate(Object oldInstance, Encoder out)

    returns an expression for invoking the constructor with either no parameters or the values of the properties specified in the constructor.

  • protected void initialize(Class<?> type, Object oldInstance, Object newInstance, Encoder out)

    writes statements to out that apply property setters to newInstance, attempting to turn it into an object that is equivalent to oldInstance.


 java.beans.Expression 1.4 

  • Expression(Object value, Object target, String methodName, Object[] parameters)

    constructs an expression that calls the given method on target, with the given parameters. The result of the expression is assumed to be value. To call a constructor, target should be a Class object and methodName should be "new".


 java.beans.Statement 1.4 

  • Statement(Object target, String methodName, Object[] parameters)

    constructs a statement that calls the given method on target, with the given parameters.



    Core JavaT 2 Volume II - Advanced Features
    Building an On Demand Computing Environment with IBM: How to Optimize Your Current Infrastructure for Today and Tomorrow (MaxFacts Guidebook series)
    ISBN: 193164411X
    EAN: 2147483647
    Year: 2003
    Pages: 156
    Authors: Jim Hoskins

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net