Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/flexstor/common/io/MacBinaryAccessFile.java


1   /*
2    * MacBinaryAccessFile.java
3    *
4    * Copyright $Date: 2003/08/11 02:22:30 $ FLEXSTOR.net Inc.
5    *
6    * This work is licensed for use and distribution under license terms found at
7    * http://www.flexstor.org/license.html
8    *
9    */
10  
11  package com.flexstor.common.io;
12  
13  import java.io.BufferedInputStream;
14  import java.io.BufferedOutputStream;
15  import java.io.ByteArrayInputStream;
16  import java.io.File;
17  import java.io.FileInputStream;
18  import java.io.FileOutputStream;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.RandomAccessFile;
22  import java.util.Calendar;
23  import java.util.zip.CRC32;
24  
25  import com.flexstor.common.io.xfile.FlexXFile;
26  
27  /**
28  * Instances of this class allow both reading and writing to a MacBinary file.
29  * This class provides method for reading and writing all fields required in the
30  * header of a MacBinary file, as well as access to reding and writing the data fork
31  * and resource fork.
32  * The MacBinary file created complies with the Macintosh Binary Transfer Format
33  * ("MacBinary III") Standard Proposal as of December of 1996.
34  *
35  * Offset   Length      Contents
36  * ------   ------      --------
37  * 000      Byte        old version number, must be kept at zero for compatibility
38  * 001      Byte        Length of filename (must be in the range 1-31)
39  * 002      1-63 Bytes  filename (only "length" bytes are significant).
40  * 065      Long Word   file type (normally expressed as four characters)
41  * 069      Long Word   file creator (normally expressed as four characters)
42  * 073      Byte        original Finder flags
43  *                         Bit 7 - isAlias.
44  *                         Bit 6 - isInvisible.
45  *                         Bit 5 - hasBundle.
46  *                         Bit 4 - nameLocked.
47  *                         Bit 3 - isStationery.
48  *                         Bit 2 - hasCustomIcon.
49  *                         Bit 1 - reserved.
50  *                         Bit 0 - hasBeenInited.
51  * 074      Byte        zero fill, must be zero for compatibility
52  * 075      Word        file's vertical position within its window.
53  * 077      Word        file's horizontal position within its window.
54  * 079      Word        file's window or folder ID.
55  * 081      Byte        "Protected" flag (in low order bit).
56  * 082      Byte        zero fill, must be zero for compatibility
57  * 083      Long Word   Data Fork length (bytes, zero if no Data Fork).
58  * 087      Long Word   Resource Fork length (bytes, zero if no R.F.).
59  * 091      Long Word   File's creation date
60  * 095      Long Word   File's "last modified" date.
61  * 099      Word        length of Get Info comment to be sent after the resource fork (if
62  *                      implemented, see below).
63  * 101      Byte        Finder Flags, bits 0-7. (Bits 8-15 are already in byte 73)
64  *                      Bit 7 - hasNoInits
65  *                      Bit 6 - isShared
66  *                      Bit 5 - requiresSwitchLaunch
67  *                      Bit 4 - ColorReserved
68  *                      Bits 1-3 - color
69  *                      Bit 0 - isOnDesk
70  * *102     Long Word   signature for indentification purposes (ĉmBINĈ)
71  * *106     Byte        script of file name (from the fdScript field of an fxInfo record)
72  * *107     Byte        extended Finder flags (from the fdXFlags field of an fxInfo record)
73  * 108-115              Unused (must be zeroed by creators, must be ignored by readers)
74  * 116      Long Word   Length of total files when packed files are unpacked. As of the
75  *                      writing of this document, this field has never been used.
76  * 120      Word        Length of a secondary header. If this is non-zero, skip this many
77  *                      bytes (rounded up to the next multiple of 128). This is for future
78  *                      expansion only, when sending files with MacBinary, this word
79  *                      should be zero.
80  * *122     Byte        Version number of MacBinary III that the uploading program is
81  *                      written for (the version is 130 for MacBinary III)
82  * 123      Byte        Minimum MacBinary version needed to read this file (set this value
83  *                      at 129 for backwards compatibility with MacBinary II)
84  * 124      Word        CRC of previous 124 bytes
85  */
86  public class MacBinaryAccessFile
87     extends RandomAccessFile
88  {
89     public  static final int BUFFER_SIZE = 1048576; // 1 MB
90     private static final int HEADER_SIZE = 128;
91  
92    /**
93     * MAC_TIME_OFFSET is the number of seconds
94     * between the Mac OS epoch and Java epoch, i.e. the respective </i>t = 0 </i>points.
95     * Mac OS time-keeping starts 01 Jan 1904, Java at 01 Jan 1970.
96     * This value does not account for the fact that Mac-time is in the local time-zone,
97     * while Java-time is fundamentally in GMT.
98      */
99    private static final long MAC_TIME_OFFSET = 24107L * 24L * 60L * 60L;
100 
101    private int       nDataForkLength   = 0;
102    private int       nDataForkPadding  = 0;
103    private int       nRsrcForkLength   = 0;
104    private int       nRsrcForkPadding  = 0;
105    private int       nCommentLength    = 0;
106    private boolean   bOpenedForWriting = false;
107    private boolean   bFileClosed       = false;
108 
109    /**
110    * Creates a random access file stream to read from, and optionally
111    * to write to, the Macbinary file specified by the <code>File</code>
112    * argument.
113    * <p>
114    * The mode argument must either be equal to <code>"r"</code> or to
115    * <code>"rw"</code>, indicating either to open the file for input,
116    * or for both input and output, respectively.
117    *
118    * @param      binFile   the file object.
119    * @param      sMode   the access mode.
120    * @exception  IllegalArgumentException  if the mode argument is not equal
121    *               to <code>"r"</code> or to <code>"rw"</code>.
122    * @exception  IOException               if an I/O error occurs.
123    * @exception  SecurityException         if a security manager exists, its
124    *               <code>checkRead</code> method is called with the pathname
125    *               of the <code>File</code> argument to see if the
126    *               application is allowed read access to the file. If the
127    *               mode argument is equal to <code>"rw"</code>, its
128    *               <code>checkWrite</code> method also is called with the
129    *               pathname to see if the application is allowed write access
130    *               to the file.
131    */
132    public MacBinaryAccessFile( FlexXFile xBinFile, String sMode )
133       throws IOException
134    {
135       super( getPath(xBinFile), sMode );
136       // If opened for writing, fill first 128 bytes with zeros so any information
137       //not set by caller is automatically set to zero.
138       if ( sMode.equals( "rw" ) )
139       {
140          bOpenedForWriting = true;
141          seek( 0 );
142          write( new byte[HEADER_SIZE] );
143       }
144    }
145 
146    /**
147    * Creates a random access file stream to read from, and optionally
148    * to write to, a MacBInary file with the specified name.
149    * <p>
150    * The mode argument must either be equal to <code>"r"</code> or
151    * <code>"rw"</code>, indicating either to open the file for input or
152    * for both input and output.
153    *
154    * @param      sBinFileName   the system-dependent filename.
155    * @param      sMode   the access mode.
156    * @exception  IllegalArgumentException  if the mode argument is not equal
157    *               to <code>"r"</code> or to <code>"rw"</code>.
158    * @exception  IOException               if an I/O error occurs.
159    * @exception  SecurityException         if a security manager exists, its
160    *               <code>checkRead</code> method is called with the name
161    *               argument to see if the application is allowed read access
162    *               to the file. If the mode argument is equal to
163    *               <code>"rw"</code>, its <code>checkWrite</code> method also
164    *               is called with the name argument to see if the application
165    *               is allowed write access to the file. Either of these may
166    *               result in a security exception.
167    */
168    public MacBinaryAccessFile( String sBinFileName, String sMode )
169       throws IOException
170    {
171       this( new FlexXFile(sBinFileName), sMode );
172    }
173 
174    /**
175    * Creates a random access file stream to read from, and optionally
176    * to write to, the Macbinary file specified by the <code>File</code>
177    * argument.
178    * <p>
179    * The mode argument must either be equal to <code>"r"</code> or to
180    * <code>"rw"</code>, indicating either to open the file for input,
181    * or for both input and output, respectively.
182    *
183    * @param      binFile   the file object.
184    * @param      sMode   the access mode.
185    * @exception  IllegalArgumentException  if the mode argument is not equal
186    *               to <code>"r"</code> or to <code>"rw"</code>.
187    * @exception  IOException               if an I/O error occurs.
188    * @exception  SecurityException         if a security manager exists, its
189    *               <code>checkRead</code> method is called with the pathname
190    *               of the <code>File</code> argument to see if the
191    *               application is allowed read access to the file. If the
192    *               mode argument is equal to <code>"rw"</code>, its
193    *               <code>checkWrite</code> method also is called with the
194    *               pathname to see if the application is allowed write access
195    *               to the file.
196    */
197    public MacBinaryAccessFile( File binFile, String sMode )
198       throws IOException
199    {
200       this( new FlexXFile(binFile.getPath()), sMode );
201    }
202 
203    private boolean isMacBinaryIIICompatible()
204       throws IOException
205    {
206       int nVersionNumber = getVersionNumber();
207       //System.out.println( "MacBinary version number: " + nVersionNumber );
208       // We handle MacBinaries I, II and III
209       if ( nVersionNumber >= 128 && nVersionNumber <= 130 )
210          return true;
211       else
212          return false;
213    }
214    
215    public int getVersionNumber()
216       throws IOException
217    {
218       // 122 - Byte - Version number of MacBinary III that the uploading program is
219       //              written for (the version is 130 for MacBinary III)
220       return readByte( 122 );
221    }
222 
223    /**
224    * 000      Byte        old version number, must be kept at zero for compatibility
225    */
226    public void setOldVersionNumber( int nOldVersion )
227       throws IOException
228    {
229       writeIntAsByte( 0, nOldVersion );
230    }
231 
232    public int getOldVersionNumber()
233       throws IOException
234    {
235       return readByteAsInt( 0 );
236    }
237 
238    /**
239    * 001      Byte        Length of filename (must be in the range 1-31)
240    */
241    public void setFileNameLength( int nLength )
242       throws IOException
243    {
244       if ( nLength > 31 )
245          nLength = 31;
246       writeIntAsByte( 1, nLength );
247    }
248 
249    public int getFileNameLength()
250       throws IOException
251    {
252       return readByteAsInt( 1 );
253    }
254 
255    /**
256    * 002      1-63 Bytes  filename (only "length" bytes are significant).
257    */
258    public void setFileName( String sFileName )
259       throws IOException
260    {
261       sFileName = substituteEncodedChars( sFileName );
262 
263       if ( sFileName.length() > 31 )
264          sFileName = sFileName.substring( 0, 31 );
265 
266       writeStringAsBytes( 2, sFileName );
267       setFileNameLength( sFileName.length() );
268    }
269 
270    public String getFileName()
271       throws IOException
272    {
273       return readBytesAsString( 2, getFileNameLength() );
274    }
275 
276    /**
277    * 065      Long Word   file type (normally expressed as four characters)
278    */
279    public void setFileType( byte[] baType )
280       throws IOException
281    {
282       writeBytes( 65, baType, 0, 4 );
283    }
284 
285    public byte[] getFileType()
286       throws IOException
287    {
288       return readBytes( 65, 4 );
289    }
290 
291    /**
292    * 069      Long Word   file creator (normally expressed as four characters)
293    */
294    public void setFileCreator( byte[] baCreator )
295       throws IOException
296    {
297       writeBytes( 69, baCreator, 0, 4 );
298    }
299 
300    public byte[] getFileCreator()
301       throws IOException
302    {
303       return readBytes( 69, 4 );
304    }
305 
306    /**
307    * 073      Byte        original Finder flags
308    *                         Bit 7 - isAlias.
309    *                         Bit 6 - isInvisible.
310    *                         Bit 5 - hasBundle.
311    *                         Bit 4 - nameLocked.
312    *                         Bit 3 - isStationery.
313    *                         Bit 2 - hasCustomIcon.
314    *                         Bit 1 - reserved.
315    *                         Bit 0 - hasBeenInited.
316    */
317    public void setFirstByteOfFinderFlags( byte bFinderFlags )
318       throws IOException
319    {
320       writeByte( 73, bFinderFlags );
321    }
322 
323    public byte getFirstByteOfFinderFlags()
324       throws IOException
325    {
326       return readByte( 73 );
327    }
328 
329    /**
330    * 075      Word        file's vertical position within its window.
331    */
332    public void setVerticalPosition( byte[]  baVerticalPosition )
333       throws IOException
334    {
335       writeBytes( 75, baVerticalPosition, 0, 2 );
336    }
337 
338    public byte[] getVerticalPosition()
339       throws IOException
340    {
341       return readBytes( 75, 2 );
342    }
343 
344    /**
345    * 077      Word        file's horizontal position within its window.
346    */
347    public void setHorizontalPosition( byte[] baHorizontalPosition )
348       throws IOException
349    {
350       writeBytes( 77, baHorizontalPosition, 0, 2 );
351    }
352 
353    public byte[] getHorizontalPosition()
354       throws IOException
355    {
356       return readBytes( 77, 2 );
357    }
358 
359    /**
360    * 079      Word        file's window or folder ID.
361    */
362    public void setFolderId( byte[] baFolderId )
363       throws IOException
364    {
365       writeBytes( 79, baFolderId, 0, 2 );
366    }
367 
368    public byte[] getFolderId()
369       throws IOException
370    {
371       return readBytes( 79, 2 );
372    }
373 
374    /**
375    * 081      Byte        "Protected" flag (in low order bit).
376    */
377    public void setProtectedFlag( byte protectedFlag )
378       throws IOException
379    {
380       writeByte( 81, protectedFlag );
381    }
382 
383    public byte getProtectedFlag()
384       throws IOException
385    {
386       return readByte( 81 );
387    }
388 
389    /**
390    * 083      Long Word   Data Fork length (bytes, zero if no Data Fork).
391    */
392    public void setDataForkLength( int nLength )
393       throws IOException
394    {
395       this.nDataForkLength = nLength;
396       writeInt( 83, nLength );
397    }
398 
399    public int getDataForkLength()
400       throws IOException
401    {
402       nDataForkLength = readInt( 83 );
403       this.nDataForkPadding = getPadding( nDataForkLength );
404 
405       return nDataForkLength;
406    }
407 
408    /**
409    * 087      Long Word   Resource Fork length (bytes, zero if no R.F.).
410    */
411    public void setResourceForkLength( int nLength )
412       throws IOException
413    {
414       this.nRsrcForkLength = nLength;
415       writeInt( 87, nLength );
416    }
417 
418    public int getResourceForkLength()
419       throws IOException
420    {
421       nRsrcForkLength = readInt( 87 );
422       nRsrcForkPadding = getPadding( nRsrcForkLength );
423 
424       return nRsrcForkLength;
425    }
426 
427    // 091      Long Word   File's creation date
428    public void setFileCreationDate( long nFileCreation )
429       throws IOException
430    {
431       writeInt( 91, javaToMacTime( nFileCreation ) );
432    }
433 
434    public int getFileCreationDate()
435       throws IOException
436    {
437       return readInt( 91 );
438    }
439 
440    /**
441    * 095      Long Word   File's "last modified" date.
442    */
443    public void setFileLastModifiedDate( long nFileModified )
444       throws IOException
445    {
446       writeInt( 95, javaToMacTime(nFileModified) );
447    }
448 
449    public int getFileLastModifiedDate()
450       throws IOException
451    {
452       return readInt( 95 );
453    }
454 
455    /**
456    * 099      Word        length of Get Info comment to be sent after the resource fork (if implemented)
457    */
458    public void setCommentLength( int nLength )
459       throws IOException
460    {
461       this.nCommentLength = nLength;
462       writeIntAsShort( 99, nLength );
463    }
464 
465    public int getCommentLength()
466       throws IOException
467    {
468       nCommentLength = readShortAsInt( 99 );
469       return nCommentLength;
470    }
471 
472    /**
473    * 101      Byte        Finder Flags, bits 0-7. (Bits 8-15 are already in byte 73)
474    *                      Bit 7 - hasNoInits
475    *                      Bit 6 - isShared
476    *                      Bit 5 - requiresSwitchLaunch
477    *                      Bit 4 - ColorReserved
478    *                      Bits 1-3 - color
479    *                      Bit 0 - isOnDesk
480    */
481    public void setSecondByteOfFinderFlags( byte bFinderFlags )
482       throws IOException
483    {
484       writeByte( 101, bFinderFlags );
485    }
486 
487    public byte getSecondByteOfFinderFlags()
488       throws IOException
489    {
490       return readByte( 101 );
491    }
492 
493    /**
494    * 106     Byte        script of file name (from the fdScript field of an fxInfo record)
495    */
496    public void setFileNameScript( byte script )
497       throws IOException
498    {
499       writeByte( 106, script );
500    }
501 
502    public byte getFileNameScript()
503       throws IOException
504    {
505       return readByte( 106 );
506    }
507 
508    /**
509    * 107     Byte        extended Finder flags (from the fdXFlags field of an fxInfo record)
510    */
511    public void setExtendedFinderFlags( byte bFinderFlags )
512       throws IOException
513    {
514       writeByte( 107, bFinderFlags );
515    }
516 
517    public byte getExtendedFinderFlags()
518       throws IOException
519    {
520       return readByte( 107 );
521    }
522 
523    public void setDataFork( String sInFile )
524       throws IOException
525    {
526       byte[] baRsrc = null;
527       byte[] baComment = null;
528       BufferedInputStream bInput = null;
529       ByteArrayInputStream baRsrcStream = null;
530       ByteArrayInputStream baCommentStream = null;
531       try
532       {
533          // Check if resource fork and comment was already set, if so read them into byte arrays
534          // to move them after the data fork
535          if ( nRsrcForkLength > 0 )
536          {
537             baRsrc = new byte[ nRsrcForkLength ];
538             seek( getResourceForkOffset() );
539             nRsrcForkLength = read( baRsrc );
540          }
541          if ( nCommentLength > 0 )
542          {
543             baComment = new byte[ nCommentLength ];
544             seek( getCommentOffset() );
545             nCommentLength = read( baComment );
546          }
547 
548          bInput = new BufferedInputStream( new FileInputStream( sInFile ) );
549 
550          // Go to offset 128 to start writing data fork
551          seek( getDataForkOffset() );
552 
553          // Write data fork
554          byte[] buf = new byte[BUFFER_SIZE];
555          int length = 0;
556          int nTotalLength = 0;
557 
558          while( (length = bInput.read(buf)) != -1 )
559          {
560             nTotalLength += length;
561             write( buf, 0, length );
562          }
563          
564          nDataForkPadding = getPadding( nTotalLength );
565          for ( int i = 0; i < nDataForkPadding; i++ )
566             writeByte(0);
567 
568          // If necessary update the length of the data fork
569          if ( nDataForkLength != nTotalLength )
570             setDataForkLength( nTotalLength );
571 
572          if ( nRsrcForkLength > 0 )
573          {
574             baRsrcStream = new ByteArrayInputStream( baRsrc, 0, nRsrcForkLength );
575             setResourceFork( baRsrcStream );
576          }
577          if ( nCommentLength > 0 )
578          {
579             baCommentStream = new ByteArrayInputStream( baComment, 0, nCommentLength );
580             setComment( baCommentStream );
581          }
582       }
583       finally
584       {
585          if ( bInput != null )
586             bInput.close();
587 
588          if ( baRsrcStream != null )
589             baRsrcStream.close();
590 
591          if ( baCommentStream != null )
592             baCommentStream.close();
593       }
594    }
595 
596    public void getDataFork( String sOutFile )
597       throws IOException
598    {
599       BufferedOutputStream bOutput = null;
600 
601       try
602       {
603          getDataForkLength();
604 
605          bOutput = new BufferedOutputStream( new FileOutputStream(sOutFile), BUFFER_SIZE );
606 
607          // Do this after opening the output stream to allow the creation of the data fork
608          // even if it is zero length
609          if ( nDataForkLength <= 0 )
610             return;
611 
612          // Skip until offset of data fork and start writing
613          seek( getDataForkOffset() );
614          byte[] buf = new byte[BUFFER_SIZE];
615          int length = 0;
616          int nReadLength = (nDataForkLength > BUFFER_SIZE) ? BUFFER_SIZE : nDataForkLength;
617          int nRemainLength = nDataForkLength;
618 
619          while( nRemainLength > 0 && (length = this.read(buf, 0, nReadLength)) != -1 )
620          {
621             nRemainLength -= length;
622             bOutput.write( buf, 0, length );
623             
624             if ( nRemainLength < nReadLength )
625                nReadLength = nRemainLength;
626          }
627       }
628       finally
629       {
630          if ( bOutput != null )
631             bOutput.close();
632       }
633    }
634 
635    public void setResourceFork( InputStream input )
636       throws IOException
637    {
638       byte[] baComment = null;
639       ByteArrayInputStream baCommentStream = null;
640 
641       try
642       {
643          // Check if the comment was already set, if so read it into a byte array
644          // to move them after the resource fork
645          if ( nCommentLength > 0 )
646          {
647             baComment = new byte[ nCommentLength ];
648             seek( getCommentOffset() );
649             nCommentLength = read( baComment );
650          }
651 
652          // Go to end of file to start writing resource fork
653          seek( getResourceForkOffset() );
654 
655          // Write resource fork
656          byte[] ba = new byte[BUFFER_SIZE];
657          int length = 0;
658          int nTotalLength = 0;
659          while( (length = input.read(ba)) != -1 )
660          {
661             nTotalLength += length;
662             write( ba, 0, length );
663          }
664 
665          nRsrcForkPadding = getPadding( nTotalLength );
666          for ( int i = 0; i < nRsrcForkPadding; i++ )
667             writeByte(0);
668 
669          // If necessary update the length of the resoruce fork
670          if ( nRsrcForkLength != nTotalLength )
671             setResourceForkLength( nTotalLength );
672 
673          if ( nCommentLength > 0 )
674          {
675             baCommentStream = new ByteArrayInputStream( baComment, 0, nCommentLength );
676             setComment( baCommentStream );
677          }
678       }
679       finally
680       {
681          if ( input != null )
682             input.close();
683 
684          if ( baCommentStream != null )
685             baCommentStream.close();
686       }
687    }
688 
689    public ByteArrayInputStream getResourceFork()
690       throws IOException
691    {
692       getResourceForkLength();
693 
694       if ( nRsrcForkLength <= 0 )
695          return null;
696 
697       getDataForkLength(); // to find out the offset of the resource fork
698 
699       // Go to offset of resource fork
700       seek( getResourceForkOffset() );
701 
702       byte[] ba = new byte[ nRsrcForkLength ];
703       int nLength = read( ba );
704       if ( nLength != -1 )
705          return new ByteArrayInputStream( ba, 0, nLength );
706       else
707          return null;
708    }
709 
710    public void setComment( InputStream input )
711       throws IOException
712    {
713       try
714       {
715          // Go to end of file to start writing the info comment
716          seek( getCommentOffset() );
717 
718          // Write the info comment
719          byte[] ba = new byte[BUFFER_SIZE];
720          int length = 0;
721          int nTotalLength = 0;
722          while( (length = input.read(ba)) != -1 )
723          {
724             nTotalLength += length;
725             write( ba, 0, length );
726          }
727          
728          // If necessary update the length of the comment
729          if ( nCommentLength != nTotalLength )
730             setCommentLength( nTotalLength );
731       }
732       finally
733       {
734          if ( input != null )
735             input.close();
736       }
737    }
738 
739    public ByteArrayInputStream getComment()
740       throws IOException
741    {
742       getCommentLength();
743 
744       if ( nCommentLength <= 0 )
745          return null;
746 
747       getDataForkLength(); // to find out the offset of the comment
748       getResourceForkLength(); // to find out the offset of the comment
749 
750       // Go to offset of comment
751       seek( getCommentOffset() );
752 
753       byte[] ba = new byte[ nCommentLength ];
754       int nLength = read( ba );
755       if ( nLength != -1 )
756          return new ByteArrayInputStream( ba, 0, nLength );
757       else
758          return null;
759    }
760    
761    private int getDataForkOffset()
762    {
763       return HEADER_SIZE;
764    }
765    
766    private int getResourceForkOffset()
767    {
768       return getDataForkOffset() + nDataForkLength + nDataForkPadding;
769    }
770    
771    private int getCommentOffset()
772    {
773       return getResourceForkOffset() + nRsrcForkLength + nRsrcForkPadding; 
774    }
775    
776    /*
777    * Return the number of bytes needed to pad this size to a multiple of 128
778    */
779    private int getPadding( int nSize )
780    {
781       return ( (nSize + 127) & ~127 ) - nSize;
782    }
783    
784    public void close()
785       throws IOException
786    {
787       if ( bOpenedForWriting )
788       {
789          // Set MacBinary information, add CRC number and close the file
790 
791          // 102 - Long Word - signature for indentification purposes (ĉmBINĈ)
792          writeStringAsBytes( 102, "mBIN" );
793 
794          // 122 - Byte - Version number of MacBinary III that the uploading program is
795          //              written for (the version is 130 for MacBinary III)
796          writeIntAsByte( 122, 130 );
797 
798          // 123 - Byte - Minimum MacBinary version needed to read this file (set this value
799          //              at 129 for backwards compatibility with MacBinary II)
800          writeIntAsByte( 123, 129 );
801 
802          addCRC();
803       }
804 
805       super.close();
806       bFileClosed = true;
807    }
808 
809    private void writeByte( long nPos, byte b )
810       throws IOException
811    {
812       seek( nPos );
813       writeByte( b );
814    }
815 
816    private byte readByte( long nPos )
817       throws IOException
818    {
819       seek( nPos );
820       return readByte();
821    }
822 
823    private void writeBytes( long nPos, byte[] ba, int offset, int length )
824       throws IOException
825    {
826       seek( nPos );
827       write( ba, offset, length );
828    }
829 
830    private byte[] readBytes( long nPos, int nLength )
831       throws IOException
832    {
833       seek( nPos );
834       byte[] ba = new byte[ nLength ];
835       read( ba );
836       return ba;
837    }
838 
839    private void writeInt( long nPos, int n )
840       throws IOException
841    {
842       seek( nPos );
843       writeInt( n );
844    }
845 
846    private void writeShort( long nPos, int n )
847       throws IOException
848    {
849       seek( nPos );
850       writeShort( n );
851    }
852 
853    private int readInt( long nPos )
854       throws IOException
855    {
856       seek( nPos );
857       return this.readInt();
858    }
859 
860    private void writeIntAsByte( long nPos, int n )
861       throws IOException
862    {
863       seek( nPos );
864       write( n );
865    }
866 
867    private int readByteAsInt( long nPos )
868       throws IOException
869    {
870       seek( nPos );
871       return read();
872    }
873 
874    private void writeStringAsBytes( long nPos, String s )
875       throws IOException
876    {
877       seek( nPos );
878       writeBytes( s );
879    }
880 
881    private String readBytesAsString( long nPos, int nLength )
882       throws IOException
883    {
884       seek( nPos );
885       byte[] ba = new byte[ nLength ];
886       read( ba );
887       return new String( ba );
888    }
889 
890    private void writeIntAsShort( long nPos, int n )
891       throws IOException
892    {
893       seek( nPos );
894       writeShort( n );
895    }
896 
897    private int readShortAsInt( long nPos )
898       throws IOException
899    {
900       seek( nPos );
901       return (int)readShort();
902    }
903 
904    private void addCRC()
905       throws IOException
906    {
907       byte[] ba = new byte[124];
908       read( ba, 0, 124 );
909 
910       CRC32 checksum = new CRC32();
911       checksum.update( ba );
912       long lChecksum = checksum.getValue();
913 
914       // 124 - Word - CRC of previous 124 bytes
915       writeShort( 124, (int)lChecksum );
916    }
917 
918    /**
919     * Replaces the ocurrences of :nn characters with its right translation
920     */
921    private String substituteEncodedChars( String sFileName )
922    {
923       if ( sFileName == null )
924          return null;
925 
926       int nLength = sFileName.length();
927       char cCurrent;
928       String sReturn = "";
929 
930       for ( int i = 0; i < nLength; i++ )
931       {
932          cCurrent = sFileName.charAt(i);
933          if( cCurrent == ':' )
934          {
935             if( i + 2 < nLength ) // make sure there's enough characters left
936                sReturn += replaceChar( sFileName.substring(i + 1, i + 3) );
937 
938              i += 2; // increment counter to skip over the code part
939          }
940          else // : was not the current char
941             sReturn += cCurrent;
942       }
943       return sReturn;
944    }
945 
946    private String replaceChar( String str )
947    {
948       try
949       {
950          // Using the String representation of the hex number passed as argument,
951          // get its decimal representation, create a new Character and return
952          // its String value.
953          return (new Character( (char)(Integer.parseInt(str, 16)) )).toString();
954       }
955       catch ( NumberFormatException nfe )
956       {
957          return str;
958       }
959    }
960 
961    /**
962     * Replaces the ocurrences of illegal characters by its :nn translation
963     */
964    private String substituteDecodedChars( String sFileName )
965    {
966       return "";
967    }
968 
969    public void finalize()
970    {
971       try
972       {
973          if ( !bFileClosed )
974             close();
975       }
976       catch ( IOException ioe ) {}
977    }
978 
979   /**
980    * Convert a Java time to a Mac OS time, i.e. seconds since midnight 01 Jan 1904 local-time.
981    * Since Mac OS time is local-time, and Java is GMT, account for the
982    * given GMT-offset, which is negative for locations west of prime meridian,
983    * positive for locations east.  That is, it's the GMT-offset of local-time, in minutes.
984    */
985   private int javaToMacTime( long javaTime )
986   {
987     javaTime /= 1000L;
988 
989       // Calendar.getInstance().get(Calendar.ZONE_OFFSET) will return the offset between the Java Epoch in GMT and
990       // local time zone.
991       long gmtOffset = Calendar.getInstance().get(Calendar.ZONE_OFFSET) / 1000L;
992 
993     return (int)(javaTime + MAC_TIME_OFFSET + gmtOffset);
994   }
995 
996    /**
997    * Prints the first 128 bytes of this MacBinary file
998    */
999    public String toString()
1000   {
1001      StringBuffer sb = new StringBuffer( "" );
1002
1003      try
1004      {
1005         /*seek( 0 );
1006         for ( int i = 0; i < HEADER_SIZE; i++ )
1007         {
1008            sb.append( read() + " " );
1009            if ( ( i + 1 ) % 10 == 0 )
1010               sb.append( "\r\n" );
1011         }
1012
1013         sb.append( "\r\n" );*/
1014         sb.append( "MACBINARY INFORMATION:\r\n" );
1015         sb.append( "    VERSION: " + this.getVersionNumber() + "\r\n" );
1016         
1017         sb.append( "File Size: " + this.length() + "\r\n" );
1018         
1019         int nDataForkLength = this.getDataForkLength();
1020         sb.append( "    Data Fork Length: " + nDataForkLength + "\r\n" );
1021         if ( nDataForkLength > 0 )
1022         {
1023            sb.append( "             Padding: " + this.nDataForkPadding + "\r\n" );
1024            sb.append( "              Offset: " + this.getDataForkOffset() + "\r\n" );
1025         }  
1026         int nRsrcForkLength = this.getResourceForkLength();
1027         sb.append( "    Resource Fork Length: " + nRsrcForkLength + "\r\n" );
1028         if ( nRsrcForkLength > 0 )
1029         {
1030            sb.append( "                 Padding: " + this.nRsrcForkPadding + "\r\n" );
1031            sb.append( "                  Offset: " + this.getResourceForkOffset() + "\r\n" );
1032         }
1033         int nCommentLength = this.getCommentLength();
1034         sb.append( "    Comment Length: " + nCommentLength + "\r\n" );
1035         if ( nCommentLength > 0 )
1036            sb.append( "            Offset: " + this.getCommentOffset() + "\r\n" );
1037         
1038         return sb.toString();
1039      }
1040      catch ( IOException ioe ) { return ioe.getMessage(); }
1041   }
1042
1043   private static String getPath( FlexXFile xBinFile )
1044   {
1045      if ( System.getProperty("os.name").startsWith("Win") )
1046         return "\\\\" + com.flexstor.common.util.InetUtil.getLocalHostName() + xBinFile.getLocalPath();
1047      else
1048         return xBinFile.getLocalPath();
1049   }
1050}