/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/VCFImporter.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

1
1
/*
2
2
 * VCFImporter.java
3
3
 *
4
 
 * Copyright (C) 2009 to 2011 Tim Marston <edam@waxworlds.org>
 
4
 * Copyright (C) 2009 Tim Marston <edam@waxworlds.org>
5
5
 *
6
6
 * This file is part of the Import Contacts program (hereafter referred
7
7
 * to as "this program"). For more information, see
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.io.BufferedReader;
27
27
import java.io.File;
28
 
import java.io.FileInputStream;
29
28
import java.io.FileNotFoundException;
30
29
import java.io.FileReader;
31
30
import java.io.FilenameFilter;
32
31
import java.io.IOException;
33
32
import java.io.UnsupportedEncodingException;
34
 
import java.nio.ByteBuffer;
35
 
import java.util.ArrayList;
36
33
import java.util.Arrays;
37
 
import java.util.HashMap;
38
34
import java.util.HashSet;
39
 
import java.util.Iterator;
40
35
import java.util.List;
41
 
import java.util.NoSuchElementException;
42
36
import java.util.Set;
43
37
import java.util.Vector;
44
38
import java.util.regex.Matcher;
45
39
import java.util.regex.Pattern;
46
40
 
 
41
import org.waxworlds.importcontacts.Importer.AbortImportException;
 
42
 
47
43
import android.content.SharedPreferences;
48
44
import android.provider.Contacts;
49
45
import android.provider.Contacts.PhonesColumns;
50
46
 
51
 
public class VcardImporter extends Importer
 
47
public class VCFImporter extends Importer
52
48
{
53
 
        private int _vcard_count = 0;
 
49
        private int _vCardCount = 0;
54
50
        private int _progress = 0;
55
51
 
56
 
        public VcardImporter( Doit doit )
 
52
        public VCFImporter( Doit doit )
57
53
        {
58
54
                super( doit );
59
55
        }
71
67
                try
72
68
                {
73
69
                        // open directory
74
 
                        String path = "/sdcard" + prefs.getString( "location", "/" );
75
 
                        File file = new File( path );
76
 
                        if( !file.exists() )
 
70
                        String location = prefs.getString( "location", "" );
 
71
                        File dir = new File( location );
 
72
                        if( !dir.exists() || !dir.isDirectory() )
77
73
                                showError( R.string.error_locationnotfound );
78
74
 
79
 
                        // directory, or file?
80
 
                        if( file.isDirectory() )
81
 
                        {
82
 
                                // get files
83
 
                                class VCardFilter implements FilenameFilter {
84
 
                                        public boolean accept( File dir, String name ) {
85
 
                                                return name.toLowerCase().endsWith( ".vcf" );
86
 
                                        }
87
 
                                }
88
 
                                files = file.listFiles( new VCardFilter() );
89
 
                        }
90
 
                        else
91
 
                        {
92
 
                                // use just this file
93
 
                                files = new File[ 1 ];
94
 
                                files[ 0 ] = file;
95
 
                        }
 
75
                        // get files
 
76
                        class VCardFilter implements FilenameFilter {
 
77
                            public boolean accept( File dir, String name ) {
 
78
                                return name.toLowerCase().endsWith( ".vcf" );
 
79
                            }
 
80
                        }
 
81
                        files = dir.listFiles( new VCardFilter() );
96
82
                }
97
83
                catch( SecurityException e ) {
98
84
                        showError( R.string.error_locationpermissions );
110
96
                        countVCardFile( files[ i ] );
111
97
                        setTmpProgress( i );
112
98
                }
113
 
                setProgressMax( _vcard_count ); // will also update tmp progress
 
99
                setProgressMax( _vCardCount );  // will also update tmp progress
114
100
 
115
101
                // import them
116
102
                setProgress( 0 );
124
110
                {
125
111
                        // open file
126
112
                        BufferedReader reader = new BufferedReader(
127
 
                                new FileReader( file ) );
 
113
                                        new FileReader( file ) );
128
114
 
129
115
                        // read
130
116
                        String line;
131
 
                        boolean in_vcard = false;
 
117
                        boolean inVCard = false;
132
118
                        while( ( line = reader.readLine() ) != null )
133
119
                        {
134
 
                                if( !in_vcard ) {
 
120
                                if( !inVCard ) {
135
121
                                        // look for vcard beginning
136
 
                                        if( line.matches( "^BEGIN:VCARD" ) ) {
137
 
                                                in_vcard = true;
138
 
                                                _vcard_count++;
 
122
                                        if( line.matches( "^BEGIN[ \\t]*:[ \\t]*VCARD" ) ) {
 
123
                                                inVCard = true;
 
124
                                                _vCardCount++;
139
125
                                        }
140
126
                                }
141
 
                                else if( line.matches( "^END:VCARD" ) )
142
 
                                        in_vcard = false;
 
127
                                else if( line.matches( "^END[ \\t]*:[ \\t]*VCARD" ) )
 
128
                                        inVCard = false;
143
129
                        }
144
130
 
145
131
                }
146
132
                catch( FileNotFoundException e ) {
147
 
                        showError( getText( R.string.error_filenotfound ) +
148
 
                                file.getName() );
 
133
                        showError( getText( R.string.error_filenotfound ) + file.getName() );
149
134
                }
150
135
                catch( IOException e ) {
151
136
                        showError( getText( R.string.error_ioerror ) + file.getName() );
154
139
 
155
140
        private void importVCardFile( File file ) throws AbortImportException
156
141
        {
157
 
                // check file is good
158
 
                if( !file.exists() )
159
 
                        showError( getText( R.string.error_filenotfound ) +
160
 
                                file.getName() );
161
 
                if( file.length() == 0 )
162
 
                        showError( getText( R.string.error_fileisempty ) +
163
 
                                file.getName() );
164
 
 
165
142
                try
166
143
                {
167
 
                        // open/read file
168
 
                        FileInputStream istream = new FileInputStream( file );
169
 
                        byte[] content = new byte[ (int)file.length() ];
170
 
                        istream.read( content );
171
 
 
172
 
                        // import
173
 
                        importVCardFileContent( content, file.getName() );
 
144
                        // open file
 
145
                        BufferedReader reader = new BufferedReader(
 
146
                                        new FileReader( file ) );
 
147
 
 
148
                        // read
 
149
                        StringBuffer content = new StringBuffer();
 
150
                        String line;
 
151
                        while( ( line = reader.readLine() ) != null )
 
152
                                content.append( line ).append( "\n" );
 
153
 
 
154
                        importVCardFileContent( content.toString(), file.getName() );
174
155
                }
175
156
                catch( FileNotFoundException e ) {
176
 
                        showError( getText( R.string.error_filenotfound ) +
177
 
                                file.getName() );
 
157
                        showError( getText( R.string.error_filenotfound ) + file.getName() );
178
158
                }
179
159
                catch( IOException e ) {
180
160
                        showError( getText( R.string.error_ioerror ) + file.getName() );
181
161
                }
182
162
        }
183
163
 
184
 
        private void importVCardFileContent( byte[] content, String fileName )
185
 
                throws AbortImportException
 
164
        private void importVCardFileContent( String content, String fileName )
 
165
                        throws AbortImportException
186
166
        {
187
 
                // go through lines
188
 
                Vcard vcard = null;
189
 
                ContentLineIterator cli = new ContentLineIterator( content );
190
 
                while( cli.hasNext() )
 
167
                // unfold RFC2425 section 5.8.1 folded lines, except that we must also
 
168
                // handle embedded Quoted-Printable encodings that have a trailing '='.
 
169
                // So we remove these first before doing RFC2425 unfolding.
 
170
                content = content.replaceAll( "=\n[ \\t]", "" )
 
171
                                .replaceAll( "\n[ \\t]", "" );
 
172
 
 
173
                // get lines and parse them
 
174
                String[] lines = content.split( "\n" );
 
175
                VCard vCard = null;
 
176
                for( int i = 0; i < lines.length; i++ )
191
177
                {
192
 
                        ByteBuffer buffer = cli.next();
193
 
 
194
 
                        // get a US-ASCII version of the line for processing
195
 
                        String line;
196
 
                        try {
197
 
                                line = new String( buffer.array(), buffer.position(),
198
 
                                        buffer.limit() - buffer.position(), "US-ASCII" );
199
 
                        }
200
 
                        catch( UnsupportedEncodingException e ) {
201
 
                                // we know US-ASCII is supported, so appease the compiler...
202
 
                                line = "";
203
 
                        }
204
 
 
205
 
                        if( vcard == null ) {
 
178
                        String line = lines[ i ];
 
179
 
 
180
                        if( vCard == null ) {
206
181
                                // look for vcard beginning
207
 
                                if( line.matches( "^BEGIN:VCARD" ) ) {
 
182
                                if( line.matches( "^BEGIN[ \\t]*:[ \\t]*VCARD" ) ) {
208
183
                                        setProgress( ++_progress );
209
 
                                        vcard = new Vcard();
 
184
                                        vCard = new VCard();
210
185
                                }
211
186
                        }
212
187
                        else {
213
188
                                // look for vcard content or ending
214
 
                                if( line.matches( "^END:VCARD" ) )
 
189
                                if( line.matches( "^END[ \\t]*:[ \\t]*VCARD" ) )
215
190
                                {
216
 
                                        // finalise the vcard/contact
 
191
                                        // store vcard and do away with it
217
192
                                        try {
218
 
                                                vcard.finaliseVcard();
 
193
                                                vCard.finaliseParsing();
 
194
                                                importContact( vCard );
219
195
                                        }
220
 
                                        catch( Vcard.ParseException e ) {
 
196
                                        catch( VCard.ParseException e ) {
221
197
                                                skipContact();
222
198
                                                if( !showContinue(
223
 
                                                        getText( R.string.error_vcf_parse ).toString()
224
 
                                                        + fileName + "\n" + e.getMessage() ) )
225
 
                                                {
 
199
                                                                getText( R.string.error_vcf_parse ).toString()
 
200
                                                                + fileName + "\n" + e.getMessage() ) )
226
201
                                                        finish( ACTION_ABORT );
227
 
                                                }
228
 
                                        }
229
 
 
230
 
                                        // pass the finalised contact to the importer
231
 
                                        importContact( vcard );
232
 
 
233
 
                                        // and discard it
234
 
                                        vcard = null;
 
202
                                        }
 
203
                                        catch( VCard.SkipContactException e ) {
 
204
                                                skipContact();
 
205
                                                // do nothing
 
206
                                        }
 
207
                                        vCard = null;
235
208
                                }
236
209
                                else
237
210
                                {
238
211
                                        // try giving the line to the vcard
239
212
                                        try {
240
 
                                                vcard.parseLine( buffer, line,
241
 
                                                        cli.doesNextLineLookFolded() );
 
213
                                                vCard.parseLine( line );
242
214
                                        }
243
 
                                        catch( Vcard.ParseException e ) {
 
215
                                        catch( VCard.ParseException e ) {
244
216
                                                skipContact();
245
217
                                                if( !showContinue(
246
 
                                                        getText( R.string.error_vcf_parse ).toString()
247
 
                                                        + fileName + "\n" + e.getMessage() ) )
248
 
                                                {
 
218
                                                                getText( R.string.error_vcf_parse ).toString()
 
219
                                                                + fileName + "\n" + e.getMessage() ) )
249
220
                                                        finish( ACTION_ABORT );
250
 
                                                }
251
221
 
252
222
                                                // although we're continuing, we still need to abort
253
223
                                                // this vCard. Further lines will be ignored until we
254
224
                                                // get to another BEGIN:VCARD line.
255
 
                                                vcard = null;
 
225
                                                vCard = null;
256
226
                                        }
257
 
                                        catch( Vcard.SkipImportException e ) {
 
227
                                        catch( VCard.SkipContactException e ) {
258
228
                                                skipContact();
259
229
                                                // abort this vCard. Further lines will be ignored until
260
230
                                                // we get to another BEGIN:VCARD line.
261
 
                                                vcard = null;
 
231
                                                vCard = null;
262
232
                                        }
263
233
                                }
264
234
                        }
265
235
                }
266
236
        }
267
237
 
268
 
        class ContentLineIterator implements Iterator< ByteBuffer >
269
 
        {
270
 
                protected byte[] _content = null;
271
 
                protected int _pos = 0;
272
 
 
273
 
                public ContentLineIterator( byte[] content )
274
 
                {
275
 
                        _content = content;
276
 
                }
277
 
 
278
 
                @Override
279
 
                public boolean hasNext()
280
 
                {
281
 
                        return _pos < _content.length;
282
 
                }
283
 
 
284
 
                @Override
285
 
                public ByteBuffer next()
286
 
                {
287
 
                        int initial_pos = _pos;
288
 
 
289
 
                        // find newline
290
 
                        for( ; _pos < _content.length; _pos++ )
291
 
                                if( _content[ _pos ] == '\n' )
292
 
                                {
293
 
                                        // adjust for a \r preceding the \n
294
 
                                        int to = ( _pos > 0 && _content[ _pos - 1 ] == '\r' &&
295
 
                                                _pos > initial_pos )? _pos - 1 : _pos;
296
 
                                        _pos++;
297
 
                                        return ByteBuffer.wrap( _content, initial_pos,
298
 
                                                to - initial_pos );
299
 
                                }
300
 
 
301
 
                        // we didn't find one, but were there bytes left?
302
 
                        if( _pos != initial_pos ) {
303
 
                                int to = _pos;
304
 
                                _pos++;
305
 
                                return ByteBuffer.wrap( _content, initial_pos,
306
 
                                        to - initial_pos );
307
 
                        }
308
 
 
309
 
                        // no bytes left
310
 
                        throw new NoSuchElementException();
311
 
                }
312
 
 
313
 
                @Override
314
 
                public void remove()
315
 
                {
316
 
                        throw new UnsupportedOperationException();
317
 
                }
318
 
 
319
 
                /**
320
 
                 * Does the next line, if there is one, look like it should be folded
321
 
                 * onto the end of this one?
322
 
                 * @return
323
 
                 */
