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