001    /* DefaultFormatter.java --
002    Copyright (C) 2005  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    package javax.swing.text;
039    
040    import java.io.Serializable;
041    import java.lang.reflect.Constructor;
042    import java.text.ParseException;
043    
044    import javax.swing.JFormattedTextField;
045    
046    /**
047     * The <code>DefaultFormatter</code> is a concrete formatter for use in
048     * {@link JFormattedTextField}s.
049     *
050     * It can format arbitrary values by invoking
051     * their {@link Object#toString} method.
052     *
053     * In order to convert a String back to
054     * a value, the value class must provide a single argument constructor that
055     * takes a String object as argument value. If no such constructor is found,
056     * the String itself is passed back by #stringToValue.
057     *
058     * @author Roman Kennke (roman@kennke.org)
059     */
060    public class DefaultFormatter extends JFormattedTextField.AbstractFormatter
061      implements Cloneable, Serializable
062    {
063    
064      /**
065       * A {@link DocumentFilter} that intercepts modification of the
066       * JFormattedTextField's Document and commits the value depending
067       * on the value of the <code>commitsOnValidEdit</code> property.
068       *
069       */
070      // FIXME: Handle allowsInvalid and overwriteMode properties
071      private class FormatterDocumentFilter
072        extends DocumentFilter
073      {
074        /**
075         * Invoked when text is removed from a text component.
076         *
077         * @param bypass the FilterBypass to use to mutate the document
078         * @param offset the start position of the modification
079         * @param length the length of the removed text
080         *
081         * @throws BadLocationException if offset or lenght are invalid in
082         *     the Document
083         */
084        public void remove(DocumentFilter.FilterBypass bypass, int offset,
085                            int length)
086          throws BadLocationException
087        {
088          super.remove(bypass, offset, length);
089          checkValidInput();
090          commitIfAllowed();
091        }
092    
093        /**
094         * Invoked when text is inserted into a text component.
095         *
096         * @param bypass the FilterBypass to use to mutate the document
097         * @param offset the start position of the modification
098         * @param text the inserted text
099         * @param attributes the attributes of the inserted text
100         *
101         * @throws BadLocationException if offset or lenght are invalid in
102         *     the Document
103         */
104        public void insertString(DocumentFilter.FilterBypass bypass, int offset,
105                                  String text, AttributeSet attributes)
106          throws BadLocationException
107        {
108          if (overwriteMode == true)
109            replace(bypass, offset, text.length(), text, attributes);
110          else
111            super.insertString(bypass, offset, text, attributes);
112          checkValidInput();
113          commitIfAllowed();
114        }
115    
116        /**
117         * Invoked when text is replaced in a text component.
118         *
119         * @param bypass the FilterBypass to use to mutate the document
120         * @param offset the start position of the modification
121         * @param length the length of the removed text
122         * @param text the inserted text
123         * @param attributes the attributes of the inserted text
124         *
125         * @throws BadLocationException if offset or lenght are invalid in
126         *     the Document
127         */
128        public void replace(DocumentFilter.FilterBypass bypass, int offset,
129                             int length, String text, AttributeSet attributes)
130          throws BadLocationException
131        {
132          super.replace(bypass, offset, length, text, attributes);
133          checkValidInput();
134          commitIfAllowed();
135        }
136    
137        /**
138         * Commits the value to the JTextTextField if the property
139         * <code>commitsOnValidEdit</code> is set to <code>true</code>.
140         */
141        private void commitIfAllowed()
142        {
143          if (commitsOnValidEdit == true)
144            try
145              {
146                getFormattedTextField().commitEdit();
147              }
148            catch (ParseException ex)
149              {
150                // ignore invalid edits
151              }
152        }
153    
154        /**
155         * Checks if the value in the input field is valid. If the
156         * property allowsInvalid is set to <code>false</code>, then
157         * the string in the input field is not allowed to be entered.
158         */
159        private void checkValidInput()
160        {
161          JFormattedTextField ftf = getFormattedTextField();
162          try
163            {
164              Object newval = stringToValue(ftf.getText());
165            }
166          catch (ParseException ex)
167            {
168              if (!allowsInvalid)
169                {
170                  // roll back the input if invalid edits are not allowed
171                  try
172                    {
173                      ftf.setText(valueToString(ftf.getValue()));
174                    }
175                  catch (ParseException pe)
176                    {
177                      // if that happens, something serious must be wrong
178                      AssertionError ae;
179                      ae = new AssertionError("values must be parseable");
180                      ae.initCause(pe);
181                      throw ae;
182                    }
183                }
184            }
185        }
186      }
187    
188      /** The serialization UID (compatible with JDK1.5). */
189      private static final long serialVersionUID = -355018354457785329L;
190    
191      /**
192       * Indicates if the value should be committed after every
193       * valid modification of the Document.
194       */
195      boolean commitsOnValidEdit;
196    
197      /**
198       * If <code>true</code> newly inserted characters overwrite existing
199       * values, otherwise insertion is done the normal way.
200       */
201      boolean overwriteMode;
202    
203      /**
204       * If <code>true</code> invalid edits are allowed for a limited
205       * time.
206       */
207      boolean allowsInvalid;
208    
209      /**
210       * The class that is used for values.
211       */
212      Class valueClass;
213    
214      /**
215       * Creates a new instance of <code>DefaultFormatter</code>.
216       */
217      public DefaultFormatter()
218      {
219        commitsOnValidEdit = false;
220        overwriteMode = true;
221        allowsInvalid = true;
222      }
223    
224      /**
225       * Installs the formatter on the specified {@link JFormattedTextField}.
226       *
227       * This method does the following things:
228       * <ul>
229       * <li>Display the value of #valueToString in the
230       *  <code>JFormattedTextField</code></li>
231       * <li>Install the Actions from #getActions on the <code>JTextField</code>
232       * </li>
233       * <li>Install the DocumentFilter returned by #getDocumentFilter</li>
234       * <li>Install the NavigationFilter returned by #getNavigationFilter</li>
235       * </ul>
236       *
237       * This method is typically not overridden by subclasses. Instead override
238       * one of the mentioned methods in order to customize behaviour.
239       *
240       * @param ftf the {@link JFormattedTextField} in which this formatter
241       *     is installed
242       */
243      public void install(JFormattedTextField ftf)
244      {
245        super.install(ftf);
246      }
247    
248      /**
249       * Returns <code>true</code> if the value should be committed after
250       * each valid modification of the input field, <code>false</code> if
251       * it should never be committed by this formatter.
252       *
253       * @return the state of the <code>commitsOnValidEdit</code> property
254       *
255       * @see #setCommitsOnValidEdit
256       */
257      public boolean getCommitsOnValidEdit()
258      {
259        return commitsOnValidEdit;
260      }
261    
262      /**
263       * Sets the value of the <code>commitsOnValidEdit</code> property.
264       *
265       * @param commitsOnValidEdit the new state of the
266       *     <code>commitsOnValidEdit</code> property
267       *
268       * @see #getCommitsOnValidEdit
269       */
270      public void setCommitsOnValidEdit(boolean commitsOnValidEdit)
271      {
272        this.commitsOnValidEdit = commitsOnValidEdit;
273      }
274    
275      /**
276       * Returns the value of the <code>overwriteMode</code> property.
277       * If that is set to <code>true</code> then newly inserted characters
278       * overwrite existing values, otherwise the characters are inserted like
279       * normal. The default is <code>true</code>.
280       *
281       * @return the value of the <code>overwriteMode</code> property
282       */
283      public boolean getOverwriteMode()
284      {
285        return overwriteMode;
286      }
287    
288      /**
289       * Sets the value of the <code>overwriteMode</code> property.
290       *
291       * If that is set to <code>true</code> then newly inserted characters
292       * overwrite existing values, otherwise the characters are inserted like
293       * normal. The default is <code>true</code>.
294       *
295       * @param overwriteMode the new value for the <code>overwriteMode</code>
296       *     property
297       */
298      public void setOverwriteMode(boolean overwriteMode)
299      {
300        this.overwriteMode = overwriteMode;
301      }
302    
303      /**
304       * Returns whether or not invalid edits are allowed or not. If invalid
305       * edits are allowed, the JFormattedTextField may temporarily contain invalid
306       * characters.
307       *
308       * @return the value of the allowsInvalid property
309       */
310      public boolean getAllowsInvalid()
311      {
312        return allowsInvalid;
313      }
314    
315      /**
316       * Sets the value of the <code>allowsInvalid</code> property.
317       *
318       * @param allowsInvalid the new value for the property
319       *
320       * @see #getAllowsInvalid()
321       */
322      public void setAllowsInvalid(boolean allowsInvalid)
323      {
324        this.allowsInvalid = allowsInvalid;
325      }
326    
327      /**
328       * Returns the class that is used for values. When Strings are converted
329       * back to values, this class is used to create new value objects.
330       *
331       * @return the class that is used for values
332       */
333      public Class<?> getValueClass()
334      {
335        return valueClass;
336      }
337    
338      /**
339       * Sets the class that is used for values.
340       *
341       * @param valueClass the class that is used for values
342       *
343       * @see #getValueClass()
344       */
345      public void setValueClass(Class<?> valueClass)
346      {
347        this.valueClass = valueClass;
348      }
349    
350      /**
351       * Converts a String (from the JFormattedTextField input) to a value.
352       * In order to achieve this, the formatter tries to instantiate an object
353       * of the class returned by #getValueClass() using a single argument
354       * constructor that takes a String argument. If such a constructor cannot
355       * be found, the String itself is returned.
356       *
357       * @param string the string to convert
358       *
359       * @return the value for the string
360       *
361       * @throws ParseException if the string cannot be converted into
362       *     a value object (e.g. invalid input)
363       */
364      public Object stringToValue(String string)
365        throws ParseException
366      {
367        Object value = string;
368        Class valueClass = getValueClass();
369        if (valueClass == null)
370          {
371            JFormattedTextField jft = getFormattedTextField();
372            if (jft != null)
373              valueClass = jft.getValue().getClass();
374          }
375        if (valueClass != null)
376          try
377            {
378              Constructor constr = valueClass.getConstructor
379                                                 (new Class[]{String.class});
380              value = constr.newInstance(new Object[]{ string });
381            }
382          catch (NoSuchMethodException ex)
383            {
384              // leave value as string
385            }
386          catch (Exception ex)
387            {
388              throw new ParseException(string, 0);
389            }
390        return value;
391      }
392    
393      /**
394       * Converts a value object into a String. This is done by invoking the
395       * {@link Object#toString()} method on the value.
396       *
397       * @param value the value to be converted
398       *
399       * @return the string representation of the value
400       *
401       * @throws ParseException if the value cannot be converted
402       */
403      public String valueToString(Object value)
404        throws ParseException
405      {
406        if (value == null)
407          return "";
408        return value.toString();
409      }
410    
411      /**
412       * Creates and returns a clone of this DefaultFormatter.
413       *
414       * @return a clone of this object
415       *
416       * @throws CloneNotSupportedException not thrown here
417       */
418      public Object clone()
419        throws CloneNotSupportedException
420      {
421        return super.clone();
422      }
423    
424      /**
425       * Returns the DocumentFilter that is used to restrict input.
426       *
427       * @return the DocumentFilter that is used to restrict input
428       */
429      protected DocumentFilter getDocumentFilter()
430      {
431        return new FormatterDocumentFilter();
432      }
433    }