/android/import-contacts

To get this branch, use:
bzr branch http://bzr.ed.am/android/import-contacts

« back to all changes in this revision

Viewing changes to src/org/waxworlds/importcontacts/Importer.java

  • Committer: edam
  • Date: 2009-01-13 06:35:26 UTC
  • Revision ID: edam@waxworlds.org-20090113063526-l9t1s9git4bav60a
- new contact's phone numebrs and email addresses are added to the caches after those contacts are updated to account for the situation where the same contact is imported again from another file (or the contact exists twice in the same file!?)

Show diffs side-by-side

added added

removed removed

21
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
22
 */
23
23
 
24
 
package org.waxworlds.edam.importcontacts;
 
24
package org.waxworlds.importcontacts;
25
25
 
26
26
import java.util.HashMap;
 
27
import java.util.HashSet;
27
28
import java.util.Iterator;
28
29
import java.util.Set;
29
30
import java.util.regex.Matcher;
32
33
import android.content.ContentUris;
33
34
import android.content.ContentValues;
34
35
import android.content.SharedPreferences;
 
36
import android.database.Cursor;
35
37
import android.net.Uri;
36
38
import android.os.Message;
37
39
import android.provider.Contacts;
38
40
 
39
 
 
40
41
public class Importer extends Thread
41
42
{
 
43
        public final static int ACTION_GOBACK = 0;
42
44
        public final static int ACTION_ABORT = 1;
43
45
        public final static int ACTION_ALLDONE = 2;
44
46
 
51
53
        private Doit _doit;
52
54
        private int _response;
53
55
        private int _responseExtra;
 
56
        private HashMap< String, Long > _contacts;
 
57
        private HashMap< Long, HashSet< String > > _contactNumbers;
 
58
        private HashMap< Long, HashSet< String > > _contactEmails;
54
59
        private int _mergeSetting;
55
60
        private int _lastMergeDecision;
56
61
        private boolean _abort = false;
57
62
        private boolean _isFinished = false;
58
 
        private ContactsCache _contactsCache = null;
59
63
 
60
64
        public class ContactData
61
65
        {
109
113
                        }
110
114
                }
111
115
 
112
 
                class AddressData
113
 
                {
114
 
                        private String _address;
115
 
                        public int _type;
116
 
 
117
 
                        public AddressData( String address, int type ) {
118
 
                                _address = address;
119
 
                                _type = type;
120
 
                        }
121
 
 
122
 
                        public String getAddress() {
123
 
                                return _address;
124
 
                        }
125
 
 
126
 
                        public int getType() {
127
 
                                return _type;
128
 
                        }
129
 
                }
130
 
 
131
116
                public String _name = null;
132
117
                public HashMap< String, PhoneData > _phones = null;
133
118
                public HashMap< String, EmailData > _emails = null;
134
 
                public HashMap< String, AddressData > _addresses = null;
135
119
 
136
120
                protected void setName( String name )
137
121
                {
148
132
                        if( _phones == null ) _phones = new HashMap< String, PhoneData >();
149
133
                        if( !_phones.containsKey( number ) )
150
134
                                _phones.put( number,
151
 
                                        new PhoneData( number, type, isPreferred ) );
 
135
                                                new PhoneData( number, type, isPreferred ) );
152
136
                }
153
137
 
154
138
                protected void addEmail( String email, int type, boolean isPreferred )
157
141
                        if( !_emails.containsKey( email ) )
158
142
                                _emails.put( email, new EmailData( email, type, isPreferred ) );
159
143
                }
160
 
 
161
 
                protected void addAddress( String address, int type )
162
 
                {
163
 
                        if( _addresses == null ) _addresses =
164
 
                                new HashMap< String, AddressData >();
165
 
                        if( !_addresses.containsKey( address ) )
166
 
                                _addresses.put( address, new AddressData( address, type ) );
167
 
                }
168
144
        }
169
145
 
170
 
        @SuppressWarnings("serial")
171
146
        protected class AbortImportException extends Exception { };
172
147
 
173
148
        public Importer( Doit doit )
175
150
                _doit = doit;
176
151
 
177
152
                SharedPreferences prefs = getSharedPreferences();
178
 
                _mergeSetting = prefs.getInt( "merge_setting", Doit.ACTION_PROMPT );
 
