001: /*
002: * $RCSfile: CompressedGeometryFile.java,v $
003: *
004: * Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * - Redistribution of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * - Redistribution in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * Neither the name of Sun Microsystems, Inc. or the names of
019: * contributors may be used to endorse or promote products derived
020: * from this software without specific prior written permission.
021: *
022: * This software is provided "AS IS," without a warranty of any
023: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
024: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
025: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
026: * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
027: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
028: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
029: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
030: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
031: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
032: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
033: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
034: * POSSIBILITY OF SUCH DAMAGES.
035: *
036: * You acknowledge that this software is not designed, licensed or
037: * intended for use in the design, construction, operation or
038: * maintenance of any nuclear facility.
039: *
040: * $Revision: 1.3 $
041: * $Date: 2007/02/09 17:20:21 $
042: * $State: Exp $
043: */
044:
045: package com.sun.j3d.utils.geometry.compression;
046:
047: import java.io.FileNotFoundException;
048: import java.io.IOException;
049: import java.io.RandomAccessFile;
050:
051: //
052: // The compressed geometry file format supported by this class has a 32
053: // byte header followed by multiple compressed geometry objects.
054: //
055: // Each object consists of a block of compressed data and an 8-byte
056: // individual block header describing its contents.
057: //
058: // The file ends with a directory data structure used for random access,
059: // containing a 64-bit offset for each object in the order in which it
060: // appears in the file. This is also used to find the size of the largest
061: // object in the file and must be present.
062: //
063:
064: /**
065: * This class provides methods to read and write compressed geometry resource
066: * files. These files usually end with the .cg extension and support
067: * sequential as well as random access to multiple compressed geometry
068: * objects.
069: *
070: * @since Java 3D 1.5
071: */
072: public class CompressedGeometryFile {
073: private static final boolean print = false;
074: private static final boolean benchmark = false;
075:
076: /**
077: * The magic number which identifies the compressed geometry file type.
078: */
079: static final int MAGIC_NUMBER = 0xbaddfab4;
080:
081: /**
082: * Byte offset of the magic number from start of file.
083: */
084: static final int MAGIC_NUMBER_OFFSET = 0;
085:
086: /**
087: * Byte offset of the major version number from start of file.
088: */
089: static final int MAJOR_VERSION_OFFSET = 4;
090:
091: /**
092: * Byte offset of the minor version number from start of file.
093: */
094: static final int MINOR_VERSION_OFFSET = 8;
095:
096: /**
097: * Byte offset of the minor minor version number from start of file.
098: */
099: static final int MINOR_MINOR_VERSION_OFFSET = 12;
100:
101: /**
102: * Byte offset of the number of objects from start of file.
103: */
104: static final int OBJECT_COUNT_OFFSET = 16;
105:
106: /**
107: * Byte offset of the directory offset from start of file.
108: * This offset is long word aligned since the directory offset is a long.
109: */
110: static final int DIRECTORY_OFFSET_OFFSET = 24;
111:
112: /**
113: * File header total size in bytes.
114: */
115: static final int HEADER_SIZE = 32;
116:
117: /**
118: * Byte offset of the object size from start of individual compressed
119: * geometry block.
120: */
121: static final int OBJECT_SIZE_OFFSET = 0;
122:
123: /**
124: * Byte offset of the compressed geometry data descriptor from start of
125: * individual compressed geometry block.
126: */
127: static final int GEOM_DATA_OFFSET = 4;
128:
129: /**
130: * Bits in compressed geometry data descriptor which encode the buffer type.
131: */
132: static final int TYPE_MASK = 0x03;
133:
134: /**
135: * Bit in compressed geometry data descriptor encoding presence of normals.
136: */
137: static final int NORMAL_PRESENT_MASK = 0x04;
138:
139: /**
140: * Bit in compressed geometry data descriptor encoding presence of colors.
141: */
142: static final int COLOR_PRESENT_MASK = 0x08;
143:
144: /**
145: * Bit in compressed geometry data descriptor encoding presence of alphas.
146: */
147: static final int ALPHA_PRESENT_MASK = 0x10;
148:
149: /**
150: * Value in compressed geometry data descriptor for a point buffer type.
151: */
152: static final int TYPE_POINT = 1;
153:
154: /**
155: * Value in compressed geometry data descriptor for a line buffer type.
156: */
157: static final int TYPE_LINE = 2;
158:
159: /**
160: * Value in compressed geometry data descriptor for a triangle buffer type.
161: */
162: static final int TYPE_TRIANGLE = 3;
163:
164: /**
165: * Block header total size in bytes.
166: */
167: static final int BLOCK_HEADER_SIZE = 8;
168:
169: // The name of the compressed geometry resource file.
170: String fileName = null;
171:
172: // The major, minor, and subminor version number of the most recent
173: // compressor used to compress any of the objects in the compressed
174: // geometry resource file.
175: int majorVersionNumber;
176: int minorVersionNumber;
177: int minorMinorVersionNumber;
178:
179: // The number of objects in the compressed geometry resource file.
180: int objectCount;
181:
182: // The index of the current object in the file.
183: int objectIndex = 0;
184:
185: // The random access file associated with this instance.
186: RandomAccessFile cgFile = null;
187:
188: // The magic number identifying the file type.
189: int magicNumber;
190:
191: // These fields are set from each individual block of compressed geometry.
192: byte cgBuffer[];
193: int geomSize;
194: int geomStart;
195: int geomDataType;
196:
197: // The directory of object offsets is read from the end of the file.
198: long directory[];
199: long directoryOffset;
200:
201: // The object sizes are computed from the directory offsets. These are
202: // used to allocate a buffer large enough to hold the largest object and
203: // to determine how many consecutive objects can be read into that buffer.
204: int objectSizes[];
205: int bufferObjectStart;
206: int bufferObjectCount;
207: int bufferNextObjectCount;
208: int bufferNextObjectOffset;
209:
210: // The shared compressed geometry header object.
211: CompressedGeometryData.Header cgh;
212:
213: // Flag indicating file update.
214: boolean fileUpdate = false;
215:
216: /**
217: * Construct a new CompressedGeometryFile instance associated with the
218: * specified file. An attempt is made to open the file with read-only
219: * access; if this fails then a FileNotFoundException is thrown.
220: *
221: * @param file path to the compressed geometry resource file
222: * @exception FileNotFoundException if file doesn't exist or
223: * cannot be read
224: * @exception IllegalArgumentException if the file is not a compressed
225: * geometry resource file
226: * @exception IOException if there is a header or directory read error
227: */
228: public CompressedGeometryFile(String file) throws IOException {
229: this (file, false);
230: }
231:
232: /**
233: * Construct a new CompressedGeometryFile instance associated with the
234: * specified file.
235: *
236: * @param file path to the compressed geometry resource file
237: * @param rw if true, opens the file for read and write access or attempts
238: * to create one if it doesn't exist; if false, opens the file with
239: * read-only access
240: * @exception FileNotFoundException if file doesn't exist or
241: * access permissions disallow access
242: * @exception IllegalArgumentException if the file is not a compressed
243: * geometry resource file
244: * @exception IOException if there is a header or directory read error
245: */
246: public CompressedGeometryFile(String file, boolean rw)
247: throws IOException {
248: // Open the file and read the file header.
249: open(file, rw);
250:
251: // Copy the file name.
252: fileName = new String(file);
253:
254: // Set up the file fields.
255: initialize();
256: }
257:
258: /**
259: * Construct a new CompressedGeometryFile instance associated with a
260: * currently open RandomAccessFile.
261: *
262: * @param file currently open RandomAccessFile
263: * @exception IllegalArgumentException if the file is not a compressed
264: * geometry resource file
265: * @exception IOException if there is a header or directory read error
266: */
267: public CompressedGeometryFile(RandomAccessFile file)
268: throws IOException {
269: // Copy the file reference.
270: cgFile = file;
271:
272: // Set up the file fields.
273: initialize();
274: }
275:
276: /**
277: * Delete all compressed objects from this instance. This method may only
278: * be called after successfully creating a CompressedGeometryFile instance
279: * with read-write access, so a corrupted or otherwise invalid resource
280: * must be removed manually before it can be rewritten. The close()
281: * method must be called sometime after invoking clear() in order to write
282: * out the new directory structure.
283: *
284: * @exception IOException if clear fails
285: */
286: public void clear() throws IOException {
287: // Truncate the file.
288: cgFile.setLength(0);
289:
290: // Set up the file fields.
291: initialize();
292: }
293:
294: /**
295: * Return a string containing the file name associated with this instance
296: * or null if there is none.
297: *
298: * @return file name associated with this instance or null if there is
299: * none
300: */
301: public String getFileName() {
302: return fileName;
303: }
304:
305: /**
306: * Return the major version number of the most recent compressor used to
307: * compress any of the objects in this instance.
308: *
309: * @return major version number
310: */
311: public int getMajorVersionNumber() {
312: return majorVersionNumber;
313: }
314:
315: /**
316: * Return the minor version number of the most recent compressor used to
317: * compress any of the objects in this instance.
318: *
319: * @return minor version number
320: */
321: public int getMinorVersionNumber() {
322: return minorVersionNumber;
323: }
324:
325: /**
326: * Return the subminor version number of the most recent compressor used to
327: * compress any of the objects in this instance.
328: *
329: * @return subminor version number
330: */
331: public int getMinorMinorVersionNumber() {
332: return minorMinorVersionNumber;
333: }
334:
335: /**
336: * Return the number of compressed objects in this instance.
337: *
338: * @return number of compressed objects
339: */
340: public int getObjectCount() {
341: return objectCount;
342: }
343:
344: /**
345: * Return the current object index associated with this instance. This is
346: * the index of the object that would be returned by an immediately
347: * following call to the readNext() method. Its initial value is 0; -1
348: * is returned if the last object has been read.
349: *
350: * @return current object index, or -1 if at end
351: */
352: public int getCurrentIndex() {
353: if (objectIndex == objectCount)
354: return -1;
355: else
356: return objectIndex;
357: }
358:
359: /**
360: * Read the next compressed geometry object in the instance. This is
361: * initially the first object (index 0) in the instance; otherwise, it is
362: * whatever object is next after the last one read. The current object
363: * index is incremented by 1 after the read. When the last object is read
364: * the index becomes invalid and an immediately subsequent call to
365: * readNext() returns null.
366: *
367: *
368: * @return a CompressedGeometryData node component, or null if the last object
369: * has been read
370: * @exception IOException if read fails
371: */
372: public CompressedGeometryData readNext() throws IOException {
373: return readNext(cgBuffer.length);
374: }
375:
376: /**
377: * Read all compressed geometry objects contained in the instance. The
378: * current object index becomes invalid; an immediately following call
379: * to readNext() will return null.
380: *
381: * @return an array of CompressedGeometryData node components.
382: * @exception IOException if read fails
383: */
384: public CompressedGeometryData[] read() throws IOException {
385: long startTime = 0;
386: CompressedGeometryData cg[] = new CompressedGeometryData[objectCount];
387:
388: if (benchmark)
389: startTime = System.currentTimeMillis();
390:
391: objectIndex = 0;
392: setFilePointer(directory[0]);
393: bufferNextObjectCount = 0;
394:
395: for (int i = 0; i < objectCount; i++)
396: cg[i] = readNext(cgBuffer.length);
397:
398: if (benchmark) {
399: long t = System.currentTimeMillis() - startTime;
400: System.out.println("read " + objectCount + " objects "
401: + cgFile.length() + " bytes in " + (t / 1000f)
402: + " sec.");
403: System.out.println((cgFile.length() / (float) t)
404: + " Kbytes/sec.");
405: }
406:
407: return cg;
408: }
409:
410: /**
411: * Read the compressed geometry object at the specified index. The
412: * current object index is set to the subsequent object unless the last
413: * object has been read, in which case the index becomes invalid and an
414: * immediately following call to readNext() will return null.
415: *
416: * @param index compressed geometry object to read
417: * @return a CompressedGeometryData node component
418: * @exception IndexOutOfBoundsException if object index is
419: * out of range
420: * @exception IOException if read fails
421: */
422: public CompressedGeometryData read(int index) throws IOException {
423: objectIndex = index;
424:
425: if (objectIndex < 0) {
426: throw new IndexOutOfBoundsException(
427: "\nobject index must be >= 0");
428: }
429: if (objectIndex >= objectCount) {
430: throw new IndexOutOfBoundsException(
431: "\nobject index must be < " + objectCount);
432: }
433:
434: // Check if object is in cache.
435: if ((objectIndex >= bufferObjectStart)
436: && (objectIndex < bufferObjectStart + bufferObjectCount)) {
437: if (print)
438: System.out.println("\ngetting object from cache\n");
439:
440: bufferNextObjectOffset = (int) (directory[objectIndex] - directory[bufferObjectStart]);
441:
442: bufferNextObjectCount = bufferObjectCount
443: - (objectIndex - bufferObjectStart);
444:
445: return readNext();
446:
447: } else {
448: // Move file pointer to correct offset.
449: setFilePointer(directory[objectIndex]);
450:
451: // Force a read from current offset. Disable cache read-ahead
452: // since cache hits are unlikely with random access.
453: bufferNextObjectCount = 0;
454: return readNext(objectSizes[objectIndex]);
455: }
456: }
457:
458: /**
459: * Add a compressed geometry node component to the end of the instance.
460: * The current object index becomes invalid; an immediately following call
461: * to readNext() will return null. The close() method must be called at
462: * some later time in order to create a valid compressed geometry file.
463: *
464: * @param cg a compressed geometry node component
465: * @exception CapabilityNotSetException if unable to get compressed
466: * geometry data from the node component
467: * @exception IOException if write fails
468: */
469: public void write(CompressedGeometryData cg) throws IOException {
470: CompressedGeometryData.Header cgh = new CompressedGeometryData.Header();
471: cg.getCompressedGeometryHeader(cgh);
472:
473: // Update the read/write buffer size if necessary.
474: if (cgh.size + BLOCK_HEADER_SIZE > cgBuffer.length) {
475: cgBuffer = new byte[cgh.size + BLOCK_HEADER_SIZE];
476: if (print)
477: System.out.println("\ncgBuffer: reallocated "
478: + (cgh.size + BLOCK_HEADER_SIZE) + " bytes");
479: }
480:
481: cg.getCompressedGeometry(cgBuffer);
482: write(cgh, cgBuffer);
483: }
484:
485: /**
486: * Add a buffer of compressed geometry data to the end of the
487: * resource. The current object index becomes invalid; an immediately
488: * following call to readNext() will return null. The close() method must
489: * be called at some later time in order to create a valid compressed
490: * geometry file.
491: *
492: * @param cgh a CompressedGeometryData.Header object describing the data.
493: * @param geometry the compressed geometry data
494: * @exception IOException if write fails
495: */
496: public void write(CompressedGeometryData.Header cgh,
497: byte geometry[]) throws IOException {
498:
499: // Update the read/write buffer size if necessary. It won't be used
500: // in this method, but should be big enough to read any object in
501: // the file, including the one to be written.
502: if (cgh.size + BLOCK_HEADER_SIZE > cgBuffer.length) {
503: cgBuffer = new byte[cgh.size + BLOCK_HEADER_SIZE];
504: if (print)
505: System.out.println("\ncgBuffer: reallocated "
506: + (cgh.size + BLOCK_HEADER_SIZE) + " bytes");
507: }
508:
509: // Assuming backward compatibility, the version number of the file
510: // should be the maximum of all individual compressed object versions.
511: if ((cgh.majorVersionNumber > majorVersionNumber)
512: || ((cgh.majorVersionNumber == majorVersionNumber) && (cgh.minorVersionNumber > minorVersionNumber))
513: || ((cgh.majorVersionNumber == majorVersionNumber)
514: && (cgh.minorVersionNumber == minorVersionNumber) && (cgh.minorMinorVersionNumber > minorMinorVersionNumber))) {
515:
516: majorVersionNumber = cgh.majorVersionNumber;
517: minorVersionNumber = cgh.minorVersionNumber;
518: minorMinorVersionNumber = cgh.minorMinorVersionNumber;
519:
520: this .cgh.majorVersionNumber = cgh.majorVersionNumber;
521: this .cgh.minorVersionNumber = cgh.minorVersionNumber;
522: this .cgh.minorMinorVersionNumber = cgh.minorMinorVersionNumber;
523: }
524:
525: // Get the buffer type and see what vertex components are present.
526: int geomDataType = 0;
527:
528: switch (cgh.bufferType) {
529: case CompressedGeometryData.Header.POINT_BUFFER:
530: geomDataType = TYPE_POINT;
531: break;
532: case CompressedGeometryData.Header.LINE_BUFFER:
533: geomDataType = TYPE_LINE;
534: break;
535: case CompressedGeometryData.Header.TRIANGLE_BUFFER:
536: geomDataType = TYPE_TRIANGLE;
537: break;
538: }
539:
540: if ((cgh.bufferDataPresent & CompressedGeometryData.Header.NORMAL_IN_BUFFER) != 0)
541: geomDataType |= NORMAL_PRESENT_MASK;
542:
543: if ((cgh.bufferDataPresent & CompressedGeometryData.Header.COLOR_IN_BUFFER) != 0)
544: geomDataType |= COLOR_PRESENT_MASK;
545:
546: if ((cgh.bufferDataPresent & CompressedGeometryData.Header.ALPHA_IN_BUFFER) != 0)
547: geomDataType |= ALPHA_PRESENT_MASK;
548:
549: // Allocate new directory and object size arrays if necessary.
550: if (objectCount == directory.length) {
551: long newDirectory[] = new long[2 * objectCount];
552: int newObjectSizes[] = new int[2 * objectCount];
553:
554: System
555: .arraycopy(directory, 0, newDirectory, 0,
556: objectCount);
557: System.arraycopy(objectSizes, 0, newObjectSizes, 0,
558: objectCount);
559:
560: directory = newDirectory;
561: objectSizes = newObjectSizes;
562:
563: if (print)
564: System.out
565: .println("\ndirectory and size arrays: reallocated "
566: + (2 * objectCount) + " entries");
567: }
568:
569: // Update directory and object size array.
570: directory[objectCount] = directoryOffset;
571: objectSizes[objectCount] = cgh.size + BLOCK_HEADER_SIZE;
572: objectCount++;
573:
574: // Seek to the directory and overwrite from there.
575: setFilePointer(directoryOffset);
576: cgFile.writeInt(cgh.size);
577: cgFile.writeInt(geomDataType);
578: cgFile.write(geometry, 0, cgh.size);
579: if (print)
580: System.out.println("\nwrote " + cgh.size
581: + " byte compressed object to " + fileName
582: + "\nfile offset " + directoryOffset);
583:
584: // Update the directory offset.
585: directoryOffset += cgh.size + BLOCK_HEADER_SIZE;
586:
587: // Return end-of-file on next read.
588: objectIndex = objectCount;
589:
590: // Flag file update so close() will write out the directory.
591: fileUpdate = true;
592: }
593:
594: /**
595: * Release the resources associated with this instance.
596: * Write out final header and directory if contents were updated.
597: * This method must be called in order to create a valid compressed
598: * geometry resource file if any updates were made.
599: */
600: public void close() {
601: if (cgFile != null) {
602: try {
603: if (fileUpdate) {
604: writeFileDirectory();
605: writeFileHeader();
606: }
607: cgFile.close();
608: } catch (IOException e) {
609: // Don't propagate this exception.
610: System.out.println("\nException: " + e.getMessage());
611: System.out.println("failed to close " + fileName);
612: }
613: }
614: cgFile = null;
615: cgBuffer = null;
616: directory = null;
617: objectSizes = null;
618: }
619:
620: //
621: // Open the file. Specifying a non-existent file creates a new one if
622: // access permissions allow.
623: //
624: void open(String fname, boolean rw) throws FileNotFoundException,
625: IOException {
626:
627: cgFile = null;
628: String mode;
629:
630: if (rw)
631: mode = "rw";
632: else
633: mode = "r";
634:
635: try {
636: cgFile = new RandomAccessFile(fname, mode);
637: if (print)
638: System.out.println("\n" + fname + ": opened mode "
639: + mode);
640: } catch (FileNotFoundException e) {
641: // N.B. this exception is also thrown on access permission errors
642: throw new FileNotFoundException(e.getMessage() + "\n"
643: + fname + ": open mode " + mode + " failed");
644: }
645: }
646:
647: //
648: // Seek to the specified offset in the file.
649: //
650: void setFilePointer(long offset) throws IOException {
651: cgFile.seek(offset);
652:
653: // Reset number of objects that can be read sequentially from cache.
654: bufferNextObjectCount = 0;
655: }
656:
657: //
658: // Initialize directory, object size array, read/write buffer, and the
659: // shared compressed geometry header.
660: //
661: void initialize() throws IOException {
662: int maxSize = 0;
663:
664: if (cgFile.length() == 0) {
665: // New file for writing: allocate nominal initial sizes for arrays.
666: objectCount = 0;
667: cgBuffer = new byte[32768];
668: directory = new long[16];
669: objectSizes = new int[directory.length];
670:
671: // Set fields as if they have been read.
672: magicNumber = MAGIC_NUMBER;
673: majorVersionNumber = 1;
674: minorVersionNumber = 0;
675: minorMinorVersionNumber = 0;
676: directoryOffset = HEADER_SIZE;
677:
678: // Write the file header.
679: writeFileHeader();
680:
681: } else {
682: // Read the file header.
683: readFileHeader();
684:
685: // Check file type.
686: if (magicNumber != MAGIC_NUMBER) {
687: close();
688: throw new IllegalArgumentException("\n" + fileName
689: + " is not a compressed geometry file");
690: }
691:
692: // Read the directory and determine object sizes.
693: directory = new long[objectCount];
694: readDirectory(directoryOffset, directory);
695:
696: objectSizes = new int[objectCount];
697: for (int i = 0; i < objectCount - 1; i++) {
698: objectSizes[i] = (int) (directory[i + 1] - directory[i]);
699: if (objectSizes[i] > maxSize)
700: maxSize = objectSizes[i];
701: }
702:
703: if (objectCount > 0) {
704: objectSizes[objectCount - 1] = (int) (directoryOffset - directory[objectCount - 1]);
705:
706: if (objectSizes[objectCount - 1] > maxSize)
707: maxSize = objectSizes[objectCount - 1];
708: }
709:
710: // Allocate a buffer big enough to read the largest object.
711: cgBuffer = new byte[maxSize];
712:
713: // Move to the first object.
714: setFilePointer(HEADER_SIZE);
715: }
716:
717: // Set up common parts of the compressed geometry object header.
718: cgh = new CompressedGeometryData.Header();
719: cgh.majorVersionNumber = this .majorVersionNumber;
720: cgh.minorVersionNumber = this .minorVersionNumber;
721: cgh.minorMinorVersionNumber = this .minorMinorVersionNumber;
722:
723: if (print) {
724: System.out.println(fileName + ": " + objectCount
725: + " objects");
726: System.out.println("magic number 0x"
727: + Integer.toHexString(magicNumber)
728: + ", version number " + majorVersionNumber + "."
729: + minorVersionNumber + "."
730: + minorMinorVersionNumber);
731: System.out.println("largest object is " + maxSize
732: + " bytes");
733: }
734: }
735:
736: //
737: // Read the file header.
738: //
739: void readFileHeader() throws IOException {
740: byte header[] = new byte[HEADER_SIZE];
741:
742: try {
743: setFilePointer(0);
744: if (cgFile.read(header) != HEADER_SIZE) {
745: close();
746: throw new IOException("failed header read");
747: }
748: } catch (IOException e) {
749: if (cgFile != null) {
750: close();
751: }
752: throw e;
753: }
754:
755: magicNumber = ((header[MAGIC_NUMBER_OFFSET + 0] & 0xff) << 24)
756: | ((header[MAGIC_NUMBER_OFFSET + 1] & 0xff) << 16)
757: | ((header[MAGIC_NUMBER_OFFSET + 2] & 0xff) << 8)
758: | ((header[MAGIC_NUMBER_OFFSET + 3] & 0xff));
759:
760: majorVersionNumber = ((header[MAJOR_VERSION_OFFSET + 0] & 0xff) << 24)
761: | ((header[MAJOR_VERSION_OFFSET + 1] & 0xff) << 16)
762: | ((header[MAJOR_VERSION_OFFSET + 2] & 0xff) << 8)
763: | ((header[MAJOR_VERSION_OFFSET + 3] & 0xff));
764:
765: minorVersionNumber = ((header[MINOR_VERSION_OFFSET + 0] & 0xff) << 24)
766: | ((header[MINOR_VERSION_OFFSET + 1] & 0xff) << 16)
767: | ((header[MINOR_VERSION_OFFSET + 2] & 0xff) << 8)
768: | ((header[MINOR_VERSION_OFFSET + 3] & 0xff));
769:
770: minorMinorVersionNumber = ((header[MINOR_MINOR_VERSION_OFFSET + 0] & 0xff) << 24)
771: | ((header[MINOR_MINOR_VERSION_OFFSET + 1] & 0xff) << 16)
772: | ((header[MINOR_MINOR_VERSION_OFFSET + 2] & 0xff) << 8)
773: | ((header[MINOR_MINOR_VERSION_OFFSET + 3] & 0xff));
774:
775: objectCount = ((header[OBJECT_COUNT_OFFSET + 0] & 0xff) << 24)
776: | ((header[OBJECT_COUNT_OFFSET + 1] & 0xff) << 16)
777: | ((header[OBJECT_COUNT_OFFSET + 2] & 0xff) << 8)
778: | ((header[OBJECT_COUNT_OFFSET + 3] & 0xff));
779:
780: directoryOffset = ((long) (header[DIRECTORY_OFFSET_OFFSET + 0] & 0xff) << 56)
781: | ((long) (header[DIRECTORY_OFFSET_OFFSET + 1] & 0xff) << 48)
782: | ((long) (header[DIRECTORY_OFFSET_OFFSET + 2] & 0xff) << 40)
783: | ((long) (header[DIRECTORY_OFFSET_OFFSET + 3] & 0xff) << 32)
784: | ((long) (header[DIRECTORY_OFFSET_OFFSET + 4] & 0xff) << 24)
785: | ((long) (header[DIRECTORY_OFFSET_OFFSET + 5] & 0xff) << 16)
786: | ((long) (header[DIRECTORY_OFFSET_OFFSET + 6] & 0xff) << 8)
787: | ((long) (header[DIRECTORY_OFFSET_OFFSET + 7] & 0xff));
788: }
789:
790: //
791: // Write the file header based on current field values.
792: //
793: void writeFileHeader() throws IOException {
794: setFilePointer(0);
795: try {
796: cgFile.writeInt(MAGIC_NUMBER);
797: cgFile.writeInt(majorVersionNumber);
798: cgFile.writeInt(minorVersionNumber);
799: cgFile.writeInt(minorMinorVersionNumber);
800: cgFile.writeInt(objectCount);
801: cgFile.writeInt(0); // long word alignment
802: cgFile.writeLong(directoryOffset);
803: if (print)
804: System.out.println("wrote file header for " + fileName);
805: } catch (IOException e) {
806: throw new IOException(e.getMessage()
807: + "\ncould not write file header for " + fileName);
808: }
809: }
810:
811: //
812: // Read the directory of compressed geometry object offsets.
813: //
814: void readDirectory(long offset, long[] directory)
815: throws IOException {
816:
817: byte buff[] = new byte[directory.length * 8];
818: setFilePointer(offset);
819:
820: try {
821: cgFile.read(buff);
822: if (print)
823: System.out.println("read " + buff.length
824: + " byte directory");
825: } catch (IOException e) {
826: throw new IOException(e.getMessage() + "\nfailed to read "
827: + buff.length + " byte directory, offset " + offset
828: + " in file " + fileName);
829: }
830:
831: for (int i = 0; i < directory.length; i++) {
832: directory[i] = ((long) (buff[i * 8 + 0] & 0xff) << 56)
833: | ((long) (buff[i * 8 + 1] & 0xff) << 48)
834: | ((long) (buff[i * 8 + 2] & 0xff) << 40)
835: | ((long) (buff[i * 8 + 3] & 0xff) << 32)
836: | ((long) (buff[i * 8 + 4] & 0xff) << 24)
837: | ((long) (buff[i * 8 + 5] & 0xff) << 16)
838: | ((long) (buff[i * 8 + 6] & 0xff) << 8)
839: | ((long) (buff[i * 8 + 7] & 0xff));
840: }
841: }
842:
843: //
844: // Write the file directory.
845: //
846: void writeFileDirectory() throws IOException {
847: setFilePointer(directoryOffset);
848:
849: int directoryAlign = (int) (directoryOffset % 8);
850: if (directoryAlign != 0) {
851: // Align to long word before writing directory of long offsets.
852: byte bytes[] = new byte[8 - directoryAlign];
853:
854: try {
855: cgFile.write(bytes);
856: if (print)
857: System.out.println("wrote " + (8 - directoryAlign)
858: + " bytes long alignment");
859: } catch (IOException e) {
860: throw new IOException(e.getMessage()
861: + "\ncould not write " + directoryAlign
862: + " bytes to long word align directory for "
863: + fileName);
864: }
865: directoryOffset += 8 - directoryAlign;
866: }
867:
868: try {
869: for (int i = 0; i < objectCount; i++)
870: cgFile.writeLong(directory[i]);
871:
872: if (print)
873: System.out.println("wrote file directory for "
874: + fileName);
875: } catch (IOException e) {
876: throw new IOException(e.getMessage()
877: + "\ncould not write directory for " + fileName);
878: }
879: }
880:
881: //
882: // Get the next compressed object in the file, either from the read-ahead
883: // cache or from the file itself.
884: //
885: CompressedGeometryData readNext(int bufferReadLimit)
886: throws IOException {
887: if (objectIndex == objectCount)
888: return null;
889:
890: if (bufferNextObjectCount == 0) {
891: // No valid objects are in the cache.
892: int curSize = 0;
893: bufferObjectCount = 0;
894:
895: // See how much we have room to read.
896: for (int i = objectIndex; i < objectCount; i++) {
897: if (curSize + objectSizes[i] > bufferReadLimit)
898: break;
899: curSize += objectSizes[i];
900: bufferObjectCount++;
901: }
902:
903: // Try to read that amount.
904: try {
905: int n = cgFile.read(cgBuffer, 0, curSize);
906: if (print)
907: System.out.println("\nread " + n + " bytes from "
908: + fileName);
909: } catch (IOException e) {
910: throw new IOException(e.getMessage()
911: + "\nfailed to read " + curSize
912: + " bytes, object " + objectIndex + " in file "
913: + fileName);
914: }
915:
916: // Point at the first object in the buffer.
917: bufferObjectStart = objectIndex;
918: bufferNextObjectCount = bufferObjectCount;
919: bufferNextObjectOffset = 0;
920: }
921:
922: // Get block header info.
923: geomSize = ((cgBuffer[bufferNextObjectOffset
924: + OBJECT_SIZE_OFFSET + 0] & 0xff) << 24)
925: | ((cgBuffer[bufferNextObjectOffset
926: + OBJECT_SIZE_OFFSET + 1] & 0xff) << 16)
927: | ((cgBuffer[bufferNextObjectOffset
928: + OBJECT_SIZE_OFFSET + 2] & 0xff) << 8)
929: | ((cgBuffer[bufferNextObjectOffset
930: + OBJECT_SIZE_OFFSET + 3] & 0xff));
931:
932: geomDataType = ((cgBuffer[bufferNextObjectOffset
933: + GEOM_DATA_OFFSET + 0] & 0xff) << 24)
934: | ((cgBuffer[bufferNextObjectOffset + GEOM_DATA_OFFSET
935: + 1] & 0xff) << 16)
936: | ((cgBuffer[bufferNextObjectOffset + GEOM_DATA_OFFSET
937: + 2] & 0xff) << 8)
938: | ((cgBuffer[bufferNextObjectOffset + GEOM_DATA_OFFSET
939: + 3] & 0xff));
940:
941: // Get offset of compressed geometry data from start of buffer.
942: geomStart = bufferNextObjectOffset + BLOCK_HEADER_SIZE;
943:
944: if (print) {
945: System.out.println("\nobject " + objectIndex
946: + "\nfile offset " + directory[objectIndex]
947: + ", buffer offset " + bufferNextObjectOffset);
948: System.out.println("size " + geomSize + " bytes, "
949: + "data descriptor 0x"
950: + Integer.toHexString(geomDataType));
951: }
952:
953: // Update cache info.
954: bufferNextObjectOffset += objectSizes[objectIndex];
955: bufferNextObjectCount--;
956: objectIndex++;
957:
958: return newCG(geomSize, geomStart, geomDataType);
959: }
960:
961: //
962: // Construct and return a compressed geometry node.
963: //
964: CompressedGeometryData newCG(int geomSize, int geomStart,
965: int geomDataType) {
966: cgh.size = geomSize;
967: cgh.start = geomStart;
968:
969: if ((geomDataType & TYPE_MASK) == TYPE_POINT)
970: cgh.bufferType = CompressedGeometryData.Header.POINT_BUFFER;
971: else if ((geomDataType & TYPE_MASK) == TYPE_LINE)
972: cgh.bufferType = CompressedGeometryData.Header.LINE_BUFFER;
973: else if ((geomDataType & TYPE_MASK) == TYPE_TRIANGLE)
974: cgh.bufferType = CompressedGeometryData.Header.TRIANGLE_BUFFER;
975:
976: cgh.bufferDataPresent = 0;
977:
978: if ((geomDataType & NORMAL_PRESENT_MASK) != 0)
979: cgh.bufferDataPresent |= CompressedGeometryData.Header.NORMAL_IN_BUFFER;
980:
981: if ((geomDataType & COLOR_PRESENT_MASK) != 0)
982: cgh.bufferDataPresent |= CompressedGeometryData.Header.COLOR_IN_BUFFER;
983:
984: if ((geomDataType & ALPHA_PRESENT_MASK) != 0)
985: cgh.bufferDataPresent |= CompressedGeometryData.Header.ALPHA_IN_BUFFER;
986:
987: return new CompressedGeometryData(cgh, cgBuffer);
988: }
989:
990: /**
991: * Release file resources when this object is garbage collected.
992: */
993: protected void finalize() {
994: close();
995: }
996: }
|