Statistics
| Revision:

svn-gvsig-desktop / branches / v2_0_0_prep / libraries / libFMap_dalfile / src / org / gvsig / fmap / dal / store / dbf / utils / DbaseFileHeader.java @ 26219

History | View | Annotate | Download (22.3 KB)

1
/*
2
 * Created on 16-feb-2004
3
 *
4
 * To change the template for this generated file go to
5
 * Window>Preferences>Java>Code Generation>Code and Comments
6
 */
7
package org.gvsig.fmap.dal.store.dbf.utils;
8

    
9
import java.io.IOException;
10
import java.nio.ByteBuffer;
11
import java.nio.ByteOrder;
12
import java.nio.channels.FileChannel;
13
import java.util.Calendar;
14
import java.util.Date;
15
import java.util.Iterator;
16

    
17
import org.gvsig.fmap.dal.DataTypes;
18
import org.gvsig.fmap.dal.exception.UnsupportedVersionException;
19
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
20
import org.gvsig.fmap.dal.feature.FeatureType;
21
import org.gvsig.fmap.dal.feature.exception.AttributeFeatureTypeNotSuportedException;
22

    
23
import com.iver.utiles.bigfile.BigByteBuffer2;
24

    
25

    
26
/**
27
 * Class to represent the header of a Dbase III file. Creation date: (5/15/2001
28
 * 5:15:30 PM)
29
 */
