Source code: org/ydp/jai/StructuredStorage.java
1 /*
2 * The contents of this file are subject to the JAVA ADVANCED IMAGING
3 * SAMPLE INPUT-OUTPUT CODECS AND WIDGET HANDLING SOURCE CODE License
4 * Version 1.0 (the "License"); You may not use this file except in
5 * compliance with the License. You may obtain a copy of the License at
6 * http://www.sun.com/software/imaging/JAI/index.html
7 *
8 * Software distributed under the License is distributed on an "AS IS"
9 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
10 * the License for the specific language governing rights and limitations
11 * under the License.
12 *
13 * The Original Code is JAVA ADVANCED IMAGING SAMPLE INPUT-OUTPUT CODECS
14 * AND WIDGET HANDLING SOURCE CODE.
15 * The Initial Developer of the Original Code is: Sun Microsystems, Inc..
16 * Portions created by: _______________________________________
17 * are Copyright (C): _______________________________________
18 * All Rights Reserved.
19 * Contributor(s): _______________________________________
20 */
21
22 package org.ydp.jai;
23
24 import java.io.ByteArrayInputStream;
25 import java.io.InputStream;
26 import java.io.IOException;
27 import java.io.RandomAccessFile;
28 import java.util.StringTokenizer;
29
30 //
31 // NOTE -- all 'long' variables are really at most 32 bits,
32 // corresponding to Microsoft 'ULONG' variables.
33 //
34
35 // Temporary assumptions:
36 //
37 // All streams, including the ministream, are shorter than 2GB (size < 2GB)
38 //
39 // There are < 2^31 directory entries (#streams < 2^31)
40 //
41
42 class SSDirectoryEntry {
43
44 int index;
45 String name;
46 long size;
47 long startSector;
48 long SIDLeftSibling;
49 long SIDRightSibling;
50 long SIDChild;
51
52 public SSDirectoryEntry(int index,
53 String name,
54 long size,
55 long startSector,
56 long SIDLeftSibling,
57 long SIDRightSibling,
58 long SIDChild) {
59 this.name = name;
60 this.index = index;
61 this.size = size;
62 this.startSector = startSector;
63 this.SIDLeftSibling = SIDLeftSibling;
64 this.SIDRightSibling = SIDRightSibling;
65 this.SIDChild = SIDChild;
66
67 // System.out.println("Got a directory entry named " + name +
68 // " (index " + index + ")");
69 // System.out.println("Start sector = " + startSector);
70 }
71
72 public String getName() {
73 return name;
74 }
75
76 public long getSize() {
77 return size;
78 }
79
80 public long getStartSector() {
81 return startSector;
82 }
83
84 public long getSIDLeftSibling() {
85 return SIDLeftSibling;
86 }
87
88 public long getSIDRightSibling() {
89 return SIDRightSibling;
90 }
91
92 public long getSIDChild() {
93 return SIDChild;
94 }
95 }
96
97 public class StructuredStorage {
98
99 SeekableStream file;
100
101 // Header fields
102 private int sectorShift; // ULONG -- must be between 1 and 31
103 private int miniSectorShift;
104 private long csectFat;
105 private long sectDirStart;
106 private long miniSectorCutoff;
107 private long sectMiniFatStart;
108 private long csectMiniFat;
109 private long sectDifStart;
110 private long csectDif;
111 private long[] sectFat;
112
113 // FAT, MiniFAT, and ministream in unrolled format
114 // private long[] FAT; // ULONG -- only 2G entries max
115 private long[] MINIFAT; // ULONG -- only 2G entries max
116 private SSDirectoryEntry[] DIR;
117
118 private SeekableStream miniStream;
119 private SeekableStream FATStream;
120
121 // The index of the current directory
122 long cwdIndex = -1L;
123
124 public StructuredStorage(SeekableStream file) throws IOException {
125 this.file = file;
126
127 // Read fields from the header
128 getHeader();
129
130 // Read the FAT
131 getFat();
132
133 // Read the MiniFAT
134 getMiniFat();
135
136 // Read the directory
137 getDirectory();
138
139 // Read the MiniStream
140 getMiniStream();
141 }
142
143 private void getHeader() throws IOException {
144 file.seek(0x1e);
145 this.sectorShift = file.readUnsignedShortLE();
146
147 file.seek(0x20);
148 this.miniSectorShift = file.readUnsignedShortLE();
149
150 file.seek(0x2c);
151 this.csectFat = file.readUnsignedIntLE();
152
153 file.seek(0x30);
154 this.sectDirStart = file.readUnsignedIntLE();
155
156 file.seek(0x38);
157 this.miniSectorCutoff = file.readUnsignedIntLE();
158
159 file.seek(0x3c);
160 this.sectMiniFatStart = file.readUnsignedIntLE();
161
162 file.seek(0x40);
163 this.csectMiniFat = file.readUnsignedIntLE();
164
165 file.seek(0x44);
166 this.sectDifStart = file.readUnsignedIntLE();
167
168 file.seek(0x48);
169 this.csectDif = file.readUnsignedIntLE();
170
171 this.sectFat = new long[109];
172 file.seek(0x4c);
173 for (int i = 0; i < 109; i++) {
174 this.sectFat[i] = file.readUnsignedIntLE();
175 }
176 }
177
178 private void getFat() throws IOException {
179 int size = getSectorSize();
180 int sectsPerFat = size/4;
181 int fatsPerDif = size/4 - 1;
182
183 int numFATSectors = (int)(csectFat + csectDif*fatsPerDif);
184 long[] FATSectors = new long[numFATSectors];
185 int count = 0;
186
187 for (int i = 0; i < 109; i++) {
188 long sector = sectFat[i];
189 if (sector == 0xFFFFFFFFL) {
190 break;
191 }
192
193 FATSectors[count++] = getOffsetOfSector(sectFat[i]);
194 }
195
196 if (csectDif > 0) {
197 long dif = sectDifStart;
198 byte[] difBuf = new byte[size];
199
200 for (int i = 0; i < csectDif; i++) {
201 readSector(dif, difBuf, 0);
202 for (int j = 0; j < fatsPerDif; j++) {
203 int sec = FPXUtils.getIntLE(difBuf, 4*j);
204 FATSectors[count++] = getOffsetOfSector(sec);
205 }
206
207 dif = FPXUtils.getIntLE(difBuf, size - 4);
208 }
209 }
210
211 FATStream = new SegmentedSeekableStream(file,
212 FATSectors,
213 size,
214 numFATSectors*size,
215 true);
216 }
217
218 private void getMiniFat() throws IOException {
219 int size = getSectorSize();
220 int sectsPerFat = size/4;
221 int index = 0;
222
223 this.MINIFAT = new long[(int)(csectMiniFat*sectsPerFat)];
224
225 long sector = sectMiniFatStart;
226 byte[] buf = new byte[size];
227 while (sector != 0xFFFFFFFEL) {
228 readSector(sector, buf, 0);
229 for (int j = 0; j < sectsPerFat; j++) {
230 MINIFAT[index++] = FPXUtils.getIntLE(buf, 4*j);
231 }
232 sector = getFATSector(sector);
233 }
234 }
235
236 private void getDirectory() throws IOException {
237 int size = getSectorSize();
238 long sector = sectDirStart;
239
240 // Count the length of the directory in sectors
241 int numDirectorySectors = 0;
242 while (sector != 0xFFFFFFFEL) {
243 sector = getFATSector(sector);
244 ++numDirectorySectors;
245 }
246
247 int directoryEntries = 4*numDirectorySectors;
248 this.DIR = new SSDirectoryEntry[directoryEntries];
249
250 sector = sectDirStart;
251 byte[] buf = new byte[size];
252 int index = 0;
253 while (sector != 0xFFFFFFFEL) {
254 readSector(sector, buf, 0);
255
256 int offset = 0;
257 for (int i = 0; i < 4; i++) { // 4 dirents per sector
258 // We divide the length by 2 for now even though
259 // the spec says not to...
260 int length = FPXUtils.getShortLE(buf, offset + 0x40);
261
262 String name = FPXUtils.getString(buf, offset + 0x00, length);
263 long SIDLeftSibling =
264 FPXUtils.getUnsignedIntLE(buf, offset + 0x44);
265 long SIDRightSibling =
266 FPXUtils.getUnsignedIntLE(buf, offset + 0x48);
267 long SIDChild = FPXUtils.getUnsignedIntLE(buf, offset + 0x4c);
268 long startSector =
269 FPXUtils.getUnsignedIntLE(buf, offset + 0x74);
270 long streamSize = FPXUtils.getUnsignedIntLE(buf, offset + 0x78);
271
272 DIR[index] = new SSDirectoryEntry(index,
273 name,
274 streamSize,
275 startSector,
276 SIDLeftSibling,
277 SIDRightSibling,
278 SIDChild);
279 ++index;
280 offset += 128;
281 }
282
283 sector = getFATSector(sector);
284 }
285 }
286
287 private void getMiniStream() throws IOException {
288 int length = getLength(0L);
289 int sectorSize = getSectorSize();
290 int sectors = (int)((length + sectorSize - 1)/sectorSize);
291
292 long[] segmentPositions = new long[sectors];
293
294 long sector = getStartSector(0);
295
296 for (int i = 0; i < sectors - 1; i++) {
297 segmentPositions[i] = getOffsetOfSector(sector);
298 sector = getFATSector(sector);
299 }
300 segmentPositions[sectors - 1] = getOffsetOfSector(sector);
301
302 miniStream = new SegmentedSeekableStream(file,
303 segmentPositions,
304 sectorSize,
305 length,
306 true);
307 }
308
309 private int getSectorSize() {
310 return 1 << sectorShift;
311 }
312
313 private long getOffsetOfSector(long sector) {
314 return sector*getSectorSize() + 512;
315 }
316
317 private int getMiniSectorSize() {
318 return 1 << miniSectorShift;
319 }
320
321 private long getOffsetOfMiniSector(long sector) {
322 return sector*getMiniSectorSize();
323 }
324
325 private void readMiniSector(long sector, byte[] buf,
326 int offset, int length)
327 throws IOException {
328 miniStream.seek(getOffsetOfMiniSector(sector));
329 miniStream.read(buf, offset, length);
330 }
331
332 private void readMiniSector(long sector, byte[] buf, int offset)
333 throws IOException {
334 readMiniSector(sector, buf, offset, getMiniSectorSize());
335 }
336
337 private void readSector(long sector, byte[] buf, int offset, int length)
338 throws IOException {
339 file.seek(getOffsetOfSector(sector));
340 file.read(buf, offset, length);
341 }
342
343 private void readSector(long sector, byte[] buf, int offset)
344 throws IOException {
345 readSector(sector, buf, offset, getSectorSize());
346 }
347
348 private SSDirectoryEntry getDirectoryEntry(long index) {
349 // Assume #streams < 2^31
350 return DIR[(int)index];
351 }
352
353 private long getStartSector(long index) {
354 // Assume #streams < 2^31
355 return DIR[(int)index].getStartSector();
356 }
357
358 private int getLength(long index) {
359 // Assume #streams < 2^31
360 // Assume size < 2GB
361 return (int)DIR[(int)index].getSize();
362 }
363
364 private long getFATSector(long sector) throws IOException {
365 FATStream.seek(4*sector);
366 return FATStream.readUnsignedIntLE();
367 }
368
369 private long getMiniFATSector(long sector) {
370 return MINIFAT[(int)sector];
371 }
372
373 private int getCurrentIndex() {
374 return -1;
375 }
376
377 private int getIndex(String name, int index) {
378 return -1;
379 }
380
381 private long searchDirectory(String name, long index) {
382 if (index == 0xFFFFFFFFL) {
383 return -1L;
384 }
385
386 SSDirectoryEntry dirent = getDirectoryEntry(index);
387
388 if (name.equals(dirent.getName())) {
389 return index;
390 } else {
391 long lindex =
392 searchDirectory(name, dirent.getSIDLeftSibling());
393 if (lindex != -1L) {
394 return lindex;
395 }
396
397 long rindex =
398 searchDirectory(name, dirent.getSIDRightSibling());
399 if (rindex != -1L) {
400 return rindex;
401 }
402 }
403
404 return -1L;
405 }
406
407 // Public methods
408
409 public void changeDirectoryToRoot() {
410 cwdIndex = getDirectoryEntry(0L).getSIDChild();
411 }
412
413 public boolean changeDirectory(String name) {
414 long index = searchDirectory(name, cwdIndex);
415 if (index != -1L) {
416 cwdIndex = getDirectoryEntry(index).getSIDChild();
417 return true;
418 } else {
419 return false;
420 }
421 }
422
423 private long getStreamIndex(String name) {
424 // Move down the directory hierarchy
425 long index = cwdIndex;
426
427 StringTokenizer st = new StringTokenizer(name, "/");
428 boolean firstTime = true;
429 while (st.hasMoreTokens()) {
430 String tok = st.nextToken();
431
432 if (!firstTime) {
433 index = getDirectoryEntry(index).getSIDChild();
434 } else {
435 firstTime = false;
436 }
437 index = searchDirectory(tok, index);
438 }
439
440 return index;
441 }
442
443 public byte[] getStreamAsBytes(String name) throws IOException {
444 long index = getStreamIndex(name);
445 if (index == -1L) {
446 return null;
447 }
448
449 // Cast index to int (streams < 2^31) and cast stream size to an
450 // int (size < 2GB)
451 int length = getLength(index);
452 byte[] buf = new byte[length];
453
454 if (length > miniSectorCutoff) {
455 int sectorSize = getSectorSize();
456 int sectors = (int)((length + sectorSize - 1)/sectorSize);
457
458 long sector = getStartSector(index);
459 int offset = 0;
460 for (int i = 0; i < sectors - 1; i++) {
461 readSector(sector, buf, offset, sectorSize);
462 offset += sectorSize;
463 sector = getFATSector(sector);
464 }
465
466 readSector(sector, buf, offset, length - offset);
467 } else {
468 int sectorSize = getMiniSectorSize();
469 int sectors = (int)((length + sectorSize - 1)/sectorSize);
470
471 long sector = getStartSector(index);
472
473 // Assume ministream size < 2GB
474 int offset = 0;
475 for (int i = 0; i < sectors - 1; i++) {
476 long miniSectorOffset = getOffsetOfMiniSector(sector);
477 readMiniSector(sector, buf, offset, sectorSize);
478 offset += sectorSize;
479 sector = getMiniFATSector(sector);
480 }
481 readMiniSector(sector, buf, offset, length - offset);
482 }
483
484 return buf;
485 }
486
487 public SeekableStream getStream(String name) throws IOException {
488 long index = getStreamIndex(name);
489 if (index == -1L) {
490 return null;
491 }
492
493 // Cast index to int (streams < 2^31) and cast stream size to an
494 // int (size < 2GB)
495 int length = getLength(index);
496
497 long[] segmentPositions;
498 int sectorSize, sectors;
499
500 if (length > miniSectorCutoff) {
501 sectorSize = getSectorSize();
502 sectors = (int)((length + sectorSize - 1)/sectorSize);
503 segmentPositions = new long[sectors];
504
505 long sector = getStartSector(index);
506 for (int i = 0; i < sectors - 1; i++) {
507 segmentPositions[i] = getOffsetOfSector(sector);
508 sector = getFATSector(sector);
509 }
510 segmentPositions[sectors - 1] = getOffsetOfSector(sector);
511
512 return new SegmentedSeekableStream(file,
513 segmentPositions,
514 sectorSize,
515 length,
516 true);
517 } else {
518 sectorSize = getMiniSectorSize();
519 sectors = (int)((length + sectorSize - 1)/sectorSize);
520 segmentPositions = new long[sectors];
521
522 long sector = getStartSector(index);
523 for (int i = 0; i < sectors - 1; i++) {
524 segmentPositions[i] = getOffsetOfMiniSector(sector);
525 sector = getMiniFATSector(sector);
526 }
527 segmentPositions[sectors - 1] = getOffsetOfMiniSector(sector);
528
529 return new SegmentedSeekableStream(miniStream,
530 segmentPositions,
531 sectorSize,
532 length,
533 true);
534 }
535 }
536
537 public static void main(String[] args) {
538 try {
539 RandomAccessFile f = new RandomAccessFile(args[0], "r");
540 SeekableStream sis = new FileSeekableStream(f);
541 StructuredStorage ss = new StructuredStorage(sis);
542
543 ss.changeDirectoryToRoot();
544
545 byte[] s = ss.getStreamAsBytes("SummaryInformation");
546
547 PropertySet ps = new PropertySet(new ByteArraySeekableStream(s));
548
549 // Get the thumbnail property
550 byte[] thumb = ps.getBlob(17);
551
552 // Emit it as a BMP file
553 System.out.print("BM");
554 int fs = (thumb.length - 8) + 14 + 40;
555 System.out.print((char)(fs & 0xff));
556 System.out.print((char)((fs >> 8) & 0xff));
557 System.out.print((char)((fs >> 16) & 0xff));
558 System.out.print((char)((fs >> 24) & 0xff));
559 System.out.print((char)0);
560 System.out.print((char)0);
561 System.out.print((char)0);
562 System.out.print((char)0);
563 System.out.print('6');
564 System.out.print((char)0);
565 System.out.print((char)0);
566 System.out.print((char)0);
567 for (int i = 8; i < thumb.length; i++) {
568 System.out.print((char)(thumb[i] & 0xff));
569 }
570
571 } catch (Exception e) {
572 e.printStackTrace();
573 }
574 }
575 }