324
 
                public boolean doesNextLineLookFolded()
325
 
                {
326
 
                        return _pos > 0 && _pos < _content.length &&
327
 
                                _content[ _pos - 1 ] == '\n' && _content[ _pos ] == ' ';
328
 
                }
329
 
        }
330
 
 
331
 
        private class Vcard extends ContactData
 
238
        private class VCard extends ContactData
332
239
        {
333
240
                private final static int NAMELEVEL_NONE = 0;
334
 
                private final static int NAMELEVEL_FN = 1;
335
 
                private final static int NAMELEVEL_N = 2;
336
 
 
337
 
                private final static int MULTILINE_NONE = 0;
338
 
                private final static int MULTILINE_ENCODED = 1; // v2.1 quoted-printable
339
 
                private final static int MULTILINE_ESCAPED = 2; // v2.1 \\CRLF
340
 
                private final static int MULTILINE_FOLDED = 3;  // v3.0 folding
 
241
                private final static int NAMELEVEL_ORG = 1;
 
242
                private final static int NAMELEVEL_FN = 2;
 
243
                private final static int NAMELEVEL_N = 3;
341
244
 
342
245
                private String _version = null;
343
 
                private Vector< ByteBuffer > _buffers = null;
344
 
                private int _name_level = NAMELEVEL_NONE;
345
 
                private int _parser_multiline_state = MULTILINE_NONE;
346
 
                private String _parser_current_name_and_params = null;
347
 
                private String _parser_buffered_value_so_far = "";
348
 
                private String _cached_organisation = null;
349
 
                private String _cached_title = null;
350
 
 
351
 
                protected class UnencodeResult
352
 
                {
353
 
                        private boolean _another_line_required;
354
 
                        private ByteBuffer _buffer;
355
 
 
356
 
                        public UnencodeResult( boolean another_line_required,
357
 
                                ByteBuffer buffer )
358
 
                        {
359
 
                                _another_line_required = another_line_required;
360
 
                                _buffer = buffer;
361
 
                        }
362
 
 
363
 
                        public boolean isAnotherLineRequired()
364
 
                        {
365
 
                                return _another_line_required;
366
 
                        }
367
 
 
368
 
                        public ByteBuffer getBuffer()
369
 
                        {
370
 
                                return _buffer;
371
 
                        }
372
 
                }
373
 
 
374
 
                @SuppressWarnings("serial")
 
246
                private Vector< String > _lines = null;
 
247
                private int _nameLevel = NAMELEVEL_NONE;
 
248
 
375
249
                protected class ParseException extends Exception
376
250
                {
377
 
                        @SuppressWarnings("unused")
378
251
                        public ParseException( String error )
379
252
                        {
380
253
                                super( error );
382
255
 
383
256
                        public ParseException( int res )
384
257
                        {
385
 
                                super( VcardImporter.this.getText( res ).toString() );
386
 
                        }
387
 
                }
388
 
 
389
 
                @SuppressWarnings("serial")
390
 
                protected class SkipImportException extends Exception { }
391
 
 
392
 
                private String extractCollonPartFromLine( ByteBuffer buffer,
393
 
                        String line, boolean former )
394
 
                {
395
 
                        String ret = null;
396
 
 
397
 
                        // get a US-ASCII version of the line for processing, unless we were
398
 
                        // supplied with one
399
 
                        if( line == null ) {
400
 
                                try {
401
 
                                        line = new String( buffer.array(), buffer.position(),
402
 
                                                buffer.limit() - buffer.position(), "US-ASCII" );
403
 
                                }
404
 
                                catch( UnsupportedEncodingException e ) {
405
 
                                        // we know US-ASCII is supported, so appease the compiler...
406
 
                                        line = "";
407
 
                                }
408
 
                        }
409
 
 
410
 
                        // split line into name and value parts and check to make sure we
411
 
                        // only got 2 parts and that the first part is not zero in length
412
 
                        String[] parts = line.split( ":", 2 );
413
 
                        if( parts.length == 2 && parts[ 0 ].length() > 0 )
414
 
                                ret = parts[ former? 0 : 1 ];
415
 
 
416
 
                        return ret;
417
 
                }
418
 
 
419
 
                private String extractNameAndParamsFromLine( ByteBuffer buffer,
420
 
                        String line )
421
 
                {
422
 
                        return extractCollonPartFromLine( buffer, line, true );
423
 
                }
424
 
 
425
 
                private String extractValueFromLine( ByteBuffer buffer, String line )
426
 
                {
427
 
                        return extractCollonPartFromLine( buffer, line, false );
428
 
                }
429
 
 
430
 
                public void parseLine( ByteBuffer buffer, String line,
431
 
                        boolean next_line_looks_folded )
432
 
                        throws ParseException, SkipImportException,
433
 
                        AbortImportException
434
 
                {
435
 
                        // do we have a version yet?
 
258
                                super( VCFImporter.this.getText( res ).toString() );
 
259
                        }
 
260
                }
 
261
 
 
262
                protected class SkipContactException extends Exception { }
 
263
 
 
264
                public void parseLine( String line )
 
265
                                throws ParseException, SkipContactException,
 
266
                                AbortImportException
 
267
                {
 
268
                        // get property halves
 
269
                        String[] props = line.split( ":" );
 
270
                        for( int i = 0; i < props.length; i++ )
 
271
                                props[ i ] = props[ i ].trim();
 
272
                        if( props.length < 2 ||
 
273
                                        props[ 0 ].length() < 1 || props[ 1 ].length() < 1 )
 
274
                                throw new ParseException( R.string.error_vcf_malformed );
 
275
 
436
276
                        if( _version == null )
437
277
                        {
438
 
                                // tentatively get name and params from line
439
 
                                String name_and_params =
440
 
                                        extractNameAndParamsFromLine( buffer, line );
441
 
 
442
 
                                // is it a version line?
443
 
                                if( name_and_params != null &&
444
 
                                        name_and_params.equals( "VERSION" ) )
 
278
                                if( props[ 0 ].equals( "VERSION" ) )
445
279
                                {
446
 
                                        // yes, get it!
447
 
                                        String value = extractValueFromLine( buffer, line );
448
 
                                        if( !value.equals( "2.1" ) && !value.equals( "3.0" ) )
 
280
                                        // get version
 
281
                                        if( !props[ 1 ].equals( "2.1" ) &&
 
282
                                                        !props[ 1 ].equals( "3.0" ) )
449
283
                                                throw new ParseException( R.string.error_vcf_version );
450
 
                                        _version = value;
 
284
                                        _version = props[ 1 ];
451
285
 
452
 
                                        // parse any buffers we've been accumulating while we waited
453
 
                                        // for a version
454
 
                                        if( _buffers != null )
455
 
                                                for( int i = 0; i < _buffers.size(); i++ )
456
 
                                                        parseLine( _buffers.get( i ), null,
457
 
                                                                i + 1 < _buffers.size() &&
458
 
                                                                _buffers.get( i + 1 ).hasRemaining() &&
459
 
                                                                _buffers.get( i + 1 ).get(
460
 
                                                                        _buffers.get( i + 1 ).position() ) == ' ' );
461
 
                                        _buffers = null;
 
286
                                        // parse any other lines we've accumulated so far
 
287
                                        if( _lines != null )
 
288
                                                for( int i = 0; i < _lines.size(); i++ )
 
289
                                                        parseLine( _lines.get( i ) );
 
290
                                        _lines = null;
462
291
                                }
463
292
                                else
464
293
                                {
465
 
                                        // no, so stash this line till we get a version
466
 
                                        if( _buffers == null )
467
 
                                                _buffers = new Vector< ByteBuffer >();
468
 
                                        _buffers.add( buffer );
 
294
                                        // stash this line till we have a version
 
295
                                        if( _lines == null )
 
296
                                                _lines = new Vector< String >();
 
297
                                        _lines.add( line );
469
298
                                }
470
299
                        }
471
300
                        else
472
301
                        {
473
 
                                // name and params and the position in the buffer where the
474
 
                                // "value" part of the line start
475
 
                                String name_and_params;
476
 
                                int pos;
477
 
 
478
 
                                if( _parser_multiline_state != MULTILINE_NONE )
479
 
                                {
480
 
                                        // if we're currently in a multi-line value, use the stored
481
 
                                        // property name and parameters
482
 
                                        name_and_params = _parser_current_name_and_params;
483
 
 
484
 
                                        // skip some initial line characters, depending on the type
485
 
                                        // of multi-line we're handling
486
 
                                        pos = buffer.position();
487
 
                                        switch( _parser_multiline_state )
488
 
                                        {
489
 
                                        case MULTILINE_FOLDED:
490
 
                                                pos++;
491
 
                                                break;
492
 
                                        case MULTILINE_ENCODED:
493
 
                                                while( pos < buffer.limit() && (
494
 
                                                        buffer.get( pos ) == ' ' ||
495
 
                                                        buffer.get( pos ) == '\t' ) )
496
 
                                                {
497
 
                                                        pos++;
498
 
                                                }
499
 
                                                break;
500
 
                                        default:
501
 
                                                // do nothing
502
 
                                        }
503
 
 
504
 
                                        // take us out of multi-line so that we can re-detect that
505
 
                                        // this line is a multi-line or not
506
 
                                        _parser_multiline_state = MULTILINE_NONE;
507
 
                                }
508
 
                                else
509
 
                                {
510
 
                                        // get name and params from line, and since we're not
511
 
                                        // parsing a subsequent line in a multi-line, this should
512
 
                                        // not fail, or it's an error
513
 
                                        name_and_params =
514
 
                                                extractNameAndParamsFromLine( buffer, line );
515
 
                                        if( name_and_params == null )
516
 
                                                throw new ParseException(
517
 
                                                        R.string.error_vcf_malformed );
518
 
 
519
 
                                        // calculate how many chars to skip from beginning of line
520
 
                                        // so we skip the property "name:" part
521
 
                                        pos = buffer.position() + name_and_params.length() + 1;
522
 
 
523
 
                                        // reset the saved multi-line state
524
 
                                        _parser_current_name_and_params = name_and_params;
525
 
                                        _parser_buffered_value_so_far = "";
526
 
                                }
527
 
 
528
 
                                // get value from buffer, as raw bytes
529
 
                                ByteBuffer value;
530
 
                                value = ByteBuffer.wrap( buffer.array(), pos,
531
 
                                        buffer.limit() - pos );
532
 
 
533
302
                                // get parameter parts
534
 
                                String[] name_param_parts = name_and_params.split( ";", -1 );
535
 
                                for( int i = 0; i < name_param_parts.length; i++ )
536
 
                                        name_param_parts[ i ] = name_param_parts[ i ].trim();
537
 
 
538
 
                                // parse encoding parameter
539
 
                                String encoding = checkParam( name_param_parts, "ENCODING" );
540
 
                                if( encoding != null ) encoding = encoding.toUpperCase();
541
 
                                if( encoding != null && !encoding.equals( "8BIT" ) &&
542
 
                                        !encoding.equals( "QUOTED-PRINTABLE" ) )
543
 
                                        //&& !encoding.equals( "BASE64" ) )
544
 
                                {
545
 
                                        throw new ParseException( R.string.error_vcf_encoding );
546
 
                                }
547
 
 
548
 
                                // parse charset parameter
549
 
                                String charset = checkParam( name_param_parts, "CHARSET" );
550
 
                                if( charset != null ) charset = charset.toUpperCase();
551
 
                                if( charset != null && !charset.equals( "US-ASCII" ) &&
552
 
                                        !charset.equals( "ASCII" ) &&
553
 
                                        !charset.equals( "UTF-8" ) )
554
 
                                {
555
 
                                        throw new ParseException( R.string.error_vcf_charset );
556
 
                                }
557
 
 
558
 
                                // do unencoding (or default to a fake unencoding result with
559
 
                                // the raw string)
560
 
                                UnencodeResult unencoding_result = null;
561
 
                                if( encoding != null && encoding.equals( "QUOTED-PRINTABLE" ) )
562
 
                                        unencoding_result = unencodeQuotedPrintable( value );
563
 
//                              else if( encoding != null && encoding.equals( "BASE64" ) )
564
 
//                                      unencoding_result = unencodeBase64( props[ 1 ], charset );
565
 
                                if( unencoding_result != null ) {
566
 
                                        value = unencoding_result.getBuffer();
567
 
                                        if( unencoding_result.isAnotherLineRequired() )
568
 
                                                _parser_multiline_state = MULTILINE_ENCODED;
569
 
                                }
570
 
 
571
 
                                // convert 8-bit ASCII charset to US-ASCII
572
 
                                if( charset == null || charset.equals( "ASCII" ) ) {
573
 
                                        value = transcodeAsciiToUtf8( value );
574
 
                                        charset = "UTF-8";
575
 
                                }
576
 
 
577
 
                                // process charset
578
 
                                String string_value;
579
 
                                try {
580
 
                                        string_value = new String( value.array(), value.position(),
581
 
                                                value.limit() - value.position(), charset );
582
 
                                } catch( UnsupportedEncodingException e ) {
583
 
                                        throw new ParseException( R.string.error_vcf_charset );
584
 
                                }
585
 
 
586
 
                                // for some entries that have semicolon-separated value parts,
587
 
                                // check to see if the value ends in an escape character, which
588
 
                                // indicates that we have a multi-line value
589
 
                                if( ( name_param_parts[ 0 ].equals( "N" ) ||
590
 
                                        name_param_parts[ 0 ].equals( "ORG" ) ||
591
 
                                        name_param_parts[ 0 ].equals( "ADR" ) ) &&
592
 
                                        doesStringEndInAnEscapeChar( string_value ) )
593
 
                                {
594
 
                                        _parser_multiline_state = MULTILINE_ESCAPED;
595
 
                                        string_value = string_value.substring( 0,
596
 
                                                string_value.length() - 1 );
597
 
                                }
598
 
 
599
 
                                // now we know whether we're in an encoding multi-line,
600
 
                                // determine if we're in a v3 folded multi-line or not
601
 
                                if( _parser_multiline_state == MULTILINE_NONE &&
602
 
                                        _version.equals( "3.0" ) && next_line_looks_folded )
603
 
                                {
604
 
                                        _parser_multiline_state = MULTILINE_FOLDED;
605
 
                                }
606
 
 
607
 
                                // handle multi-lines by buffering them and parsing them when we
608
 
                                // are processing the last line in a multi-line sequence
609
 
                                if( _parser_multiline_state != MULTILINE_NONE ) {
610
 
                                        _parser_buffered_value_so_far += string_value;
611
 
                                        return;
612
 
                                }
613
 
                                String complete_value =
614
 
                                        ( _parser_buffered_value_so_far + string_value ).trim();
615
 
 
616
 
                                // ignore empty values
617
 
                                if( complete_value.length() < 1 ) return;
 
303
                                String[] params = props[ 0 ].split( ";" );
 
304
                                for( int i = 0; i < params.length; i++ )
 
305
                                        params[ i ] = params[ i ].trim();
618
306
 
619
307
                                // parse some properties
620
 
                                if( name_param_parts[ 0 ].equals( "N" ) )
621
 
                                        parseN( name_param_parts, complete_value );
622
 
                                else if( name_param_parts[ 0 ].equals( "FN" ) )
623
 
                                        parseFN( name_param_parts, complete_value );
624
 
                                else if( name_param_parts[ 0 ].equals( "ORG" ) )
625
 
                                        parseORG( name_param_parts, complete_value );
626
 
                                else if( name_param_parts[ 0 ].equals( "TITLE" ) )
627
 
                                        parseTITLE( name_param_parts, complete_value );
628
 
                                else if( name_param_parts[ 0 ].equals( "TEL" ) )
629
 
                                        parseTEL( name_param_parts, complete_value );
630
 
                                else if( name_param_parts[ 0 ].equals( "EMAIL" ) )
631
 
                                        parseEMAIL( name_param_parts, complete_value );
632
 
                                else if( name_param_parts[ 0 ].equals( "ADR" ) )
633
 
                                        parseADR( name_param_parts, complete_value );
634
 
                        }
635
 
                }
636
 
 
637
 
                private boolean doesStringEndInAnEscapeChar( String string )
638
 
                {
639
 
                        // count the number of backslashes at the end of the string
640
 
                        int count = 0;
641
 
                        for( int a = string.length() - 1; a >= 0; a-- )
642
 
                                if( string.charAt( a ) == '\\' )
643
 
                                        count++;
644
 
                                else
645
 
                                        break;
646
 
 
647
 
                        // if there are an even number of backslashes then the final one
648
 
                        // doesn't count
649
 
                        return ( count & 1 ) == 1;
650
 
                }
651
 
 
652
 
                private String[] splitValueBySemicolon( String value )
653
 
                {
654
 
                        // split string in to parts by semicolon
655
 
                        ArrayList< String > parts = new ArrayList< String >(
656
 
                                Arrays.asList( value.split(  ";" ) ) );
657
 
 
658
 
                        // go through parts
659
 
                        for( int a = 0; a < parts.size(); a++ )
660
 
                        {
661
 
                                String str = parts.get( a );
662
 
 
663
 
                                // look for parts that end in an escape character, but ignore
664
 
                                // the final part. We've already detected escape chars at the
665
 
                                // end of the final part in parseLine() and handled multi-lines
666
 
                                // accordingly.
667
 
                                if( a < parts.size() - 1 &&
668
 
                                        doesStringEndInAnEscapeChar( str ) )
669
 
                                {
670
 
                                        // join the next part to this part and remove the next part
671
 
                                        parts.set( a, str.substring( 0, str.length() - 1 ) +
672
 
                                                ';' + parts.get( a + 1 ) );
673
 
                                        parts.remove( a + 1 );
674
 
 
675
 
                                        // re-visit this part
676
 
                                        a--;
677
 
                                        continue;
678
 
                                }
679
 
 
680
 
                                // trim and replace string
681
 
                                str = str.trim();
682
 
                                parts.set( a, str );
683
 
                        }
684
 
 
685
 
                        String[] ret = new String[ parts.size() ];
686
 
                        return parts.toArray( ret );
 
308
                                if( params[ 0 ].equals( "N" ) )
 
309
                                        parseN( params, props[ 1 ] );
 
310
                                else if( params[ 0 ].equals( "FN" ) )
 
311
                                        parseFN( params, props[ 1 ] );
 
312
                                else if( params[ 0 ].equals( "ORG" ) )
 
313
                                        parseORG( params, props[ 1 ] );
 
314
                                else if( params[ 0 ].equals( "TEL" ) )
 
315
                                        parseTEL( params, props[ 1 ] );
 
316
                                else if( params[ 0 ].equals( "EMAIL" ) )
 
317
                                        parseEMAIL( params, props[ 1 ] );
 
318
                        }
687
319
                }