30
public class DbaseFileHeader {
31
    // Constant for the size of a record
32
    private int FILE_DESCRIPTOR_SIZE = 32;
33

    
34
        // type of the file, must be 03h
35
        private static final byte MAGIC = 0x03;
36

    
37
        private static final int MINIMUM_HEADER = 33;
38

    
39
    // type of the file, must be 03h
40
    private int myFileType = 0x03;
41

    
42
    // Date the file was last updated.
43
    private Date myUpdateDate = new Date();
44

    
45
    // Number of records in the datafile
46
        private int myNumRecords = 0;
47

    
48
    // Length of the header structure
49
    private int myHeaderLength;
50

    
51
    // Length of the records
52
    private int myRecordLength;
53

    
54
    // Number of fields in the record.
55
    private int myNumFields;
56

    
57
    // collection of header records.
58
    private DbaseFieldDescriptor[] myFieldDescriptions;
59

    
60
        private byte myLanguageID;
61

    
62
    /**
63
     * DbaseFileHreader constructor comment.
64
     */
65
    public DbaseFileHeader() {
66
        super();
67
    }
68

    
69
    /**
70
     * Add a column to this DbaseFileHeader. The type is one of (C N L or D)
71
     * character, number, logical(true/false), or date. The Field length is
72
     * the total length in bytes reserved for this column. The decimal count
73
     * only applies to numbers(N), and floating point values (F), and refers
74
     * to the number of characters to reserve after the decimal point.
75
     *
76
     * @param inFieldName DOCUMENT ME!
77
     * @param inFieldType DOCUMENT ME!
78
     * @param inFieldLength DOCUMENT ME!
79
     * @param inDecimalCount DOCUMENT ME!
80
     * @throws BadFieldDriverException
81
     *
82
     * @throws Exception DOCUMENT ME!
83
     */
84
    public void addColumn(String inFieldName, char inFieldType,
85
        int inFieldLength, int inDecimalCount)
86
                        throws AttributeFeatureTypeNotSuportedException {
87
        if (inFieldLength <= 0) {
88
            inFieldLength = 1;
89
        }
90

    
91
        if (myFieldDescriptions == null) {
92
            myFieldDescriptions = new DbaseFieldDescriptor[0];
93
        }
94

    
95
        int tempLength = 1; // the length is used for the offset, and there is a * for deleted as the first byte
96
        DbaseFieldDescriptor[] tempFieldDescriptors = new DbaseFieldDescriptor[myFieldDescriptions.length +
97
            1];
98

    
99
        for (int i = 0; i < myFieldDescriptions.length; i++) {
100
            myFieldDescriptions[i].myFieldDataAddress = tempLength;
101
            tempLength = tempLength + myFieldDescriptions[i].myFieldLength;
102
            tempFieldDescriptors[i] = myFieldDescriptions[i];
103
        }
104

    
105
        tempFieldDescriptors[myFieldDescriptions.length] = new DbaseFieldDescriptor();
106
        tempFieldDescriptors[myFieldDescriptions.length].myFieldLength = inFieldLength;
107
        tempFieldDescriptors[myFieldDescriptions.length].myDecimalCount = inDecimalCount;
108
        tempFieldDescriptors[myFieldDescriptions.length].myFieldDataAddress = tempLength;
109

    
110
        // set the field name
111
        String tempFieldName = inFieldName;
112

    
113
        if (tempFieldName == null) {
114
            tempFieldName = "NoName";
115
        }
116

    
117
        if (tempFieldName.length() > 11) {
118
            tempFieldName = tempFieldName.substring(0, 11);
119
            warn("FieldName " + inFieldName +
120
                " is longer than 11 characters, truncating to " +
121
                tempFieldName);
122
        }
123

    
124
        tempFieldDescriptors[myFieldDescriptions.length].myFieldName = tempFieldName;
125

    
126
        // the field type
127
        if ((inFieldType == 'C') || (inFieldType == 'c')) {
128
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'C';
129

    
130
            if (inFieldLength > 254) {
131
                warn("Field Length for " + inFieldName + " set to " +
132
                    inFieldLength +
133
                    " Which is longer than 254, not consistent with dbase III");
134
            }
135
        } else if ((inFieldType == 'S') || (inFieldType == 's')) {
136
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'C';
137
            warn("Field type for " + inFieldName +
138
                " set to S which is flat out wrong people!, I am setting this to C, in the hopes you meant character.");
139

    
140
            if (inFieldLength > 254) {
141
                warn("Field Length for " + inFieldName + " set to " +
142
                    inFieldLength +
143
                    " Which is longer than 254, not consistent with dbase III");
144
            }
145

    
146
            tempFieldDescriptors[myFieldDescriptions.length].myFieldLength = 8;
147
        } else if ((inFieldType == 'D') || (inFieldType == 'd')) {
148
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'D';
149

    
150
            if (inFieldLength != 8) {
151
                warn("Field Length for " + inFieldName + " set to " +
152
                    inFieldLength + " Setting to 8 digets YYYYMMDD");
153
            }
154

    
155
            tempFieldDescriptors[myFieldDescriptions.length].myFieldLength = 8;
156
        } else if ((inFieldType == 'F') || (inFieldType == 'f')) {
157
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'F';
158

    
159
            if (inFieldLength > 20) {
160
                warn("Field Length for " + inFieldName + " set to " +
161
                    inFieldLength +
162
                    " Preserving length, but should be set to Max of 20 not valid for dbase IV, and UP specification, not present in dbaseIII.");
163
            }
164
        } else if ((inFieldType == 'N') || (inFieldType == 'n')) {
165
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'N';
166

    
167
            if (inFieldLength > 18) {
168
                warn("Field Length for " + inFieldName + " set to " +
169
                    inFieldLength +
170
                    " Preserving length, but should be set to Max of 18 for dbase III specification.");
171
            }
172

    
173
            if (inDecimalCount < 0) {
174
                warn("Field Decimal Position for " + inFieldName + " set to " +
175
                    inDecimalCount +
176
                    " Setting to 0 no decimal data will be saved.");
177
                tempFieldDescriptors[myFieldDescriptions.length].myDecimalCount = 0;
178
            }
179
//
180
//            if (inDecimalCount > (inFieldLength - 1)) {
181
//                warn("Field Decimal Position for " + inFieldName + " set to " +
182
//                    inDecimalCount + " Setting to " + (inFieldLength - 1) +
183
//                    " no non decimal data will be saved.");
184
//                tempFieldDescriptors[myFieldDescriptions.length].myDecimalCount = inFieldLength -
185
//                    1;
186
//            }
187
        } else if ((inFieldType == 'L') || (inFieldType == 'l')) {
188
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'L';
189

    
190
            if (inFieldLength != 1) {
191
                warn("Field Length for " + inFieldName + " set to " +
192
                    inFieldLength +
193
                    " Setting to length of 1 for logical fields.");
194
            }
195

    
196
            tempFieldDescriptors[myFieldDescriptions.length].myFieldLength = 1;
197
        } else {
198
            throw new AttributeFeatureTypeNotSuportedException(tempFieldName,
199
                                        inFieldType, DataTypes.TYPE_NAMES[inFieldType], "DBF");
200
        }
201

    
202
        // the length of a record
203
        tempLength = tempLength +
204
            tempFieldDescriptors[myFieldDescriptions.length].myFieldLength;
205

    
206
        // set the new fields.
207
        myFieldDescriptions = tempFieldDescriptors;
208
        myHeaderLength = 33 + (32 * myFieldDescriptions.length);
209
        myNumFields = myFieldDescriptions.length;
210
        myRecordLength = tempLength;
211
    }
212

    
213
    /**
214
     * Remove a column from this DbaseFileHeader.
215
     *
216
     * @param inFieldName DOCUMENT ME!
217
     *
218
     * @return index of the removed column, -1 if no found
219
     */
220
    public int removeColumn(String inFieldName) {
221
        int retCol = -1;
222
        int tempLength = 1;
223
        DbaseFieldDescriptor[] tempFieldDescriptors = new DbaseFieldDescriptor[myFieldDescriptions.length -
224
            1];
225

    
226
        for (int i = 0, j = 0; i < myFieldDescriptions.length; i++) {
227
            if (!inFieldName.equalsIgnoreCase(
228
                        myFieldDescriptions[i].myFieldName.trim())) {
229
                // if this is the last field and we still haven't found the
230
                // named field
231
                if ((i == j) && (i == (myFieldDescriptions.length - 1))) {
232
                    System.err.println("Could not find a field named '" +
233
                        inFieldName + "' for removal");
234

    
235
                    return retCol;
236
                }
237

    
238
                tempFieldDescriptors[j] = myFieldDescriptions[i];
239
                tempFieldDescriptors[j].myFieldDataAddress = tempLength;
240
                tempLength += tempFieldDescriptors[j].myFieldLength;
241

    
242
                // only increment j on non-matching fields
243
                j++;
244
            } else {
245
                retCol = i;
246
            }
247
        }
248

    
249
        // set the new fields.
250
        myFieldDescriptions = tempFieldDescriptors;
251
        myHeaderLength = 33 + (32 * myFieldDescriptions.length);
252
        myNumFields = myFieldDescriptions.length;
253
        myRecordLength = tempLength;
254

    
255
        return retCol;
256
    }
257

    
258
    /**
259
     * DOCUMENT ME!
260
     *
261
     * @param inWarn DOCUMENT ME!
262
     */
263
    private void warn(String inWarn) {
264
        //TODO Descomentar esto cuando tenga la clase warning support
265
        //            warnings.warn(inWarn);
266
    }
267

    
268
    /**
269
     * Return the Field Descriptor for the given field.
270
     *
271
     * @param inIndex DOCUMENT ME!
272
     *
273
     * @return DOCUMENT ME!
274
     */
275
    public DbaseFieldDescriptor getFieldDescription(int inIndex) {
276
        return myFieldDescriptions[inIndex];
277
    }
278

    
279
    // Retrieve the length of the field at the given index
280
    public int getFieldLength(int inIndex) {
281
        return myFieldDescriptions[inIndex].myFieldLength;
282
    }
283

    
284
    // Retrieve the location of the decimal point within the field.
285
    public int getFieldDecimalCount(int inIndex) {
286
        return myFieldDescriptions[inIndex].myDecimalCount;
287
    }
288

    
289
    // Retrieve the Name of the field at the given index
290
    public String getFieldName(int inIndex) {
291
        return myFieldDescriptions[inIndex].myFieldName;
292
    }
293

    
294
    // Retrieve the type of field at the given index
295
    public char getFieldType(int inIndex) {
296
        return myFieldDescriptions[inIndex].myFieldType;
297
    }
298

    
299
    /**
300
     * Return the date this file was last updated.
301
     *
302
     * @return DOCUMENT ME!
303
     */
304
    public Date getLastUpdateDate() {
305
        return myUpdateDate;
306
    }
307

    
308
     /**
309
     * Return the number of fields in the records.
310
     *
311
     * @return DOCUMENT ME!
312
     */
313
    public int getNumFields() {
314
        return myNumFields;
315
    }
316

    
317
    /**
318
     * Return the number of records in the file
319
     *
320
     * @return DOCUMENT ME!
321
     */
322
    public int getNumRecords() {
323
        return myNumRecords;
324
    }
325

    
326
    /**
327
     * Return the length of the records in bytes.
328
     *
329
     * @return DOCUMENT ME!
330
     */
331
    public int getRecordLength() {
332
        return myRecordLength;
333
    }
334

    
335
    /**
336
     * Return the length of the header
337
     *
338
     * @return DOCUMENT ME!
339
     */
340
    public int getHeaderLength() {
341
        return myHeaderLength;
342
    }
343

    
344
    /**
345
     * Read the header data from the DBF file.
346
     *
347
     * @param in DOCUMENT ME!
348
     * @throws UnsupportedVersionException
349
     *
350
     * @throws IOException DOCUMENT ME!
351
     */
352
    public void readHeader(BigByteBuffer2 in) throws UnsupportedVersionException {
353
        // type of file.
354
        myFileType = in.get();
355

    
356
        if (myFileType != 0x03) {
357
            throw new UnsupportedVersionException("DBF", Integer
358
                                        .toHexString(myFileType));
359
        }
360

    
361
        // parse the update date information.
362
        int tempUpdateYear = in.get();
363
        int tempUpdateMonth = in.get();
364
        int tempUpdateDay = in.get();
365
        tempUpdateYear = tempUpdateYear + 1900;
366

    
367
        Calendar c = Calendar.getInstance();
368
        c.set(Calendar.YEAR, tempUpdateYear);
369
        c.set(Calendar.MONTH, tempUpdateMonth - 1);
370
        c.set(Calendar.DATE, tempUpdateDay);
371
        myUpdateDate = c.getTime();
372

    
373
        // read the number of records.
374
        in.order(ByteOrder.LITTLE_ENDIAN);
375
        myNumRecords = in.getInt();
376

    
377
        // read the length of the header structure.
378
        myHeaderLength = in.getShort();
379

    
380
        // read the length of a record
381
        myRecordLength = in.getShort(); //posicon 0h
382

    
383
        in.order(ByteOrder.BIG_ENDIAN);
384

    
385
        // skip the reserved bytes in the header.
386
        // in.position(in.position() + 20);
387

    
388
        // Leemos el byte de language
389
        in.position(29);
390
        myLanguageID = in.get();
391

    
392
        // Posicionamos para empezar a leer los campos.
393
        in.position(32);
394

    
395
        // calculate the number of Fields in the header
396
        myNumFields = (myHeaderLength - FILE_DESCRIPTOR_SIZE - 1) / FILE_DESCRIPTOR_SIZE;
397

    
398
        // read all of the header records
399
        myFieldDescriptions = new DbaseFieldDescriptor[myNumFields];
400
        int fieldOffset = 0;
401

    
402
        for (int i = 0; i < myNumFields; i++) {
403
            myFieldDescriptions[i] = new DbaseFieldDescriptor();
404

    
405
            // read the field name
406
            byte[] buffer = new byte[11];
407
            in.get(buffer);
408
            myFieldDescriptions[i].myFieldName = new String(buffer);
409

    
410
            // read the field type
411
            myFieldDescriptions[i].myFieldType = (char) in.get();
412

    
413
            // read the field data address, offset from the start of the record.
414
            myFieldDescriptions[i].myFieldDataAddress = in.getInt();
415

    
416
            // read the field length in bytes
417
            int tempLength = in.get();
418

    
419
            if (tempLength < 0) {
420
                tempLength = tempLength + 256;
421
            }
422

    
423
            myFieldDescriptions[i].myFieldLength = tempLength;
424

    
425
            // read the field decimal count in bytes
426
            myFieldDescriptions[i].myDecimalCount = in.get();
427

    
428
            // NUEVO: Calculamos los offsets aqu? para no
429
            // tener que recalcular cada vez que nos piden
430
            // algo.
431
            myFieldDescriptions[i].myFieldDataAddress = fieldOffset;
432
            fieldOffset += tempLength;
433
            // Fin NUEVO
434
            // read the reserved bytes.
435
            in.position(in.position() + 14);
436
        }
437

    
438
        // Last byte is a marker for the end of the field definitions.
439
        in.get();
440
    }
441

    
442
    /**
443
     * Set the number of records in the file
444
     *
445
     * @param inNumRecords DOCUMENT ME!
446
     */
447
    public void setNumRecords(int inNumRecords) {
448
        myNumRecords = inNumRecords;
449
    }
450

    
451
    /*
452
     * Write the header data to the DBF file.
453
     *
454
     * @param out DOCUMENT ME!
455
     *
456
     * @throws Exception DOCUMENT ME!
457
     *
458
           public void writeHeader(LEDataOutputStream out) throws Exception {
459
               // write the output file type.
460
               out.writeByte(myFileType);
461
               // write the date stuff
462
               Calendar c = Calendar.getInstance();
463
               c.setTime(new Date());
464
               out.writeByte(c.get(Calendar.YEAR) - 1900);
465
               out.writeByte(c.get(Calendar.MONTH) + 1);
466
               out.writeByte(c.get(Calendar.DAY_OF_MONTH));
467
               // write the number of records in the datafile.
468
               out.writeInt(myNumRecords);
469
               // write the length of the header structure.
470
               out.writeShort(myHeaderLength);
471
               // write the length of a record
472
               out.writeShort(myRecordLength);
473
               // write the reserved bytes in the header
474
               for (int i = 0; i < 20; i++)
475
                   out.writeByte(0);
476
               // write all of the header records
477
               int tempOffset = 0;
478
               for (int i = 0; i < myFieldDescriptions.length; i++) {
479
                   // write the field name
480
                   for (int j = 0; j < 11; j++) {
481
                       if (myFieldDescriptions[i].myFieldName.length() > j) {
482
                           out.writeByte((int) myFieldDescriptions[i].myFieldName.charAt(
483
                                   j));
484
                       } else {
485
                           out.writeByte(0);
486
                       }
487
                   }
488
                   // write the field type
489
                   out.writeByte(myFieldDescriptions[i].myFieldType);
490
                   // write the field data address, offset from the start of the record.
491
                   out.writeInt(tempOffset);
492
                   tempOffset += myFieldDescriptions[i].myFieldLength;
493
                   // write the length of the field.
494
                   out.writeByte(myFieldDescriptions[i].myFieldLength);
495
                   // write the decimal count.
496
                   out.writeByte(myFieldDescriptions[i].myDecimalCount);
497
                   // write the reserved bytes.
498
                   for (int j = 0; j < 14; j++)
499
                       out.writeByte(0);
500
               }
501
               // write the end of the field definitions marker
502
               out.writeByte(0x0D);
503
           }
504
     */
505

    
506
    /**
507
     * Class for holding the information assicated with a record.
508
     */
509
    class DbaseFieldDescriptor {
510
        // Field Name
511
        String myFieldName;
512

    
513
        // Field Type (C N L D F or M)
514
        char myFieldType;
515

    
516
        // Field Data Address offset from the start of the record.
517
        int myFieldDataAddress;
518

    
519
        // Length of the data in bytes
520
        int myFieldLength;
521

    
522
        // Field decimal count in Binary, indicating where the decimal is
523
        int myDecimalCount;
524
    }
525

    
526
        public byte getLanguageID() {
527
                return myLanguageID;
528
        }
529

    
530

    
531

    
532
        public static DbaseFileHeader createDbaseHeader(FeatureType featureType)
533
                        throws AttributeFeatureTypeNotSuportedException {
534
                DbaseFileHeader header = new DbaseFileHeader();
535
                Iterator iterator=featureType.iterator();
536
                // TODO header.myLanguageID = langId;
537
                while (iterator.hasNext()) {
538
                        FeatureAttributeDescriptor descriptor = (FeatureAttributeDescriptor) iterator.next();
539

    
540

    
541
                        int type = descriptor.getDataType();
542
                        String colName = descriptor.getName();
543

    
544
                        int fieldLen = descriptor.getSize(); // TODO aqu? el
545
                        // tama?o no es
546
                        // correcto hay que
547
                        // calcularlo, ahora
548
                        // mismo est? puesto
549
                        // a pi??n.
550
                        int decimales = descriptor.getPrecision();
551
                        if ((type==DataTypes.DOUBLE || type==DataTypes.FLOAT) && decimales==0){
552
                                decimales=1;
553
                        }
554

    
555
                        if (DataTypes.DOUBLE == type || DataTypes.FLOAT == type
556
                                        || DataTypes.INT == type || DataTypes.LONG == type) {
557
                                header.addColumn(colName, 'N', Math.min(fieldLen, 18),
558
                                                decimales);
559
                        } else if (DataTypes.DATE == type) {
560
                                header.addColumn(colName, 'D', fieldLen, 0);
561
                        } else if (DataTypes.BOOLEAN == type) {
562
                                header.addColumn(colName, 'L', 1, 0);
563
                        } else if (DataTypes.STRING == type) {
564
                                header.addColumn(colName, 'C', Math.min(254, fieldLen), 0);
565
                        }
566

    
567

    
568
                }
569
                return header;
570
        }
571
        /**
572
         * Write the header data to the DBF file.
573
         *
574
         * @param out
575
         *            A channel to write to. If you have an OutputStream you can
576
         *            obtain the correct channel by using
577
         *            java.nio.Channels.newChannel(OutputStream out).
578
         *
579
         * @throws IOException
580
         *             If errors occur.
581
         */
582
        public void writeHeader(FileChannel out) throws IOException {
583
                // take care of the annoying case where no records have been added...
584
                if (myHeaderLength == -1) {
585
                        myHeaderLength = MINIMUM_HEADER;
586
                }
587

    
588
                // Desde el principio
589
                out.position(0);
590

    
591
                ByteBuffer buffer = ByteBuffer.allocateDirect(myHeaderLength);
592
                buffer.order(ByteOrder.LITTLE_ENDIAN);
593

    
594
                // write the output file type.
595
                buffer.put(MAGIC);
596

    
597
                // write the date stuff
598
                Calendar c = Calendar.getInstance();
599
                c.setTime(new Date());
600
                buffer.put((byte) (c.get(Calendar.YEAR) % 100));
601
                buffer.put((byte) (c.get(Calendar.MONTH) + 1));
602
                buffer.put((byte) (c.get(Calendar.DAY_OF_MONTH)));
603

    
604
                // write the number of records in the datafile.
605
                buffer.putInt(myNumRecords);
606

    
607
                // write the length of the header structure.
608
                buffer.putShort((short) myHeaderLength);
609

    
610
                // write the length of a record
611
                buffer.putShort((short) myRecordLength);
612

    
613
                // // write the reserved bytes in the header
614
                // for (int i=0; i<20; i++) out.writeByteLE(0);
615
                buffer.position(buffer.position() + 20);
616

    
617
                // write all of the header records
618
                int tempOffset = 0;
619

    
620
                if (myFieldDescriptions != null) {
621
                        for (int i = 0; i < myFieldDescriptions.length; i++) {
622
                                // write the field name
623
                                for (int j = 0; j < 11; j++) {
624
                                        if (myFieldDescriptions[i].myFieldName.length() > j) {
625
                                                buffer.put((byte) myFieldDescriptions[i].myFieldName.charAt(j));
626
                                        } else {
627
                                                buffer.put((byte) 0);
628
                                        }
629
                                }
630

    
631
                                // write the field type
632
                                buffer.put((byte) myFieldDescriptions[i].myFieldType);
633

    
634
                                // // write the field data address, offset from the start of the
635
                                // record.
636
                                buffer.putInt(tempOffset);
637
                                tempOffset += myFieldDescriptions[i].myFieldLength;
638

    
639
                                // write the length of the field.
640
                                buffer.put((byte) myFieldDescriptions[i].myFieldLength);
641

    
642
                                // write the decimal count.
643
                                buffer.put((byte) myFieldDescriptions[i].myDecimalCount);
644

    
645
                                // write the reserved bytes.
646
                                // for (in j=0; jj<14; j++) out.writeByteLE(0);
647
                                buffer.position(buffer.position() + 14);
648
                        }
649
                }
650
                // write the end of the field definitions marker
651
                buffer.put((byte) 0x0D);
652

    
653
                buffer.position(0);
654

    
655
                int r = buffer.remaining();
656

    
657
                while ((r -= out.write(buffer)) > 0) {
658
                        ; // do nothing
659
                }
660
        }
661

    
662
}