001 /* FormView.java -- A view for a variety of HTML form elements
002 Copyright (C) 2006 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.text.html;
040
041 import java.awt.Component;
042 import java.awt.Point;
043 import java.awt.event.ActionEvent;
044 import java.awt.event.ActionListener;
045 import java.awt.event.MouseAdapter;
046 import java.awt.event.MouseEvent;
047 import java.io.IOException;
048 import java.io.OutputStreamWriter;
049 import java.io.PrintWriter;
050 import java.net.MalformedURLException;
051 import java.net.URL;
052 import java.net.URLConnection;
053 import java.net.URLEncoder;
054
055 import javax.swing.ButtonModel;
056 import javax.swing.ImageIcon;
057 import javax.swing.JButton;
058 import javax.swing.JCheckBox;
059 import javax.swing.JComboBox;
060 import javax.swing.JEditorPane;
061 import javax.swing.JList;
062 import javax.swing.JPasswordField;
063 import javax.swing.JRadioButton;
064 import javax.swing.JScrollPane;
065 import javax.swing.JTextArea;
066 import javax.swing.JTextField;
067 import javax.swing.ListSelectionModel;
068 import javax.swing.SwingUtilities;
069 import javax.swing.UIManager;
070 import javax.swing.event.HyperlinkEvent;
071 import javax.swing.text.AttributeSet;
072 import javax.swing.text.BadLocationException;
073 import javax.swing.text.ComponentView;
074 import javax.swing.text.Document;
075 import javax.swing.text.Element;
076 import javax.swing.text.ElementIterator;
077 import 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 */
104 public 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 }