153
                _mergeSetting = prefs.getInt( "merge_setting", 0 );
179
154
        }
180
155
 
181
156
        @Override
183
158
        {
184
159
                try
185
160
                {
186
 
                        // update UI
187
 
                        setProgressMessage( R.string.doit_caching );
188
 
 
189
 
                        // build a cache of existing contacts
190
 
                        _contactsCache = new ContactsCache();
191
 
                        _contactsCache.buildCache( _doit );
 
161
                        // cache current contact names
 
162
                        buildContactsCache();
192
163
 
193
164
                        // do the import
194
165
                        onImport();
254
225
        {
255
226
                checkAbort();
256
227
                _doit._handler.sendMessage( Message.obtain(
257
 
                        _doit._handler, Doit.MESSAGE_ERROR, message ) );
 
228
                                _doit._handler, Doit.MESSAGE_ERROR, message ) );
258
229
                try {
259
230
                        wait();
260
231
                }
261
232
                catch( InterruptedException e ) { }
262
 
 
263
233
                // no need to check if an abortion happened during the wait, we are
264
234
                // about to finish anyway!
265
235
                finish( ACTION_ABORT );
275
245
        {
276
246
                checkAbort();
277
247
                _doit._handler.sendMessage( Message.obtain(
278
 
                        _doit._handler, Doit.MESSAGE_ERROR, message ) );
 
248
                                _doit._handler, Doit.MESSAGE_ERROR, message ) );
279
249
                try {
280
250
                        wait();
281
251
                }
282
252
                catch( InterruptedException e ) { }
283
 
 
284
253
                // no need to check if an abortion happened during the wait, we are
285
254
                // about to finish anyway!
286
255
                finish( ACTION_ABORT );
296
265
        {
297
266
                checkAbort();
298
267
                _doit._handler.sendMessage( Message.obtain(
299
 
                        _doit._handler, Doit.MESSAGE_CONTINUEORABORT, message ) );
 
268
                                _doit._handler, Doit.MESSAGE_CONTINUEORABORT, message ) );
300
269
                try {
301
270
                        wait();
302
271
                }
312
281
        {
313
282
                checkAbort();
314
283
                _doit._handler.sendMessage( Message.obtain( _doit._handler,
315
 
                        Doit.MESSAGE_SETPROGRESSMESSAGE, getText( res ) ) );
 
284
                                Doit.MESSAGE_SETPROGRESSMESSAGE, getText( res ) ) );
316
285
        }
317
286
 
318
287
        protected void setProgressMax( int maxProgress )
320
289
        {
321
290
                checkAbort();
322
291
                _doit._handler.sendMessage( Message.obtain(
323
 
                        _doit._handler, Doit.MESSAGE_SETMAXPROGRESS,
324
 
                        new Integer( maxProgress ) ) );
 
292
                                _doit._handler, Doit.MESSAGE_SETMAXPROGRESS,
 
293
                                new Integer( maxProgress ) ) );
325
294
        }
326
295
 
327
296
        protected void setTmpProgress( int tmpProgress ) throws AbortImportException
328
297
        {
329
298
                checkAbort();
330
299
                _doit._handler.sendMessage( Message.obtain(
331
 
                        _doit._handler, Doit.MESSAGE_SETTMPPROGRESS,
332
 
                        new Integer( tmpProgress ) ) );
 
300
                                _doit._handler, Doit.MESSAGE_SETTMPPROGRESS,
 
301
                                new Integer( tmpProgress ) ) );
333
302
        }
334
303
 
335
304
        protected void setProgress( int progress ) throws AbortImportException
336
305
        {
337
306
                checkAbort();
338
307
                _doit._handler.sendMessage( Message.obtain(
339
 
                        _doit._handler, Doit.MESSAGE_SETPROGRESS,
340
 
                        new Integer( progress ) ) );
 
308
                                _doit._handler, Doit.MESSAGE_SETPROGRESS,
 
309
                                new Integer( progress ) ) );
341
310
        }
342
311
 
343
312
        protected void finish( int action ) throws AbortImportException
346
315
                int message;
347
316
                switch( action )
