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}