688
320
 
689
321
                private void parseN( String[] params, String value )
 
322
                                throws ParseException, SkipContactException,
 
323
                                AbortImportException
690
324
                {
691
325
                        // already got a better name?
692
 
                        if( _name_level >= NAMELEVEL_N ) return;
 
326
                        if( _nameLevel >= NAMELEVEL_N ) return;
693
327
 
694
328
                        // get name parts
695
 
                        String[] name_parts = splitValueBySemicolon( value );
 
329
                        String[] nameparts = value.split( ";" );
 
330
                        for( int i = 0; i < nameparts.length; i++ )
 
331
                                nameparts[ i ] = nameparts[ i ].trim();
696
332
 
697
333
                        // build name
698
334
                        value = "";
699
 
                        if( name_parts.length > 1 && name_parts[ 1 ].length() > 0 )
700
 
                                value += name_parts[ 1 ];
701
 
                        if( name_parts.length > 0 && name_parts[ 0 ].length() > 0 )
702
 
                                value += ( value.length() == 0? "" : " " ) + name_parts[ 0 ];
 
335
                        if( nameparts.length > 1 && nameparts[ 1 ].length() > 0 )
 
336
                                value += nameparts[ 1 ];
 
337
                        if( nameparts[ 0 ].length() > 0 )
 
338
                                value += ( value.length() == 0? "" : " " ) + nameparts[ 0 ];
703
339
 
704
340
                        // set name
705
 
                        setName( value );
706
 
                        _name_level = NAMELEVEL_N;
 
341
                        setName( undoCharsetAndEncoding( params, value ) );
 
342
                        _nameLevel = NAMELEVEL_N;
 
343
 
 
344
                        // check now to see if we need to import this contact (to avoid
 
345
                        // parsing the rest of the vCard unnecessarily)
 
346
                        if( !isImportRequired( getName() ) )
 
347
                                throw new SkipContactException();
707
348
                }