348
317
                {
349
 
                case ACTION_ALLDONE:    message = Doit.MESSAGE_ALLDONE; break;
 
318
                case ACTION_GOBACK:             message = Doit.MESSAGE_FINISHED_GOBACK; break;
 
319
                case ACTION_ALLDONE:    message = Doit.MESSAGE_FINISHED_ALLDONE; break;
350
320
                default:        // fall through
351
 
                case ACTION_ABORT:              message = Doit.MESSAGE_ABORT; break;
 
321
                case ACTION_ABORT:              message = Doit.MESSAGE_FINISHED; break;
352
322
                }
353
323
                _doit._handler.sendEmptyMessage( message );
354
324
 
376
346
                // handle special cases
377
347
                switch( mergeSetting )
378
348
                {
379
 
                case Doit.ACTION_KEEP:
 
349
                case R.id.merge_keep:
380
350
                        // if we keep contacts on duplicate, we better check for one
381
 
                        return !_contactsCache.exists( name );
 
351
                        return !_contacts.containsKey( name );
382
352
 
383
 
                case Doit.ACTION_PROMPT:
 
353
                case R.id.merge_prompt:
384
354
                        // if we are prompting on duplicate, we better check for one
385
 
                        if( !_contactsCache.exists( name ) )
 
355
                        if( !_contacts.containsKey( name ) )
386
356
                                return true;
387
357
 
388
358
                        // ok, it exists, so do prompt
389
359
                        _doit._handler.sendMessage( Message.obtain(
390
 
                                _doit._handler, Doit.MESSAGE_MERGEPROMPT, name ) );
 
360
                                        _doit._handler, Doit.MESSAGE_MERGEPROMPT, name ) );
391
361
                        try {
392
362
                                wait();
393
363
                        }
400
370
                        if( _responseExtra == RESPONSEEXTRA_ALWAYS )
401
371
                                _mergeSetting = _response;
402
372
 
403
 
                        // recurse, with our new merge setting
 
373
                        // recurse, with out new merge setting
404
374
                        return isImportRequired( name, _response );
405
375
                }
406
376
 
429
399
                // does contact exist already?
430
400
                Uri contactUri = null;
431
401
                Long id;
432
 
                if( ( id = (Long)_contactsCache.getId( contact._name ) ) != null )
 
402
                if( ( id = (Long)_contacts.get( contact._name ) ) != null )
433
403
                {
434
404
                        // should we skip this import altogether?
435
 
                        if( _lastMergeDecision == Doit.ACTION_KEEP ) return;
 
405
                        if( _lastMergeDecision == R.id.merge_keep ) return;
436
406
 
437
407
                        // get contact's URI
438
408
                        contactUri = ContentUris.withAppendedId(
439
 
                                Contacts.People.CONTENT_URI, id );
 
409
                                        Contacts.People.CONTENT_URI, id );
440
410
 
441
411
                        // should we destroy the existing contact before importing?
442
 
                        if( _lastMergeDecision == Doit.ACTION_OVERWRITE ) {
 
412
                        if( _lastMergeDecision == R.id.merge_overwrite ) {
443
413
                                _doit.getContentResolver().delete( contactUri, null, null );
444
414
                                contactUri = null;
445
415
 
446
 
                                // update the UI
447
 
                                _doit._handler.sendEmptyMessage(
448
 
                                                Doit.MESSAGE_CONTACTOVERWRITTEN );
 
416
                                // upate the UI
 
417
                                _doit._handler.sendEmptyMessage( Doit.MESSAGE_CONTACTOVERWRITTEN );
449
418
                                uiInformed = true;
450
419
 
451
420
                                // update cache
452
 
                                _contactsCache.remove( contact._name );
 
421
                                _contacts.remove( contact._name );
453
422
                        }
454
423
                }
455
424
 
460
429
                        // create a new contact
461
430
                        values.put( Contacts.People.NAME, contact._name );
462
431
                        contactUri = _doit.getContentResolver().insert(
463
 
                                Contacts.People.CONTENT_URI, values );
 
432
                                        Contacts.People.CONTENT_URI, values );
464
433
                        id = ContentUris.parseId( contactUri );
465
434
                        if( id <= 0 ) return;   // shouldn't happen!
466
435
 
467
 
                        // try to add them to the "My Contacts" group
468
 
                        try {
469
 
                                Contacts.People.addToMyContactsGroup(
470
 
                                        _doit.getContentResolver(), id );
471
 
                        }
472
 
                        catch( IllegalStateException e ) {
473
 
                                // ignore any failure
474
 
                        }
 
