|
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 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 programIf 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:
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 DataJavaBeans 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 ObjectUsing 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
Constructing an Object from PropertiesOften, 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 MethodSometimes, 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
EnumerationsTo 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 WorkThe 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
Predefined DelegatesYou do not have to provide your own delegates for every class. The XMLEncoder has built-in delegates for the following types:
Transient PropertiesOccasionally, 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.java1. 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 PersistenceWe 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 applicationThe application uses JavaBeans persistence to save and load DamageReport objects (see Example 8-16). It illustrates the following aspects of the persistence technology:
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
CAUTION
This example ends our discussion of JavaBeans persistence. In summary, JavaBeans persistence archives are
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
java.beans.Encoder 1.4
java.beans.ExceptionListener 1.4
java.beans.XMLDecoder 1.4
java.beans.PersistenceDelegate 1.4
java.beans.DefaultPersistenceDelegate 1.4
java.beans.Expression 1.4
java.beans.Statement 1.4
|
|