708
349
 
709
350
                private void parseFN( String[] params, String value )
 
351
                                throws ParseException, SkipContactException
710
352
                {
711
353
                        // already got a better name?
712
 
                        if( _name_level >= NAMELEVEL_FN ) return;
 
354
                        if( _nameLevel >= NAMELEVEL_FN ) return;
713
355
 
714
356
                        // set name
715
 
                        setName( value );
716
 
                        _name_level = NAMELEVEL_FN;
 
357
                        setName( undoCharsetAndEncoding( params, value ) );
 
358
                        _nameLevel = NAMELEVEL_FN;
717
359
                }
718
360
 
719
361
                private void parseORG( String[] params, String value )
 
362
                                throws ParseException, SkipContactException
720
363
                {
 
364
                        // already got a better name?
 
365
                        if( _nameLevel >= NAMELEVEL_ORG ) return;
 
366
 
721
367
                        // get org parts
722
 
                        String[] org_parts = splitValueBySemicolon( value );
723
 
                        if( org_parts == null || org_parts.length < 1 ) return;
724
 
 
725
 
                        // build organisation name
726
 
                        StringBuilder builder = new StringBuilder(
727
 
                                String.valueOf( org_parts[ 0 ] ) );
728
 
                        for( int a = 1; a < org_parts.length; a++ )
729
 
                                builder.append( ", " ).append( org_parts[ a ] );
730
 
                        String organisation = builder.toString();
731
 
 
732
 
                        // set organisation name (using a title we've previously found)
733
 
                        addOrganisation( organisation, _cached_title, true );
734
 
 
735
 
                        // if we've not previously found a title, store this organisation
736
 
                        // name (we'll need it when we find a title to update the
737
 
                        // organisation, by name), else if we *have* previously found a
738
 
                        // title, clear it (since we just used it)
739
 
                        if( _cached_title == null )
740
 
                                _cached_organisation = organisation;
741
 
                        else
742
 
                                _cached_title = null;
743
 
                }