436
                        // add them to the "My Contacts" group
 
437
                        Contacts.People.addToGroup(
 
438
                                        _doit.getContentResolver(), id,
 
439
                                        Contacts.Groups.GROUP_MY_CONTACTS );
475
440
 
476
441
                        // update cache
477
 
                        _contactsCache.put( id, contact._name );
 
442
                        _contacts.put( contact._name, id );
478
443
 
479
444
                        // update UI
480
445
                        if( !uiInformed ) {
492
457
                        importContactPhones( contactUri, contact._phones );
493
458
                if( contact._emails != null )
494
459
                        importContactEmails( contactUri, contact._emails );
495
 
                if( contact._addresses != null )
496
 
                        importContactAddresses( contactUri, contact._addresses );
497
460
        }
498
461
 
499
462
        private void importContactPhones( Uri contactUri,
502
465
                Long contactId = ContentUris.parseId( contactUri );
503
466
                Uri contactPhonesUri = Uri.withAppendedPath( contactUri,
504
467
                                Contacts.People.Phones.CONTENT_DIRECTORY );
505
 
                Set< String > phonesKeys = phones.keySet();
 
468
                Set phonesKeys = phones.keySet();
506
469
 
507
470
                // add phone numbers
508
 
                Iterator< String > i = phonesKeys.iterator();
 
471
                Iterator i = phonesKeys.iterator();
509
472
                while( i.hasNext() ) {
510
473
                        ContactData.PhoneData phone = phones.get( i.next() );
511
474
 
518
481
                        // anyway, so it's not a problem).
519
482
                        String number = sanitisePhoneNumber( phone._number );
520
483
                        if( number == null ) continue;
521
 
                        if( _contactsCache.hasNumber( contactId, number ) ) continue;
 
484
                        HashSet< String > numbers = _contactNumbers.get( contactId );
 
485
                        if( numbers != null && numbers.contains( number ) ) continue;
522
486
 
523
487
                        // add phone number
524
488
                        ContentValues values = new ContentValues();
526
490
                        values.put( Contacts.Phones.NUMBER, phone._number );
527
491
                        if( phone._isPreferred ) values.put( Contacts.Phones.ISPRIMARY, 1 );
528
492
                        _doit.getContentResolver().insert( contactPhonesUri, values );
529
 
 
530
 
                        // and add this address to the cache to prevent a addition of
531
 
                        // duplicate date from another file
532
 
                        _contactsCache.addNumber( contactId, number );
 
493
                }
 
494
 
 
495
                // now add those phone numbers to the cache to prevent the addition of
 
496
                // duplicate data from another file
 
497
                i = phonesKeys.iterator();
 
498
                while( i.hasNext() ) {
 
499
                        ContactData.PhoneData phone = phones.get( i.next() );
 
500
 
 
501
                        String number = sanitisePhoneNumber( phone._number );
 
502
                        if( number != null ) {
 
503
                                HashSet< String > numbers = _contactNumbers.get( contactId );
 
504
                                if( numbers == null ) {
 
505
                                        _contactNumbers.put( contactId, new HashSet< String >() );
 
506
                                        numbers = _contactNumbers.get( contactId );
 
507
                                }
 
508
                                numbers.add( number );
 
509
                        }
533
510
                }
534
511
        }
535
512
 
539
516
                Long contactId = ContentUris.parseId( contactUri );
540
517
                Uri contactContactMethodsUri = Uri.withAppendedPath( contactUri,
541
518
                                Contacts.People.ContactMethods.CONTENT_DIRECTORY );
542
 
                Set< String > emailsKeys = emails.keySet();
 
519
                Set emailsKeys = emails.keySet();
543
520
 
544
521
                // add email addresses
545
 
                Iterator< String > i = emailsKeys.iterator();
 
522
                Iterator i = emailsKeys.iterator();
