001/* FormView.java -- A view for a variety of HTML form elements 002 Copyright (C) 2006 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package javax.swing.text.html; 040 041import java.awt.Component; 042import java.awt.Point; 043import java.awt.event.ActionEvent; 044import java.awt.event.ActionListener; 045import java.awt.event.MouseAdapter; 046import java.awt.event.MouseEvent; 047import java.io.IOException; 048import java.io.OutputStreamWriter; 049import java.io.PrintWriter; 050import java.net.MalformedURLException; 051import java.net.URL; 052import java.net.URLConnection; 053import java.net.URLEncoder; 054 055import javax.swing.ButtonModel; 056import javax.swing.ImageIcon; 057import javax.swing.JButton; 058import javax.swing.JCheckBox; 059import javax.swing.JComboBox; 060import javax.swing.JEditorPane; 061import javax.swing.JList; 062import javax.swing.JPasswordField; 063import javax.swing.JRadioButton; 064import javax.swing.JScrollPane; 065import javax.swing.JTextArea; 066import javax.swing.JTextField; 067import javax.swing.ListSelectionModel; 068import javax.swing.SwingUtilities; 069import javax.swing.UIManager; 070import javax.swing.event.HyperlinkEvent; 071import javax.swing.text.AttributeSet; 072import javax.swing.text.BadLocationException; 073import javax.swing.text.ComponentView; 074import javax.swing.text.Document; 075import javax.swing.text.Element; 076import javax.swing.text.ElementIterator; 077import javax.swing.text.StyleConstants; 078 079/** 080 * A View that renders HTML form elements like buttons and input fields. 081 * This is implemented as a {@link ComponentView} that creates different Swing 082 * component depending on the type and setting of the different form elements. 083 * 084 * Namely, this view creates the following components: 085 * <table> 086 * <tr><th>Element type</th><th>Swing component</th></tr> 087 * <tr><td>input, button</td><td>JButton</td></tr> 088 * <tr><td>input, checkbox</td><td>JButton</td></tr> 089 * <tr><td>input, image</td><td>JButton</td></tr> 090 * <tr><td>input, password</td><td>JButton</td></tr> 091 * <tr><td>input, radio</td><td>JButton</td></tr> 092 * <tr><td>input, reset</td><td>JButton</td></tr> 093 * <tr><td>input, submit</td><td>JButton</td></tr> 094 * <tr><td>input, text</td><td>JButton</td></tr> 095 * <tr><td>select, size > 1 or with multiple attribute</td> 096 * <td>JList in JScrollPane</td></tr> 097 * <tr><td>select, size unspecified or == 1</td><td>JComboBox</td></tr> 098 * <tr><td>textarea, text</td><td>JTextArea in JScrollPane</td></tr> 099 * <tr><td>input, file</td><td>JTextField</td></tr> 100 * </table> 101 * 102 * @author Roman Kennke (kennke@aicas.com) 103 */ 104public class FormView 105 extends ComponentView 106 implements ActionListener 107{ 108 109 protected class MouseEventListener 110 extends MouseAdapter 111 { 112 /** 113 * Creates a new <code>MouseEventListener</code>. 114 */ 115 protected MouseEventListener() 116 { 117 // Nothing to do here. 118 } 119 120 public void mouseReleased(MouseEvent ev) 121 { 122 String data = getImageData(ev.getPoint()); 123 imageSubmit(data); 124 } 125 } 126 127 /** 128 * Actually submits the form data. 129 */ 130 private class SubmitThread 131 extends Thread 132 { 133 /** 134 * The submit data. 135 */ 136 private String data; 137 138 /** 139 * Creates a new SubmitThread. 140 * 141 * @param d the submit data 142 */ 143 SubmitThread(String d) 144 { 145 data = d; 146 } 147 148 /** 149 * Actually performs the submit. 150 */ 151 public void run() 152 { 153 if (data.length() > 0) 154 { 155 final String method = getMethod(); 156 final URL actionURL = getActionURL(); 157 final String target = getTarget(); 158 URLConnection conn; 159 final JEditorPane editor = (JEditorPane) getContainer(); 160 final HTMLDocument doc = (HTMLDocument) editor.getDocument(); 161 HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit(); 162 if (kit.isAutoFormSubmission()) 163 { 164 try 165 { 166 final URL url; 167 if (method != null && method.equals("post")) 168 { 169 // Perform POST. 170 url = actionURL; 171 conn = url.openConnection(); 172 postData(conn, data); 173 } 174 else 175 { 176 // Default to GET. 177 url = new URL(actionURL + "?" + data); 178 } 179 Runnable loadDoc = new Runnable() 180 { 181 public void run() 182 { 183 if (doc.isFrameDocument()) 184 { 185 editor.fireHyperlinkUpdate(createSubmitEvent(method, 186 actionURL, 187 target)); 188 } 189 else 190 { 191 try 192 { 193 editor.setPage(url); 194 } 195 catch (IOException ex) 196 { 197 // Oh well. 198 ex.printStackTrace(); 199 } 200 } 201 } 202 }; 203 SwingUtilities.invokeLater(loadDoc); 204 } 205 catch (MalformedURLException ex) 206 { 207 ex.printStackTrace(); 208 } 209 catch (IOException ex) 210 { 211 ex.printStackTrace(); 212 } 213 } 214 else 215 { 216 editor.fireHyperlinkUpdate(createSubmitEvent(method,actionURL, 217 target)); 218 } 219 } 220 } 221 222 /** 223 * Determines the submit method. 224 * 225 * @return the submit method 226 */ 227 private String getMethod() 228 { 229 AttributeSet formAtts = getFormAttributes(); 230 String method = null; 231 if (formAtts != null) 232 { 233 method = (String) formAtts.getAttribute(HTML.Attribute.METHOD); 234 } 235 return method; 236 } 237 238 /** 239 * Determines the action URL. 240 * 241 * @return the action URL 242 */ 243 private URL getActionURL() 244 { 245 AttributeSet formAtts = getFormAttributes(); 246 HTMLDocument doc = (HTMLDocument) getElement().getDocument(); 247 URL url = doc.getBase(); 248 if (formAtts != null) 249 { 250 String action = 251 (String) formAtts.getAttribute(HTML.Attribute.ACTION); 252 if (action != null) 253 { 254 try 255 { 256 url = new URL(url, action); 257 } 258 catch (MalformedURLException ex) 259 { 260 url = null; 261 } 262 } 263 } 264 return url; 265 } 266 267 /** 268 * Fetches the target attribute. 269 * 270 * @return the target attribute or _self if none is present 271 */ 272 private String getTarget() 273 { 274 AttributeSet formAtts = getFormAttributes(); 275 String target = null; 276 if (formAtts != null) 277 { 278 target = (String) formAtts.getAttribute(HTML.Attribute.TARGET); 279 if (target != null) 280 target = target.toLowerCase(); 281 } 282 if (target == null) 283 target = "_self"; 284 return target; 285 } 286 287 /** 288 * Posts the form data over the specified connection. 289 * 290 * @param conn the connection 291 */ 292 private void postData(URLConnection conn, String data) 293 { 294 conn.setDoOutput(true); 295 PrintWriter out = null; 296 try 297 { 298 out = new PrintWriter(new OutputStreamWriter(conn.getOutputStream())); 299 out.print(data); 300 out.flush(); 301 } 302 catch (IOException ex) 303 { 304 // Deal with this! 305 ex.printStackTrace(); 306 } 307 finally 308 { 309 if (out != null) 310 out.close(); 311 } 312 } 313 314 /** 315 * Determines the attributes from the relevant form tag. 316 * 317 * @return the attributes from the relevant form tag, <code>null</code> 318 * when there is no form tag 319 */ 320 private AttributeSet getFormAttributes() 321 { 322 AttributeSet atts = null; 323 Element form = getFormElement(); 324 if (form != null) 325 atts = form.getAttributes(); 326 return atts; 327 } 328 329 /** 330 * Creates the submit event that should be fired. 331 * 332 * This is package private to avoid accessor methods. 333 * 334 * @param method the submit method 335 * @param actionURL the action URL 336 * @param target the target 337 * 338 * @return the submit event 339 */ 340 FormSubmitEvent createSubmitEvent(String method, URL actionURL, 341 String target) 342 { 343 FormSubmitEvent.MethodType m = "post".equals(method) 344 ? FormSubmitEvent.MethodType.POST 345 : FormSubmitEvent.MethodType.GET; 346 return new FormSubmitEvent(FormView.this, 347 HyperlinkEvent.EventType.ACTIVATED, 348 actionURL, getElement(), target, m, data); 349 } 350 } 351 352 /** 353 * If the value attribute of an <code><input type="submit">> 354 * tag is not specified, then this string is used. 355 * 356 * @deprecated As of JDK1.3 the value is fetched from the UIManager property 357 * <code>FormView.submitButtonText</code>. 358 */ 359 public static final String SUBMIT = 360 UIManager.getString("FormView.submitButtonText"); 361 362 /** 363 * If the value attribute of an <code><input type="reset">> 364 * tag is not specified, then this string is used. 365 * 366 * @deprecated As of JDK1.3 the value is fetched from the UIManager property 367 * <code>FormView.resetButtonText</code>. 368 */ 369 public static final String RESET = 370 UIManager.getString("FormView.resetButtonText"); 371 372 /** 373 * If this is true, the maximum size is set to the preferred size. 374 */ 375 private boolean maxIsPreferred; 376 377 /** 378 * Creates a new <code>FormView</code>. 379 * 380 * @param el the element that is displayed by this view. 381 */ 382 public FormView(Element el) 383 { 384 super(el); 385 } 386 387 /** 388 * Creates the correct AWT component for rendering the form element. 389 */ 390 protected Component createComponent() 391 { 392 Component comp = null; 393 Element el = getElement(); 394 AttributeSet atts = el.getAttributes(); 395 Object tag = atts.getAttribute(StyleConstants.NameAttribute); 396 Object model = atts.getAttribute(StyleConstants.ModelAttribute); 397 if (tag.equals(HTML.Tag.INPUT)) 398 { 399 String type = (String) atts.getAttribute(HTML.Attribute.TYPE); 400 if (type.equals("button")) 401 { 402 String value = (String) atts.getAttribute(HTML.Attribute.VALUE); 403 JButton b = new JButton(value); 404 if (model != null) 405 { 406 b.setModel((ButtonModel) model); 407 b.addActionListener(this); 408 } 409 comp = b; 410 maxIsPreferred = true; 411 } 412 else if (type.equals("checkbox")) 413 { 414 if (model instanceof ResetableToggleButtonModel) 415 { 416 ResetableToggleButtonModel m = 417 (ResetableToggleButtonModel) model; 418 JCheckBox c = new JCheckBox(); 419 c.setModel(m); 420 comp = c; 421 maxIsPreferred = true; 422 } 423 } 424 else if (type.equals("image")) 425 { 426 String src = (String) atts.getAttribute(HTML.Attribute.SRC); 427 JButton b; 428 try 429 { 430 URL base = ((HTMLDocument) el.getDocument()).getBase(); 431 URL srcURL = new URL(base, src); 432 ImageIcon icon = new ImageIcon(srcURL); 433 b = new JButton(icon); 434 } 435 catch (MalformedURLException ex) 436 { 437 b = new JButton(src); 438 } 439 if (model != null) 440 { 441 b.setModel((ButtonModel) model); 442 b.addActionListener(this); 443 } 444 comp = b; 445 maxIsPreferred = true; 446 } 447 else if (type.equals("password")) 448 { 449 int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE, 450 -1); 451 JTextField tf = new JPasswordField(); 452 if (size > 0) 453 tf.setColumns(size); 454 else 455 tf.setColumns(20); 456 if (model != null) 457 tf.setDocument((Document) model); 458 tf.addActionListener(this); 459 comp = tf; 460 maxIsPreferred = true; 461 } 462 else if (type.equals("radio")) 463 { 464 if (model instanceof ResetableToggleButtonModel) 465 { 466 ResetableToggleButtonModel m = 467 (ResetableToggleButtonModel) model; 468 JRadioButton c = new JRadioButton(); 469 c.setModel(m); 470 comp = c; 471 maxIsPreferred = true; 472 } 473 } 474 else if (type.equals("reset")) 475 { 476 String value = (String) atts.getAttribute(HTML.Attribute.VALUE); 477 if (value == null) 478 value = UIManager.getString("FormView.resetButtonText"); 479 JButton b = new JButton(value); 480 if (model != null) 481 { 482 b.setModel((ButtonModel) model); 483 b.addActionListener(this); 484 } 485 comp = b; 486 maxIsPreferred = true; 487 } 488 else if (type.equals("submit")) 489 { 490 String value = (String) atts.getAttribute(HTML.Attribute.VALUE); 491 if (value == null) 492 value = UIManager.getString("FormView.submitButtonText"); 493 JButton b = new JButton(value); 494 if (model != null) 495 { 496 b.setModel((ButtonModel) model); 497 b.addActionListener(this); 498 } 499 comp = b; 500 maxIsPreferred = true; 501 } 502 else if (type.equals("text")) 503 { 504 int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE, 505 -1); 506 JTextField tf = new JTextField(); 507 if (size > 0) 508 tf.setColumns(size); 509 else 510 tf.setColumns(20); 511 if (model != null) 512 tf.setDocument((Document) model); 513 tf.addActionListener(this); 514 comp = tf; 515 maxIsPreferred = true; 516 } 517 } 518 else if (tag == HTML.Tag.TEXTAREA) 519 { 520 JTextArea textArea = new JTextArea((Document) model); 521 int rows = HTML.getIntegerAttributeValue(atts, HTML.Attribute.ROWS, 1); 522 textArea.setRows(rows); 523 int cols = HTML.getIntegerAttributeValue(atts, HTML.Attribute.COLS, 20); 524 textArea.setColumns(cols); 525 maxIsPreferred = true; 526 comp = new JScrollPane(textArea, 527 JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 528 JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 529 } 530 else if (tag == HTML.Tag.SELECT) 531 { 532 if (model instanceof SelectListModel) 533 { 534 SelectListModel slModel = (SelectListModel) model; 535 JList list = new JList(slModel); 536 int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE, 537 1); 538 list.setVisibleRowCount(size); 539 list.setSelectionModel(slModel.getSelectionModel()); 540 comp = new JScrollPane(list); 541 } 542 else if (model instanceof SelectComboBoxModel) 543 { 544 SelectComboBoxModel scbModel = (SelectComboBoxModel) model; 545 comp = new JComboBox(scbModel); 546 } 547 maxIsPreferred = true; 548 } 549 return comp; 550 } 551 552 /** 553 * Determines the maximum span for this view on the specified axis. 554 * 555 * @param axis the axis along which to determine the span 556 * 557 * @return the maximum span for this view on the specified axis 558 * 559 * @throws IllegalArgumentException if the axis is invalid 560 */ 561 public float getMaximumSpan(int axis) 562 { 563 float span; 564 if (maxIsPreferred) 565 span = getPreferredSpan(axis); 566 else 567 span = super.getMaximumSpan(axis); 568 return span; 569 } 570 571 /** 572 * Processes an action from the Swing component. 573 * 574 * If the action comes from a submit button, the form is submitted by calling 575 * {@link #submitData}. In the case of a reset button, the form is reset to 576 * the original state. If the action comes from a password or text field, 577 * then the input focus is transferred to the next input element in the form, 578 * unless this text/password field is the last one, in which case the form 579 * is submitted. 580 * 581 * @param ev the action event 582 */ 583 public void actionPerformed(ActionEvent ev) 584 { 585 Element el = getElement(); 586 Object tag = el.getAttributes().getAttribute(StyleConstants.NameAttribute); 587 if (tag.equals(HTML.Tag.INPUT)) 588 { 589 AttributeSet atts = el.getAttributes(); 590 String type = (String) atts.getAttribute(HTML.Attribute.TYPE); 591 if (type.equals("submit")) 592 submitData(getFormData()); 593 else if (type.equals("reset")) 594 resetForm(); 595 } 596 // FIXME: Implement the remaining actions. 597 } 598 599 /** 600 * Submits the form data. A separate thread is created to do the 601 * transmission. 602 * 603 * @param data the form data 604 */ 605 protected void submitData(String data) 606 { 607 SubmitThread submitThread = new SubmitThread(data); 608 submitThread.start(); 609 } 610 611 /** 612 * Submits the form data in response to a click on a 613 * <code><input type="image"></code> element. 614 * 615 * @param imageData the mouse click coordinates 616 */ 617 protected void imageSubmit(String imageData) 618 { 619 // FIXME: Implement this. 620 } 621 622 /** 623 * Determines the image data that should be submitted in response to a 624 * mouse click on a image. This is either 'x=<p.x>&y=<p.y>' if the name 625 * attribute of the element is null or '' or 626 * <name>.x=<p.x>&<name>.y=<p.y>' when the name attribute is not empty. 627 * 628 * @param p the coordinates of the mouseclick 629 */ 630 String getImageData(Point p) 631 { 632 String name = (String) getElement().getAttributes() 633 .getAttribute(HTML.Attribute.NAME); 634 String data; 635 if (name == null || name.equals("")) 636 { 637 data = "x=" + p.x + "&y=" + p.y; 638 } 639 else 640 { 641 data = name + ".x=" + p.x + "&" + name + ".y=" + p.y; 642 } 643 return data; 644 } 645 646 /** 647 * Determines and returns the enclosing form element if there is any. 648 * 649 * This is package private to avoid accessor methods. 650 * 651 * @return the enclosing form element, or <code>null</code> if there is no 652 * enclosing form element 653 */ 654 Element getFormElement() 655 { 656 Element form = null; 657 Element el = getElement(); 658 while (el != null && form == null) 659 { 660 AttributeSet atts = el.getAttributes(); 661 if (atts.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.FORM) 662 form = el; 663 else 664 el = el.getParentElement(); 665 } 666 return form; 667 } 668 669 /** 670 * Determines the form data that is about to be submitted. 671 * 672 * @return the form data 673 */ 674 private String getFormData() 675 { 676 Element form = getFormElement(); 677 StringBuilder b = new StringBuilder(); 678 if (form != null) 679 { 680 ElementIterator i = new ElementIterator(form); 681 Element next; 682 while ((next = i.next()) != null) 683 { 684 if (next.isLeaf()) 685 { 686 AttributeSet atts = next.getAttributes(); 687 String type = (String) atts.getAttribute(HTML.Attribute.TYPE); 688 if (type != null && type.equals("submit") 689 && next != getElement()) 690 { 691 // Skip this. This is not the actual submit trigger. 692 } 693 else if (type == null || ! type.equals("image")) 694 { 695 getElementFormData(next, b); 696 } 697 } 698 } 699 } 700 return b.toString(); 701 } 702 703 /** 704 * Fetches the form data from the specified element and appends it to 705 * the data string. 706 * 707 * @param el the element from which to fetch form data 708 * @param b the data string 709 */ 710 private void getElementFormData(Element el, StringBuilder b) 711 { 712 AttributeSet atts = el.getAttributes(); 713 String name = (String) atts.getAttribute(HTML.Attribute.NAME); 714 if (name != null) 715 { 716 String value = null; 717 HTML.Tag tag = (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute); 718 if (tag == HTML.Tag.SELECT) 719 { 720 getSelectData(atts, b); 721 } 722 else 723 { 724 if (tag == HTML.Tag.INPUT) 725 value = getInputFormData(atts); 726 else if (tag == HTML.Tag.TEXTAREA) 727 value = getTextAreaData(atts); 728 if (name != null && value != null) 729 { 730 addData(b, name, value); 731 } 732 } 733 } 734 } 735 736 /** 737 * Fetches form data from select boxes. 738 * 739 * @param atts the attributes of the element 740 * 741 * @param b the form data string to append to 742 */ 743 private void getSelectData(AttributeSet atts, StringBuilder b) 744 { 745 String name = (String) atts.getAttribute(HTML.Attribute.NAME); 746 if (name != null) 747 { 748 Object m = atts.getAttribute(StyleConstants.ModelAttribute); 749 if (m instanceof SelectListModel) 750 { 751 SelectListModel sl = (SelectListModel) m; 752 ListSelectionModel lsm = sl.getSelectionModel(); 753 for (int i = 0; i < sl.getSize(); i++) 754 { 755 if (lsm.isSelectedIndex(i)) 756 { 757 Option o = (Option) sl.getElementAt(i); 758 addData(b, name, o.getValue()); 759 } 760 } 761 } 762 else if (m instanceof SelectComboBoxModel) 763 { 764 SelectComboBoxModel scb = (SelectComboBoxModel) m; 765 Option o = (Option) scb.getSelectedItem(); 766 if (o != null) 767 addData(b, name, o.getValue()); 768 } 769 } 770 } 771 772 /** 773 * Fetches form data from a textarea. 774 * 775 * @param atts the attributes 776 * 777 * @return the form data 778 */ 779 private String getTextAreaData(AttributeSet atts) 780 { 781 Document doc = (Document) atts.getAttribute(StyleConstants.ModelAttribute); 782 String data; 783 try 784 { 785 data = doc.getText(0, doc.getLength()); 786 } 787 catch (BadLocationException ex) 788 { 789 data = null; 790 } 791 return data; 792 } 793 794 /** 795 * Fetches form data from an input tag. 796 * 797 * @param atts the attributes from which to fetch the data 798 * 799 * @return the field value 800 */ 801 private String getInputFormData(AttributeSet atts) 802 { 803 String type = (String) atts.getAttribute(HTML.Attribute.TYPE); 804 Object model = atts.getAttribute(StyleConstants.ModelAttribute); 805 String value = null; 806 if (type.equals("text") || type.equals("password")) 807 { 808 Document doc = (Document) model; 809 try 810 { 811 value = doc.getText(0, doc.getLength()); 812 } 813 catch (BadLocationException ex) 814 { 815 // Sigh. 816 assert false; 817 } 818 } 819 else if (type.equals("hidden") || type.equals("submit")) 820 { 821 value = (String) atts.getAttribute(HTML.Attribute.VALUE); 822 if (value == null) 823 value = ""; 824 } 825 // TODO: Implement the others. radio, checkbox and file. 826 return value; 827 } 828 829 /** 830 * Actually adds the specified data to the string. It URL encodes 831 * the name and value and handles separation of the fields. 832 * 833 * @param b the string at which the form data to be added 834 * @param name the name of the field 835 * @param value the value 836 */ 837 private void addData(StringBuilder b, String name, String value) 838 { 839 if (b.length() > 0) 840 b.append('&'); 841 String encName = URLEncoder.encode(name); 842 b.append(encName); 843 b.append('='); 844 String encValue = URLEncoder.encode(value); 845 b.append(encValue); 846 } 847 848 /** 849 * Resets the form data to their initial state. 850 */ 851 private void resetForm() 852 { 853 Element form = getFormElement(); 854 if (form != null) 855 { 856 ElementIterator iter = new ElementIterator(form); 857 Element next; 858 while ((next = iter.next()) != null) 859 { 860 if (next.isLeaf()) 861 { 862 AttributeSet atts = next.getAttributes(); 863 Object m = atts.getAttribute(StyleConstants.ModelAttribute); 864 if (m instanceof ResetableModel) 865 ((ResetableModel) m).reset(); 866 } 867 } 868 } 869 } 870}