744
 
 
745
 
                private void parseTITLE( String[] params, String value )
746
 
                {
747
 
                        // if we previously had an organisation, look it up and append this
748
 
                        // title to it
749
 
                        if( _cached_organisation != null && hasOrganisations() ) {
750
 
                                HashMap< String, ExtraDetail > datas = getOrganisations();
751
 
                                ExtraDetail detail = datas.get( _cached_organisation );
752
 
                                if( detail != null )
753
 
                                        detail.setExtra( value );
754
 
                        }
755
 
 
756
 
                        // same as when handling organisation, if we've not previously found
757
 
                        // an organisation we store this title, else we clear it (since we
758
 
                        // just appended this title to it)
759
 
                        if( _cached_organisation == null )
760
 
                                _cached_title = value;
761
 
                        else
762
 
                                _cached_organisation = null;
 
368
                        String[] orgparts = value.split( ";" );
 
369
                        for( int i = 0; i < orgparts.length; i++ )
 
370
                                orgparts[ i ] = orgparts[ i ].trim();
 
371
 
 
372
                        // build name
 
373
                        if( orgparts[ 0 ].length() == 0 && orgparts.length > 1 )
 
374
                                value = orgparts[ 1 ];
 
375
                        else
 
376
                                value = orgparts[ 0 ];
 
377
 
 
378
                        // set name
 
379
                        setName( undoCharsetAndEncoding( params, value ) );
 
380
                        _nameLevel = NAMELEVEL_ORG;
763
381
                }