546
523
                while( i.hasNext() ) {
547
524
                        ContactData.EmailData email = emails.get( i.next() );
548
525
 
549
 
                        // we don't want to add this email address if it exists already or
550
 
                        // we would introduce duplicates.
 
526
                        // like with phone numbers, we don't want to add this email address
 
527
                        // if it exists already or we would introduce duplicates.
551
528
                        String address = sanitiseEmailAddress( email.getAddress() );
552
529
                        if( address == null ) continue;
553
 
                        if( _contactsCache.hasEmail( contactId, address ) ) continue;
 
530
                        HashSet< String > addresses = _contactEmails.get( contactId );
 
531
                        if( addresses != null && addresses.contains( address ) ) continue;
554
532
 
555
533
                        // add phone number
556
534
                        ContentValues values = new ContentValues();
560
538
                        if( email.isPreferred() )
561
539
                                values.put( Contacts.ContactMethods.ISPRIMARY, 1 );
562
540
                        _doit.getContentResolver().insert( contactContactMethodsUri,
563
 
                                values );
564
 
 
565
 
                        // and add this address to the cache to prevent a addition of
566
 
                        // duplicate date from another file
567
 
                        _contactsCache.addEmail( contactId, address );
 
541
                                        values );
568
542
                }
569
 
        }
570
 
 
571
 
        private void importContactAddresses( Uri contactUri,
572
 
                HashMap< String, ContactData.AddressData > addresses )
573
 
        {
574
 
                Long contactId = ContentUris.parseId( contactUri );
575
 
                Uri contactContactMethodsUri = Uri.withAppendedPath( contactUri,
576
 
                                Contacts.People.ContactMethods.CONTENT_DIRECTORY );
577
 
                Set< String > addressesKeys = addresses.keySet();
578
 
 
579
 
                // add addresses
580
 
                Iterator< String > i = addressesKeys.iterator();
 
543
 
 
544
                // now add those email addresses to the cache to prevent the addition of
 
545
                // duplicate data from another file
 
546
                i = emailsKeys.iterator();
581
547
                while( i.hasNext() ) {
582
 
                        ContactData.AddressData address = addresses.get( i.next() );
583
 
 
584
 
                        // we don't want to add this address if it exists already or we
585
 
                        // would introduce duplicates
586
 
                        if( address == null ) continue;
587
 
                        if( _contactsCache.hasAddress( contactId, address.getAddress() ) )
588
 
                                continue;
589
 
 
590
 
                        // add postal address
591
 
                        ContentValues values = new ContentValues();
592
 
                        values.put( Contacts.ContactMethods.KIND, Contacts.KIND_POSTAL );
593
 
                        values.put( Contacts.ContactMethods.DATA, address.getAddress() );
594
 
                        values.put( Contacts.ContactMethods.TYPE, address.getType() );
595
 
                        _doit.getContentResolver().insert( contactContactMethodsUri,
596
 
                                values );
597
 
 
598
 
                        // and add this address to the cache to prevent a addition of
599
 
                        // duplicate date from another file
600
 
                        _contactsCache.addAddress( contactId, address.getAddress() );
 
548
                        ContactData.EmailData email = emails.get( i.next() );
 
549
 
 
550
                        String address = sanitiseEmailAddress( email.getAddress() );
 
551
                        if( address != null ) {
 
552
                                HashSet< String > addresses = _contactEmails.get( contactId );
 
553
                                if( addresses == null ) {
 
554
                                        _contactEmails.put( contactId, new HashSet< String >() );
 
555
                                        addresses = _contactEmails.get( contactId );
 
556
                                }
 
557
                                addresses.add( address );
 
558
                        }
601
559
                }
602
560
        }
603
561
 
609
567
                }
610
568
        }
611
569
 
612
 
        static public String sanitisePhoneNumber( String number )
 
570
        private void buildContactsCache() throws AbortImportException
 
