001/*
002 *  jDTAUS Banking Utilities
003 *  Copyright (C) 2005 Christian Schulte
004 *  <cs@schulte.it>
005 *
006 *  This library is free software; you can redistribute it and/or
007 *  modify it under the terms of the GNU Lesser General Public
008 *  License as published by the Free Software Foundation; either
009 *  version 2.1 of the License, or any later version.
010 *
011 *  This library is distributed in the hope that it will be useful,
012 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 *  Lesser General Public License for more details.
015 *
016 *  You should have received a copy of the GNU Lesser General Public
017 *  License along with this library; if not, write to the Free Software
018 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
019 *
020 */
021package org.jdtaus.banking.util.swing;
022
023import java.beans.PropertyChangeEvent;
024import java.beans.PropertyChangeListener;
025import java.text.DecimalFormat;
026import java.text.NumberFormat;
027import java.text.ParseException;
028import java.util.Locale;
029import javax.swing.JFormattedTextField;
030import javax.swing.JFormattedTextField.AbstractFormatter;
031import javax.swing.SwingUtilities;
032import javax.swing.text.AttributeSet;
033import javax.swing.text.BadLocationException;
034import javax.swing.text.DocumentFilter;
035import javax.swing.text.DocumentFilter.FilterBypass;
036import org.jdtaus.banking.Bankleitzahl;
037import org.jdtaus.banking.BankleitzahlExpirationException;
038import org.jdtaus.banking.BankleitzahlInfo;
039import org.jdtaus.banking.BankleitzahlenVerzeichnis;
040import org.jdtaus.banking.messages.BankleitzahlExpirationMessage;
041import org.jdtaus.banking.messages.BankleitzahlReplacementMessage;
042import org.jdtaus.banking.messages.UnknownBankleitzahlMessage;
043import org.jdtaus.core.container.ContainerFactory;
044import org.jdtaus.core.container.PropertyException;
045
046/**
047 * {@code JFormattedTextField} supporting the {@code Bankleitzahl} type.
048 * <p>This textfield uses the {@link Bankleitzahl} type for parsing and formatting. An empty string value is treated as
049 * {@code null}. Property {@code format} controls formatting and takes one of the format constants defined in class
050 * {@code Bankleitzahl}. By default the {@code ELECTRONIC_FORMAT} is used. The {@code validating} flag controls
051 * validation of values entered into the textfield. If {@code true} (default), a {@code DocumentFilter} is registered
052 * with the textfield disallowing invalid values, that is, values which are not {@code null} and not empty strings and
053 * for which the {@link Bankleitzahl#parse(String)} method throws a {@code ParseException}. The field's tooltip
054 * text is updated with information about the field's value.</p>
055 *
056 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
057 * @version $JDTAUS: BankleitzahlTextField.java 8661 2012-09-27 11:29:58Z schulte $
058 */
059public final class BankleitzahlTextField extends JFormattedTextField
060{
061
062    /** Serial version UID for backwards compatibility with 1.1.x classes. */
063    private static final long serialVersionUID = -5461742987164339047L;
064
065    /**
066     * The constant of the format to use when formatting Bankleitzahl instances.
067     * @serial
068     */
069    private Integer format;
070
071    /**
072     * Flag indicating if validation is performed.
073     * @serial
074     */
075    private Boolean validating;
076
077    /** Creates a new default {@code BankleitzahlTextField} instance. */
078    public BankleitzahlTextField()
079    {
080        super();
081        this.assertValidProperties();
082        this.setColumns( Bankleitzahl.MAX_CHARACTERS );
083        this.setFormatterFactory( new AbstractFormatterFactory()
084        {
085
086            public AbstractFormatter getFormatter( final JFormattedTextField ftf )
087            {
088                return new AbstractFormatter()
089                {
090
091                    public Object stringToValue( final String text ) throws ParseException
092                    {
093                        Object value = null;
094
095                        if ( text != null && text.trim().length() > 0 )
096                        {
097                            value = Bankleitzahl.parse( text );
098                        }
099
100                        return value;
101                    }
102
103                    public String valueToString( final Object value ) throws ParseException
104                    {
105                        String ret = null;
106
107                        if ( value instanceof Bankleitzahl )
108                        {
109                            final Bankleitzahl blz = (Bankleitzahl) value;
110                            ret = blz.format( getFormat() );
111                        }
112
113                        return ret;
114                    }
115
116                    protected DocumentFilter getDocumentFilter()
117                    {
118                        return new DocumentFilter()
119                        {
120
121                            public void insertString( final FilterBypass fb, final int o, String s,
122                                                      final AttributeSet a ) throws BadLocationException
123                            {
124                                if ( isValidating() )
125                                {
126                                    final StringBuffer b = new StringBuffer( fb.getDocument().getLength() + s.length() );
127                                    b.append( fb.getDocument().getText( 0, fb.getDocument().getLength() ) );
128                                    b.insert( o, s );
129
130                                    try
131                                    {
132                                        Bankleitzahl.parse( b.toString() );
133                                    }
134                                    catch ( ParseException e )
135                                    {
136                                        invalidEdit();
137                                        return;
138                                    }
139                                }
140
141                                super.insertString( fb, o, s, a );
142                            }
143
144                            public void replace( final FilterBypass fb, final int o, final int l, String s,
145                                                 final AttributeSet a ) throws BadLocationException
146                            {
147                                if ( isValidating() )
148                                {
149                                    final StringBuffer b = new StringBuffer( fb.getDocument().getLength() + s.length() );
150                                    b.append( fb.getDocument().getText( 0, fb.getDocument().getLength() ) );
151                                    b.replace( o, o + s.length(), s );
152
153                                    try
154                                    {
155                                        Bankleitzahl.parse( b.toString() );
156                                    }
157                                    catch ( ParseException e )
158                                    {
159                                        invalidEdit();
160                                        return;
161                                    }
162                                }
163
164                                super.replace( fb, o, l, s, a );
165                            }
166
167                        };
168                    }
169
170                };
171            }
172
173        } );
174
175        this.addPropertyChangeListener( "value", new PropertyChangeListener()
176        {
177
178            public void propertyChange( final PropertyChangeEvent evt )
179            {
180                new Thread()
181                {
182
183                    public void run()
184                    {
185                        updateTooltip();
186                    }
187
188                }.start();
189            }
190
191        } );
192    }
193
194    /**
195     * Gets the last valid {@code Bankleitzahl}.
196     *
197     * @return the last valid {@code Bankleitzahl} or {@code null}.
198     */
199    public Bankleitzahl getBankleitzahl()
200    {
201        return (Bankleitzahl) this.getValue();
202    }
203
204    /**
205     * Gets the constant of the format used when formatting Bankleitzahl instances.
206     *
207     * @return the constant of the format used when formatting Bankleitzahl instances.
208     *
209     * @see Bankleitzahl#ELECTRONIC_FORMAT
210     * @see Bankleitzahl#LETTER_FORMAT
211     */
212    public int getFormat()
213    {
214        if ( this.format == null )
215        {
216            this.format = this.getDefaultFormat();
217        }
218
219        return this.format.intValue();
220    }
221
222    /**
223     * Sets the constant of the format to use when formatting Bankleitzahl instances.
224     *
225     * @param value the constant of the format to use when formatting Bankleitzahl instances.
226     *
227     * @throws IllegalArgumentException if {@code format} is neither {@code ELECTRONIC_FORMAT} nor
228     * {@code LETTER_FORMAT}.
229     *
230     * @see Bankleitzahl#ELECTRONIC_FORMAT
231     * @see Bankleitzahl#LETTER_FORMAT
232     */
233    public void setFormat( final int value )
234    {
235        if ( value != Bankleitzahl.ELECTRONIC_FORMAT && value != Bankleitzahl.LETTER_FORMAT )
236        {
237            throw new IllegalArgumentException( Integer.toString( value ) );
238        }
239
240        this.format = new Integer( value );
241    }
242
243    /**
244     * Gets the flag indicating if validation is performed.
245     *
246     * @return {@code true} if the fields' value is validated; {@code false} if no validation of the fields' value is
247     * performed.
248     */
249    public boolean isValidating()
250    {
251        if ( this.validating == null )
252        {
253            this.validating = this.isDefaultValidating();
254        }
255
256        return this.validating.booleanValue();
257    }
258
259    /**
260     * Sets the flag indicating if validation should be performed.
261     *
262     * @param value {@code true} to validate the fields' values; {@code false} to not validate the fields' values.
263     */
264    public void setValidating( boolean value )
265    {
266        this.validating = Boolean.valueOf( value );
267    }
268
269    /**
270     * Updates the component's tooltip to show information available for the value returned by
271     * {@link #getBankleitzahl()}. This method is called whenever a {@code PropertyChangeEvent} for the property with
272     * name {@code value} occurs.
273     */
274    private void updateTooltip()
275    {
276        final Bankleitzahl blz = this.getBankleitzahl();
277        final StringBuffer tooltip = new StringBuffer( 200 );
278
279        if ( blz != null )
280        {
281            tooltip.append( "<html>" );
282
283            try
284            {
285                final BankleitzahlInfo headOffice = this.getBankleitzahlenVerzeichnis().getHeadOffice( blz );
286                if ( headOffice != null )
287                {
288                    tooltip.append( "<b>" ).append( this.getHeadOfficeInfoMessage( this.getLocale() ) ).
289                        append( "</b><br>" );
290
291                    this.appendBankleitzahlInfo( headOffice, tooltip );
292                }
293                else
294                {
295                    tooltip.append( new UnknownBankleitzahlMessage( blz ).getText( this.getLocale() ) );
296                }
297            }
298            catch ( BankleitzahlExpirationException e )
299            {
300                tooltip.append( new BankleitzahlExpirationMessage(
301                    e.getExpiredBankleitzahlInfo() ).getText( this.getLocale() ) );
302
303                tooltip.append( "<br>" ).append( new BankleitzahlReplacementMessage(
304                    e.getReplacingBankleitzahlInfo() ).getText( this.getLocale() ) );
305
306                tooltip.append( "<br>" );
307                this.appendBankleitzahlInfo( e.getReplacingBankleitzahlInfo(), tooltip );
308            }
309
310            tooltip.append( "</html>" );
311        }
312
313        SwingUtilities.invokeLater( new Runnable()
314        {
315
316            public void run()
317            {
318                setToolTipText( tooltip.length() > 0 ? tooltip.toString() : null );
319            }
320
321        } );
322
323    }
324
325    /**
326     * Checks configured properties.
327     *
328     * @throws PropertyException for invalid property values.
329     */
330    private void assertValidProperties()
331    {
332        if ( this.getFormat() != Bankleitzahl.ELECTRONIC_FORMAT && this.getFormat() != Bankleitzahl.LETTER_FORMAT )
333        {
334            throw new PropertyException( "format", Integer.toString( this.getFormat() ) );
335        }
336    }
337
338    /**
339     * Appends the tooltip text for a given {@code BankleitzahlInfo} to a given {@code StringBuffer}.
340     *
341     * @param bankleitzahlInfo The {@code BankleitzahlInfo} instance to append to {@code buf}.
342     * @param buf The {@code StringBuffer} instance to append {@code bankleitzahlInfo} to.
343     *
344     * @return {@code buf} with information about {@code bankleitzahlInfo} appended.
345     *
346     * @throws NullPointerException if {@code bankleitzahlInfo} is {@code null}.
347     */
348    private StringBuffer appendBankleitzahlInfo( final BankleitzahlInfo bankleitzahlInfo, StringBuffer buf )
349    {
350        if ( bankleitzahlInfo == null )
351        {
352            throw new NullPointerException( "bankleitzahlInfo" );
353        }
354        if ( buf == null )
355        {
356            buf = new StringBuffer();
357        }
358
359        final NumberFormat zipFormat = new DecimalFormat( "#####" );
360        buf.append( "<br>" ).append( bankleitzahlInfo.getName() );
361
362        if ( bankleitzahlInfo.getDescription() != null && bankleitzahlInfo.getDescription().trim().length() > 0 &&
363             !bankleitzahlInfo.getName().equals( bankleitzahlInfo.getDescription() ) )
364        {
365            buf.append( " (" ).append( bankleitzahlInfo.getDescription() ).append( ")" );
366        }
367
368        buf.append( "<br>" ).append( zipFormat.format( bankleitzahlInfo.getPostalCode() ) ).append( " " ).
369            append( bankleitzahlInfo.getCity() );
370
371        buf.append( "<br>" ).append( this.getBlzInfoMessage(
372            this.getLocale(), bankleitzahlInfo.getBankCode().format( this.getFormat() ) ) );
373
374        if ( bankleitzahlInfo.getBic() != null && bankleitzahlInfo.getBic().trim().length() > 0 )
375        {
376            buf.append( "<br>" ).append( this.getBicInfoMessage( this.getLocale(), bankleitzahlInfo.getBic() ) );
377        }
378
379        return buf;
380    }
381
382    //--Dependencies------------------------------------------------------------
383
384// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies
385    // This section is managed by jdtaus-container-mojo.
386
387    /**
388     * Gets the configured <code>BankleitzahlenVerzeichnis</code> implementation.
389     *
390     * @return The configured <code>BankleitzahlenVerzeichnis</code> implementation.
391     */
392    private BankleitzahlenVerzeichnis getBankleitzahlenVerzeichnis()
393    {
394        return (BankleitzahlenVerzeichnis) ContainerFactory.getContainer().
395            getDependency( this, "BankleitzahlenVerzeichnis" );
396
397    }
398
399// </editor-fold>//GEN-END:jdtausDependencies
400
401    //------------------------------------------------------------Dependencies--
402    //--Properties--------------------------------------------------------------
403
404// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties
405    // This section is managed by jdtaus-container-mojo.
406
407    /**
408     * Gets the value of property <code>defaultValidating</code>.
409     *
410     * @return Default value of the flag indicating if validation should be performed.
411     */
412    private java.lang.Boolean isDefaultValidating()
413    {
414        return (java.lang.Boolean) ContainerFactory.getContainer().
415            getProperty( this, "defaultValidating" );
416
417    }
418
419    /**
420     * Gets the value of property <code>defaultFormat</code>.
421     *
422     * @return Default value of the format to use when formatting Bankleitzahl instances (3001 = electronic format, 3002 letter format).
423     */
424    private java.lang.Integer getDefaultFormat()
425    {
426        return (java.lang.Integer) ContainerFactory.getContainer().
427            getProperty( this, "defaultFormat" );
428
429    }
430
431// </editor-fold>//GEN-END:jdtausProperties
432
433    //--------------------------------------------------------------Properties--
434    //--Messages----------------------------------------------------------------
435
436// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages
437    // This section is managed by jdtaus-container-mojo.
438
439    /**
440     * Gets the text of message <code>blzInfo</code>.
441     * <blockquote><pre>BLZ {0}</pre></blockquote>
442     * <blockquote><pre>BLZ {0}</pre></blockquote>
443     *
444     * @param locale The locale of the message instance to return.
445     * @param bankCode format argument.
446     *
447     * @return the text of message <code>blzInfo</code>.
448     */
449    private String getBlzInfoMessage( final Locale locale,
450            final java.lang.String bankCode )
451    {
452        return ContainerFactory.getContainer().
453            getMessage( this, "blzInfo", locale,
454                new Object[]
455                {
456                    bankCode
457                });
458
459    }
460
461    /**
462     * Gets the text of message <code>bicInfo</code>.
463     * <blockquote><pre>BIC {0}</pre></blockquote>
464     * <blockquote><pre>BIC {0}</pre></blockquote>
465     *
466     * @param locale The locale of the message instance to return.
467     * @param bic format argument.
468     *
469     * @return the text of message <code>bicInfo</code>.
470     */
471    private String getBicInfoMessage( final Locale locale,
472            final java.lang.String bic )
473    {
474        return ContainerFactory.getContainer().
475            getMessage( this, "bicInfo", locale,
476                new Object[]
477                {
478                    bic
479                });
480
481    }
482
483    /**
484     * Gets the text of message <code>headOfficeInfo</code>.
485     * <blockquote><pre>Hauptstelle</pre></blockquote>
486     * <blockquote><pre>Headoffice</pre></blockquote>
487     *
488     * @param locale The locale of the message instance to return.
489     *
490     * @return the text of message <code>headOfficeInfo</code>.
491     */
492    private String getHeadOfficeInfoMessage( final Locale locale )
493    {
494        return ContainerFactory.getContainer().
495            getMessage( this, "headOfficeInfo", locale, null );
496
497    }
498
499// </editor-fold>//GEN-END:jdtausMessages
500
501    //----------------------------------------------------------------Messages--
502}