764
382
 
765
383
                private void parseTEL( String[] params, String value )
 
384
                                throws ParseException
766
385
                {
767
386
                        if( value.length() == 0 ) return;
768
387
 
769
388
                        Set< String > types = extractTypes( params, Arrays.asList(
770
 
                                "PREF", "HOME", "WORK", "VOICE", "FAX", "MSG", "CELL",
771
 
                                "PAGER", "BBS", "MODEM", "CAR", "ISDN", "VIDEO" ) );
 
389
                                        "PREF", "HOME", "WORK", "VOICE", "FAX", "MSG", "CELL",
 
390
                                        "PAGER", "BBS", "MODEM", "CAR", "ISDN", "VIDEO" ) );
772
391
 
773
392
                        // here's the logic...
774
 
                        boolean is_preferred = types.contains( "PREF" );
775
 
                        int type;
 
393
                        boolean preferred = types.contains( "PREF" );
 
394
                        if( types.contains( "VOICE" ) )
 
395
                                if( types.contains( "WORK" ) )
 
396
                                        addPhone( value, PhonesColumns.TYPE_WORK, preferred );
 
397
                                else
 
398
                                        addPhone( value, PhonesColumns.TYPE_HOME, preferred );
 
399
                        else if( types.contains( "CELL" ) || types.contains( "VIDEO" ) )
 