571
        {
 
572
                // update UI
 
573
                setProgressMessage( R.string.doit_caching );
 
574
 
 
575
                String[] cols;
 
576
                Cursor cur;
 
577
 
 
578
                // init contacts caches
 
579
                _contacts = new HashMap< String, Long >();
 
580
                _contactNumbers = new HashMap< Long, HashSet< String > >();
 
581
                _contactEmails = new HashMap< Long, HashSet< String > >();
 
582
 
 
583
                // query and store map of contact names to ids
 
584
                cols = new String[] { Contacts.People._ID, Contacts.People.NAME };
 
585
                cur = _doit.managedQuery( Contacts.People.CONTENT_URI,
 
586
                                cols, null, null, null);
 
587
                if( cur.moveToFirst() ) {
 
588
                        int idCol = cur.getColumnIndex( Contacts.People._ID );
 
589
                        int nameCol = cur.getColumnIndex( Contacts.People.NAME );
 
590
                        do {
 
591
                                _contacts.put( cur.getString( nameCol ), cur.getLong( idCol ) );
 
592
                        } while( cur.moveToNext() );
 
593
                }
 
594
 
 
595
                // query and store map of contact ids to sets of phone numbers
 
596
                cols = new String[] { Contacts.Phones.PERSON_ID,
 
597
                                Contacts.Phones.NUMBER };
 
598
                cur = _doit.managedQuery( Contacts.Phones.CONTENT_URI,
 
599
                                cols, null, null, null);
 
600
                if( cur.moveToFirst() ) {
 
601
                        int personIdCol = cur.getColumnIndex( Contacts.Phones.PERSON_ID );
 
602
                        int numberCol = cur.getColumnIndex( Contacts.Phones.NUMBER );
 
603
                        do {
 
604
                                Long id = cur.getLong( personIdCol );
 
605
                                String number = sanitisePhoneNumber(
 
606
                                                cur.getString( numberCol ) );
 
607
                                if( number != null ) {
 
608
                                        HashSet< String > numbers = _contactNumbers.get( id );
 
609
                                        if( numbers == null ) {
 
610
                                                _contactNumbers.put( id, new HashSet< String >() );
 
611
                                                numbers = _contactNumbers.get( id );
 
612
                                        }
 
613
                                        numbers.add( number );
 
614
                                }
 
615
                        } while( cur.moveToNext() );
 
616
                }
 
617
 
 
618
                // query and store map of contact ids to sets of email addresses
 
619
                cols = new String[] { Contacts.ContactMethods.PERSON_ID,
 
620
                                Contacts.ContactMethods.DATA };
 
621
                cur = _doit.managedQuery( Contacts.ContactMethods.CONTENT_URI,
 
622
                                cols, Contacts.ContactMethods.KIND + " = ?",
 
623
                                new String[] { "" + Contacts.KIND_EMAIL }, null );
 
624
                if( cur.moveToFirst() ) {
 
625
                        int personIdCol = cur.getColumnIndex(
 
626
                                        Contacts.ContactMethods.PERSON_ID );
 
627
                        int addressCol = cur.getColumnIndex(
 
628
                                        Contacts.ContactMethods.DATA );
 
629
                        do {
 
630
                                Long id = cur.getLong( personIdCol );
 
631
                                String address = sanitiseEmailAddress(
 
632
                                                cur.getString( addressCol ) );
 
633
                                if( address != null ) {
 
634
                                        HashSet< String > addresses = _contactEmails.get( id );
 
635
                                        if( addresses == null ) {
 
636
                                                _contactEmails.put( id, new HashSet< String >() );
 
637
                                                addresses = _contactEmails.get( id );
 
638
                                        }
 
639
                                        addresses.add( address );
 
640
                                }
 
641
                        } while( cur.moveToNext() );
 
642
                }
 
643
        }
 
644
 
 
645
        private String sanitisePhoneNumber( String number )
613
646
        {
614
647
                number = number.replaceAll( "[-\\(\\) ]", "" );
615
 
                Pattern p = Pattern.compile( "^[\\+0-9#*]+" );
 
648
                Pattern p = Pattern.compile( "^\\+?[0-9]+" );
616
649
                Matcher m = p.matcher( number );
617
650
                if( m.lookingAt() ) return m.group( 0 );
618
651
                return null;
619
652
        }
620
653
 
621
 
        static public String sanitiseEmailAddress( String address )
 
654
        private String sanitiseEmailAddress( String address )
622
655
        {
623
656
                address = address.trim();
624
657
                Pattern p = Pattern.compile(
625
 
                        "^[^ @]+@[a-zA-Z]([-a-zA-Z0-9]*[a-zA-z0-9])?(\\.[a-zA-Z]([-a-zA-Z0-9]*[a-zA-z0-9])?)+$" );
 
658
                                "^[^ @]+@[a-zA-Z]([-a-zA-Z0-9]*[a-zA-z0-9])?(\\.[a-zA-Z]([-a-zA-Z0-9]*[a-zA-z0-9])?)+$" );
626
659
                Matcher m = p.matcher( address );
627
660
                if( m.matches() ) {
628
661
                        String[] bits = address.split( "@" );