400
                                addPhone( value, PhonesColumns.TYPE_MOBILE, preferred );
776
401
                        if( types.contains( "FAX" ) )
777
402
                                if( types.contains( "HOME" ) )
778
 
                                        type = PhonesColumns.TYPE_FAX_HOME;
 
403
                                        addPhone( value, PhonesColumns.TYPE_FAX_HOME, preferred );
779
404
                                else
780
 
                                        type = PhonesColumns.TYPE_FAX_WORK;
781
 
                        else if( types.contains( "CELL" ) || types.contains( "VIDEO" ) )
782
 
                                type = PhonesColumns.TYPE_MOBILE;
783
 
                        else if( types.contains( "PAGER" ) )
784
 
                                type = PhonesColumns.TYPE_PAGER;
785
 
                        else if( types.contains( "WORK" ) )
786
 
                                type = PhonesColumns.TYPE_WORK;
787
 
                        else
788
 
                                type = PhonesColumns.TYPE_HOME;
789
 
 
790
 
                        // add phone number
791
 
                        addNumber( value, type, is_preferred );
 
405
                                        addPhone( value, PhonesColumns.TYPE_FAX_WORK, preferred );
 
406
                        if( types.contains( "PAGER" ) )
 
407
                                addPhone( value, PhonesColumns.TYPE_PAGER, preferred );
792
408
                }
793
409
 
794
410
                public void parseEMAIL( String[] params, String value )
796
412
                        if( value.length() == 0 ) return;
797
413
 
798
414
                        Set< String > types = extractTypes( params, Arrays.asList(
799
 
                                "PREF", "WORK", "HOME", "INTERNET" ) );
800
 
 
801
 
                        // add email address
802
 
                        boolean is_preferred = types.contains( "PREF" );
803
 
                        int type;
804
 
                        if( types.contains( "WORK" ) )
805
 
                                type = Contacts.ContactMethods.TYPE_WORK;
806
 
                        else
807
 
                                type = Contacts.ContactMethods.TYPE_HOME;
808
 
 
809
 
                        addEmail( value, type, is_preferred );
810
 
                }
811
 
 
812
 
                private void parseADR( String[] params, String value )
813
 
                {
814
 
                        // get address parts
815
 
                        String[] adr_parts = splitValueBySemicolon( value );
816
 
 
817
 
                        // build address
818
 
                        value = "";
819
 
                        for( int a = 0; a < adr_parts.length; a++ ) {
820
 
                                if( value.length() > 0 ) value += "\n";
821
 
                                value += adr_parts[ a ].trim();
822
 
                        }
823
 
 
824
 
                        Set< String > types = extractTypes( params, Arrays.asList(
825
 
                                "PREF", "WORK", "HOME", "INTERNET" ) );
826
 
 
827
 
                        // add address
828
 
                        int type;
829
 
                        if( types.contains( "WORK" ) )
830
 
                                type = Contacts.ContactMethods.TYPE_WORK;
831
 
                        else
832
 
                                type = Contacts.ContactMethods.TYPE_HOME;
833
 
 
834
 
                        addAddress( value, type );
835
 
                }
836
 
 
837
 
                public void finaliseVcard()
838
 
                        throws ParseException
 
415
                                        "PREF", "WORK", "HOME", "INTERNET" ) );
 
416
 
 
417
                        // here's the logic...
 
418
                        boolean preferred = types.contains( "PREF" );
 
419
                        if( types.contains( "WORK" ) )
 
420
                                addEmail( value, Contacts.ContactMethods.TYPE_WORK, preferred );
 
421
                        else
 
422
                                addEmail( value, Contacts.ContactMethods.TYPE_HOME, preferred );
 
423
                }
 
424
 
 
425
                public void finaliseParsing()
 
426
                                throws ParseException, SkipContactException,
 
427
                                AbortImportException
839
428
                {
840
429
                        // missing version (and data is present)
841
 
                        if( _version == null && _buffers != null )
 
430
                        if( _version == null && _lines != null )
842
431
                                throw new ParseException( R.string.error_vcf_malformed );
843
432
 
844
 
                        // finalise the parent class
845
 
                        try {
846
 
                                finalise();
847
 
                        }
848
 
                        catch( ContactNotIdentifiableException e ) {
849
 
                                throw new ParseException( R.string.error_vcf_notenoughinfo );
850
 
                        }
 
433
                        //  missing name properties?
 
434
                        if( _nameLevel == NAMELEVEL_NONE )
 
435
                                throw new ParseException( R.string.error_vcf_noname );
 
436
 
 
437
                        // check if we should import this one? If we've already got an 'N'-
 
438
                        // type name, this will already have been done by parseN() so we
 
439
                        // mustn't do this here (or it could prompt twice!)
 
440
                        if( _nameLevel < NAMELEVEL_N && !isImportRequired( getName() ) )
 
441
                                throw new SkipContactException();
 
442
                }
 
443
 
 
444
                private String undoCharsetAndEncoding( String[] params, String value )
 
445
                                throws ParseException
 
446
                {
 
447
                        // check encoding/charset
 
448
                        String charset, encoding;
 
449
                        if( ( charset = checkParam( params, "CHARSET" ) ) != null &&
 
450
                                        !charset.equals( "UTF-8" ) && !charset.equals( "UTF-16" ) )
 
451
                                throw new ParseException( R.string.error_vcf_charset );
 
452
                        if( ( encoding = checkParam( params, "ENCODING" ) ) != null &&
 
453
                                        !encoding.equals( "QUOTED-PRINTABLE" ) )
 
454
                                throw new ParseException( R.string.error_vcf_encoding );
 
455
 
 
456
                        // do decoding?
 
457
                        if( encoding != null && encoding.equals( "QUOTED-PRINTABLE" ) )
 
458
                                return unencodeQuotedPrintable( value, charset );
 
459
 
 
460
                        // nothing to do!
 
461
                        return value;
851
462
                }
852
463
 
853
464
                private String checkParam( String[] params, String name )
854
465
                {
855
 
                        Pattern p = Pattern.compile(
856
 
                                "^" + name + "[ \\t]*=[ \\t]*(\"?)(.*)\\1$" );
 
466
                        Pattern p = Pattern.compile( "^" + name + "[ \\t]*=[ \\t]*(.*)$" );
857
467
                        for( int i = 0; i < params.length; i++ ) {
858
468
                                Matcher m = p.matcher( params[ i ] );
859
469
                                if( m.matches() )
860
 
                                        return m.group( 2 );
 
470
                                        return m.group( 1 );
861
471
                        }
862
472
                        return null;
863
473
                }
864
474
 
865
475
                private Set< String > extractTypes( String[] params,
866
 
                        List< String > valid_types )
 
476
                                List< String > validTypes )
867
477
                {
868
478
                        HashSet< String > types = new HashSet< String >();
869
479
 
870
480
                        // get 3.0-style TYPE= param
871
 
                        String type_param;
872
 
                        if( ( type_param = checkParam( params, "TYPE" ) ) != null ) {
873
 
                                String[] parts = type_param.split( "," );
874
 
                                for( int i = 0; i < parts.length; i++ )
875
 
                                        if( valid_types.contains( parts[ i ] ) )
876
 
                                                types.add( parts[ i ] );
 
481
                        String typeParam;
 
482
                        if( ( typeParam = checkParam( params, "TYPE" ) ) != null ) {
 
483
                                String[] bits = typeParam.split( "," );
 
484
                                for( int i = 0; i < bits.length; i++ )
 
485
                                        if( validTypes.contains( bits[ i ] ) )
 
486
                                                types.add( bits[ i ] );
877
487
                        }
878
488
 
879
489
                        // get 2.1-style type param
880
490
                        if( _version.equals( "2.1" ) ) {
881
491
                                for( int i = 1; i < params.length; i++ )
882
 
                                        if( valid_types.contains( params[ i ] ) )
 
492
                                        if( validTypes.contains( params[ i ] ) )
883
493
                                                types.add( params[ i ] );
884
494
                        }
885
495
 
886
496
                        return types;
887
497
                }
888
498
 
889
 
                private UnencodeResult unencodeQuotedPrintable( ByteBuffer in )
 
499
                private String unencodeQuotedPrintable( String str, String charset )
890
500
                {
891
 
                        boolean another = false;
 
501
                        // default encoding scheme
 
502
                        if( charset == null ) charset = "UTF-8";
892
503
 
893
 
                        // unencode quoted-printable encoding, as per RFC1521 section 5.1
894
 
                        byte[] out = new byte[ in.limit() - in.position() ];
 
504
                        // unencode quoted-pritable encoding, as per RFC1521 section 5.1
 
505
                        byte[] bytes = new byte[ str.length() ];
895
506
                        int j = 0;
896
 
                        for( int i = in.position(); i < in.limit(); i++ )
897
 
                        {
898
 
                                // get next char and process...
899
 
                                byte ch = in.array()[ i ];
900
 
                                if( ch == '=' && i < in.limit() - 2 )
901
 
                                {
902
 
                                        // we found a =XX format byte, add it
903
 
                                        out[ j ] = (byte)(
904
 
                                                        Character.digit( in.array()[ i + 1 ], 16 ) * 16 +
905
 
                                                        Character.digit( in.array()[ i + 2 ], 16 ) );
 
507
                        for( int i = 0; i < str.length(); i++, j++ ) {
 
508
                                char ch = str.charAt( i );
 
509
                                if( ch == '=' && i < str.length() - 2 ) {
 
510
                                        bytes[ j ] = (byte)(
 
511
                                                        Character.digit( str.charAt( i + 1 ), 16 ) * 16 +
 
512
                                                        Character.digit( str.charAt( i + 2 ), 16 ) );
906
513
                                        i += 2;
907
514
                                }
908
 
                                else if( ch == '=' && i == in.limit() - 1 )
909
 
                                {
910
 
                                        // we found a '=' at the end of a line signifying a multi-
911
 
                                        // line string, so we don't add it.
912
 
                                        another = true;
913
 
                                        continue;
914
 
                                }
915
515
                                else
916
 
                                        // just a normal char...
917
 
                                        out[ j ] = (byte)ch;
918
 
                                j++;
919
 
                        }
920
 
 
921
 
                        return new UnencodeResult( another, ByteBuffer.wrap( out, 0, j ) );
922
 
                }
923
 
 
924
 
                private ByteBuffer transcodeAsciiToUtf8( ByteBuffer in )
925
 
                {
926
 
                        // transcode
927
 
                        byte[] out = new byte[ ( in.limit() - in.position() ) * 2 ];
928
 
                        int j = 0;
929
 
                        for( int a = in.position(); a < in.limit(); a++ )
930
 
                        {
931
 
                                // if char is < 127, keep it as-is
932
 
                                if( in.array()[ a ] >= 0 )
933
 
                                        out[ j++ ] = in.array()[ a ];
934
 
 
935
 
                                // else, convert it to UTF-8
936
 
                                else {
937
 
                                        int b = 0xff & (int)in.array()[ a ];
938
 
                                        out[ j++ ] = (byte)( 0xc0 | ( b >> 6 ) );
939
 
                                        out[ j++ ] = (byte)( 0x80 | ( b & 0x3f ) );
940
 
                                }
941
 
                        }
942
 
 
943
 
                        return ByteBuffer.wrap( out, 0, j );
 
516
                                        bytes[ j ] = (byte)ch;
 
517
                        }
 
518
                        try {
 
519
                                return new String( bytes, 0, j, charset );
 
520
                        } catch( UnsupportedEncodingException e ) { }
 
521
                        return null;
944
522
                }
945
523
        }
946
524
}