KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > mckoi > database > FixedSizeDataStore


1 /**
2  * com.mckoi.database.FixedSizeDataStore 25 Jun 2000
3  *
4  * Mckoi SQL Database ( http://www.mckoi.com/database )
5  * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * Version 2 as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License Version 2 for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * Version 2 along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  *
20  * Change Log:
21  *
22  *
23  */

24
25 package com.mckoi.database;
26
27 import java.io.*;
28 import java.util.Arrays JavaDoc;
29 import com.mckoi.util.ByteArrayUtil;
30 import com.mckoi.util.IntegerVector;
31 import com.mckoi.util.UserTerminal;
32 import com.mckoi.util.Cache;
33 import com.mckoi.debug.*;
34
35 /**
36  * A file format that allows for the very quick retreival of data that is
37  * stored within it. To allow for such quick reference of information in the
38  * file, we must make stipulations about how the data is stored.
39  * <ol>
40  * <li> Each data element in the table must be a fixed length. This could be
41  * thought of like a 'sector' of a disk drive. Or in a table, each row may
42  * be of the fixed size.
43  * <li> We keep track of deleted rows via a linked list of sectors.
44  * </ol>
45  * The header of the data store is as follows:
46  * <p><pre>
47  * 0 4 (int) : MAGIC - used to identify file type.
48  * 4 4 (int) : version - version of the file store.
49  * 8 4 (int) : sector_size - the size of each sector in the store.
50  * 12 8 (long) : delete_head - the head sector of the delete list.
51  * 20 8 (long) : sectors_used - number of sectors being used (not deleted).
52  * 28 1 (byte) : open - set to 1 when file opened, 0 when closed.
53  * 29 4 (int) : sector_start - offset where sector information starts.
54  * 33 ... 63 - reserved
55  * 64 ... 191 - reserved buffer for misc. state data.
56  * 192 ... sector_start - reserved
57  * </pre><p>
58  * Each sector contains a 5 byte header. This header includes a byte that
59  * contains either USED or DELETED, and a int pointer to the next chained
60  * sector. The int pointer is used to either represent a pointer to the next
61  * sector in the chain of USED sectors, with -1 indicating the end. Or, if
62  * the sector is DELETED, it points to the next deleted sector in the chain.
63  *
64  * @author Tobias Downer
65  */

66
67 public final class FixedSizeDataStore {
68
69   /**
70    * The Magic number used to help identify that the file we are reading is
71    * formatted as a fixed size data store.
72    */

73   private final static int MAGIC = 0x0badbead;
74
75   /**
76    * The offset in the file where the sector data starts.
77    */

78   private final static int SECTOR_DATA_OFFSET = 512;
79
80   /**
81    * The number of bytes that are stored in a sector in addition to the
82    * user data in a sector.
83    */

84   private final static int EXTRA_SECTOR_SIZE = 5;
85
86   /**
87    * The mark that indicates whether a sector is deleted (available) or being
88    * used.
89    */

90   private final static byte USED = 0,
91                             DELETED = (byte) 0x080;
92
93   /**
94    * If true then sectors are cached in the 'sector_cache'.
95    */

96   private final static boolean SECTORS_CACHED = true;
97
98
99   /**
100    * A DebugLogger object we can use to write debug messages to.
101    */

102   private DebugLogger debug;
103
104   /**
105    * The size of each 'sector'
106    */

107   private int sector_size;
108
109   /**
110    * The File that keeps the data.
111    */

112   private File data_file;
113
114   /**
115    * The RandomAccessFile object for the data file.
116    */

117   private RandomAccessFile data_store;
118
119   /**
120    * Set to true if we opened the store in read only mode, otherwise false.
121    */

122   private boolean read_only;
123
124   /**
125    * The file size of the data store.
126    */

127   private long data_store_size;
128
129   /**
130    * The offset where the header information finishes and the sector data
131    * starts.
132    */

133   private int sector_offset;
134
135   /**
136    * The sector buffer. This is filled with the information in some given
137    * sector.
138    */

139   private byte[] sector_buffer;
140
141   /**
142    * The sector that we currently have loaded into the buffer.
143    */

144   private int buffered_sector;
145
146   /**
147    * The head of the deleted sectors.
148    */

149   private int delete_head;
150
151   /**
152    * The number of used sectors in the store.
153    */

154   private int used_sector_count;
155
156   /**
157    * The number of locks that have been put on this store. If this number is
158    * > 0 then we may not reclaim deleted sector because another thread may be
159    * reading the data.
160    */

161   private int lock_count;
162
163   /**
164    * A cache of sectors read from the store.
165    */

166   private Cache sector_cache;
167
168
169   /**
170    * Constructs the data store. If 'sector_size' <= 0 then we determine
171    * sector size when the file is opened. If cached_access is true then
172    * all access to the store is through a cache which will greatly improve
173    * the performance of read dominated access.
174    */

175   public FixedSizeDataStore(File data_file, int sector_size,
176                             boolean cache_access,
177                             DebugLogger logger) {
178     this.debug = logger;
179
180     // Enable the cache?
181
if (cache_access) {
182       sector_cache = new Cache(64);
183     }
184     else {
185       sector_cache = null;
186     }
187
188     if (sector_size > 0) {
189       this.sector_size = sector_size + EXTRA_SECTOR_SIZE;
190     }
191     else {
192       this.sector_size = -1;
193     }
194     this.data_file = data_file;
195   }
196
197   public FixedSizeDataStore(File data_file, int sector_size,
198                             DebugLogger logger) {
199     this(data_file, sector_size, SECTORS_CACHED, logger);
200   }
201
202   // ---------- Internal methods ----------
203

204   /**
205    * Returns true if the store is locked from reclaiming deleted rows.
206    */

207   boolean locked() {
208     return (lock_count > 0);
209   }
210
211   /**
212    * Returns the total number of sectors in the file.
213    */

214   private int sectorCount() throws IOException {
215     // PROFILE: data_store.length() is expensive so we keep the length as an
216
// instance variable
217
// return (int) ((data_store.length() - sector_offset) / sector_size);
218
return (int) ((data_store_size - sector_offset) / sector_size);
219   }
220
221   /**
222    * Seeks to the 'nth' sector in the store.
223    */

224   private long seekSector(int sector) throws IOException {
225     long ra_index = (sector * sector_size);
226     long seek_to = ra_index + sector_offset;
227     data_store.seek(seek_to); // Skip past the header.
228
return seek_to;
229   }
230
231   /**
232    * Read the 'nth' sector from the store and fills the internal
233    * 'sector_buffer' with the contents.
234    */

235   private void readSector(int sector) throws IOException {
236
237     // If the buffered sector is already loaded then don't re-read.
238
if (buffered_sector != sector) {
239
240       if (sector_cache != null) {
241         // If this sector is in the cache then use the cached entry instead.
242
Integer JavaDoc cacheKey = new Integer JavaDoc(sector);
243         byte[] sbuf = (byte[]) sector_cache.get(cacheKey);
244         if (sbuf == null) {
245           // If not in the cache then read from the file.
246
seekSector(sector);
247           data_store.readFully(sector_buffer, 0, sector_size);
248           sbuf = new byte[sector_size];
249           System.arraycopy(sector_buffer, 0, sbuf, 0, sector_size);
250           sector_cache.put(cacheKey, sbuf);
251         }
252         else {
253           // Otherwise, read the cached entry.
254
System.arraycopy(sbuf, 0, sector_buffer, 0, sector_size);
255         }
256       }
257       else {
258         // If no caching then read the sector
259
seekSector(sector);
260         data_store.readFully(sector_buffer, 0, sector_size);
261       }
262
263       buffered_sector = sector;
264     }
265   }
266
267   /**
268    * Sets the length of the data store to the given size. This has a side-
269    * effect of setting the file pointer to the end of the file.
270    */

271   private void setDataStoreSize(long new_size) throws IOException {
272     long p = new_size - 1;
273     if (p > 0) {
274       data_store.seek(p);
275       data_store.write(0);
276       data_store_size = new_size;
277     }
278   }
279
280   /**
281    * Writes the sector data in 'sector_buffer' to the given sector offset in
282    * the store.
283    */

284   private void writeSector(int sector, int length) throws IOException {
285     long seek_to = seekSector(sector);
286     // If writing to end of file, extend the file size.
287
if (seek_to == data_store_size) {
288       // This will extend the file size by 16 sector lengths and add the
289
// additional sectors to the deleted chain.
290
setDataStoreSize(seek_to + sector_size);
291       seekSector(sector);
292     }
293     // Check just to make sure,
294
if (length <= sector_size) {
295       data_store.write(sector_buffer, 0, length);
296       if (sector_cache != null) {
297         // Copy this into the cache.
298
byte[] sbuf = new byte[sector_size];
299         System.arraycopy(sector_buffer, 0, sbuf, 0, length);
300         sector_cache.put(new Integer JavaDoc(sector), sbuf);
301       }
302     }
303     else {
304       throw new IOException("length > sector_size");
305     }
306   }
307
308   /**
309    * Writes the sector data in 'sector_buffer' to the given sector offset in
310    * the store.
311    */

312   private void writeSector(int sector) throws IOException {
313     writeSector(sector, sector_size);
314   }
315
316   /**
317    * Sets up the sector header information in 'sector_buffer'.
318    */

319   private void setSectorHeader(byte status, int next_sector)
320                                                           throws IOException {
321     sector_buffer[0] = status;
322     sector_buffer[1] = (byte) ((next_sector >>> 24) & 0xFF);
323     sector_buffer[2] = (byte) ((next_sector >>> 16) & 0xFF);
324     sector_buffer[3] = (byte) ((next_sector >>> 8) & 0xFF);
325     sector_buffer[4] = (byte) ((next_sector >>> 0) & 0xFF);
326   }
327
328   /**
329    * Writes the contents of the byte[] array to the sector, setting the
330    * USED flag to true, and the 'next' int in the sector header.
331    * <p>
332    * NOTE: Assumes length is less than user space size of sector.
333    */

334   private int writeBufToSector(int sector, int next_sector,
335                       byte[] buf, int offset, int length) throws IOException {
336
337     // Write a new sector buffer entry,
338
setSectorHeader(USED, next_sector);
339
340     System.arraycopy(buf, offset, sector_buffer, 5, length);
341
342     // NOTE: Notice the order here. We update header state first, then
343
// write the sector. If a crash happens between 'synch' and 'writeSector'
344
// all that will happen is we'll be left with a hanging chain. There
345
// should be no corruption.
346

347     // Add 1 to the used sector count.
348
++used_sector_count;
349     // Sync the file
350
synch();
351     // Write the sector in the buffer
352
writeSector(sector, length + 5);
353     // We now have this sector in the buffer
354
buffered_sector = sector;
355
356     // Return the sector we wrote this to,
357
return sector;
358   }
359
360   /**
361    * Reclaims the first sector from the free sector list.
362    */

363   private int reclaimTopFree() throws IOException {
364     // There's a sector we can use so use it!
365
int free_sector = delete_head;
366     // Take this sector out of the chain of deleted records.
367
readSector(free_sector);
368
369     int c1 = (((int) sector_buffer[1]) & 0x0FF);
370     int c2 = (((int) sector_buffer[2]) & 0x0FF);
371     int c3 = (((int) sector_buffer[3]) & 0x0FF);
372     int c4 = (((int) sector_buffer[4]) & 0x0FF);
373
374     delete_head = (c1 << 24) + (c2 << 16) + (c3 << 8) + (c4);
375     return free_sector;
376   }
377
378   /**
379    * Finds the first free available sector that we can use. If we are
380    * reclaiming from the deleted list, the deleted row is taken from the
381    * linked list immediately.
382    * <p>
383    * NOTE: This method may alter 'delete_head' changing the list of deleted
384    * sectors.
385    */

386   private int findFreeSector() throws IOException {
387     // Are we locked and can we reclaim a deleted sector?
388
if (!locked() && delete_head != -1) {
389       return reclaimTopFree();
390     }
391
392     // Either we are locked or there are no deleted sectors in the chain.
393
// The new sector is at the end of the store.
394
return sectorCount();
395
396   }
397
398   /**
399    * Finds the first free available sector past the next one. This means,
400    * if locked or delete_head == -1 then we return the sectorCount() + 1,
401    * otherwise we reclaim the next available of the delete queue.
402    */

403   private int findFreeSectorPastNext() throws IOException {
404     // Are we locked and can we reclaim a deleted sector?
405
if (!locked() && delete_head != -1) {
406       return reclaimTopFree();
407     }
408
409     // Either we are locked or there are no deleted sectors in the chain.
410
// The new sector is at the end of the store.
411
return sectorCount() + 1;
412   }
413
414   /**
415    * Finds the first 'n' available sectors that we can use. If we are
416    * reclaiming from the deleted list, the deleted row(s) are taken from the
417    * linked list immediately.
418    * <p>
419    * NOTE: This method may alter 'delete_head' changing the list of deleted
420    * sectors.
421    */

422   private int[] findFreeSectors(int count) throws IOException {
423     int fs_index = 0;
424     int[] free_sectors = new int[count];
425     // Are we locked, and can we reclaim a deleted sector?
426
if (!locked()) {
427       while (fs_index < count && delete_head != -1) {
428         free_sectors[fs_index] = reclaimTopFree();
429         ++fs_index;
430       }
431     }
432     int sec_count = sectorCount();
433     // Free are on end of file now,
434
while (fs_index < count) {
435       free_sectors[fs_index] = sec_count;
436       ++sec_count;
437       ++fs_index;
438     }
439     // Return the top list of free sectors.
440
return free_sectors;
441   }
442
443
444
445   // ---------- Public methods ----------
446

447   /**
448    * Returns the size of the data store file. This is the total number of
449    * bytes stored in the data store.
450    */

451   public long totalSize() {
452     return data_file.length();
453   }
454
455   /**
456    * Every data store has a 128 byte buffer that can be used to store state
457    * information. The buffer starts at offset 64 of the file until offset 192.
458    * This method writes data to that offset.
459    */

460   public void writeReservedBuffer(byte[] info, int offset, int length,
461                                            int res_offset) throws IOException {
462     if ((length + res_offset) > 128) {
463       throw new Error JavaDoc("Attempted to write > 128 bytes in reserve buffer.");
464     }
465     data_store.seek(res_offset + 64);
466     data_store.write(info, offset, length);
467   }
468
469   public void writeReservedBuffer(byte[] info, int offset, int length)
470                                                            throws IOException {
471     writeReservedBuffer(info, offset, length, 0);
472   }
473
474   /**
475    * Reads from the buffer reserve into the given byte array.
476    */

477   public void readReservedBuffer(byte[] info, int offset, int length)
478                                                            throws IOException {
479     if (length > 128) {
480       throw new Error JavaDoc("Attempted to read > 128 bytes from reserve buffer.");
481     }
482     data_store.seek(64);
483     data_store.readFully(info, offset, length);
484   }
485
486   // Byte array used to synchronize data in store.
487
// Enough room for two longs.
488
private byte[] sync_buffer = new byte[16];
489
490   /**
491    * Synchronizes the memory store with the file header. This writes
492    * information into the header. This should be called periodically.
493    * Synch does nothing for a read only store.
494    */

495   public void synch() throws IOException {
496     if (!read_only) {
497       // Write the head deleted sector.
498
ByteArrayUtil.setLong(delete_head, sync_buffer, 0);
499       // Write the number of sectors that are used (not deleted).
500
ByteArrayUtil.setLong(used_sector_count, sync_buffer, 8);
501
502       // Write the update
503
// Skip past magic int and sector size int
504
data_store.seek(12);
505       data_store.write(sync_buffer, 0, 16);
506     }
507   }
508
509   /**
510    * Performs a hard synchronization of this store. This will force the OS
511    * to synchronize the contents of the data store. hardSynch does nothing
512    * for a read only store.
513    */

514   public void hardSynch() throws IOException {
515     if (!read_only) {
516       synch();
517       // Make sure we are also synchronized in the file system.
518
try {
519         data_store.getFD().sync();
520       }
521       catch (SyncFailedException e) { /* ignore */ }
522     }
523   }
524
525   /**
526    * Returns true if the store has been opened in read only mode.
527    */

528   public boolean isReadOnly() {
529     return read_only;
530   }
531
532   /**
533    * Opens the data store. The data store can be opened in 'read only' mode.
534    * Returns 'true' if the open procedure should repair itself (dirty open) or
535    * false if the file was cleanly closed down.
536    * <p>
537    * It is not possible to open a damaged store in read only mode.
538    *
539    * @param read_only if true, then the database is opened in read only mode,
540    * otherwise it is opened in read/write mode.
541    */

542   public boolean open(boolean read_only) throws IOException {
543     this.read_only = read_only;
544
545     // If the file doesn't exist, check we have a valid sector size.
546
if (!data_file.exists()) {
547       if (sector_size <= 0) {
548         throw new IOException("Sector size not set for new file.");
549       }
550     }
551
552     // Open the file
553
String JavaDoc mode = read_only ? "r" : "rw";
554     data_store = new RandomAccessFile(data_file, mode);
555     data_store.seek(0);
556     // Does the header exist?
557
if (data_store.length() < SECTOR_DATA_OFFSET) {
558       if (read_only) {
559         throw new IOException(
560                      "Unable to open FixedSizeDataStore. No header found.");
561       }
562
563       ByteArrayOutputStream bout =
564                                new ByteArrayOutputStream(SECTOR_DATA_OFFSET);
565       DataOutputStream dout = new DataOutputStream(bout);
566
567       // No, so create the header
568
// Write the magic int
569
dout.writeInt(MAGIC);
570       // The version of the file type.
571
dout.writeInt(0x0100);
572       // Write the sector size
573
dout.writeInt(sector_size);
574       // Write the delete_head
575
dout.writeLong(-1);
576       // Write the number of sectors that are being used.
577
dout.writeLong(0);
578       // Write whether file open or closed
579
dout.writeByte(0);
580       // Write the offset where the sector information starts.
581
dout.writeInt(SECTOR_DATA_OFFSET);
582
583       // Transfer to a new buffer and write entirely to file.
584
byte[] buf = bout.toByteArray();
585       dout.close();
586       byte[] buf2 = new byte[SECTOR_DATA_OFFSET];
587       System.arraycopy(buf, 0, buf2, 0, buf.length);
588       for (int i = buf.length; i < SECTOR_DATA_OFFSET; ++i) {
589         buf2[i] = (byte) 255;
590       }
591       data_store.write(buf2);
592
593     }
594     data_store.seek(0);
595     // Set the size of the file.
596
data_store_size = data_store.length();
597
598     // Read the header,
599
if (data_store.readInt() == MAGIC) {
600       // Read the version number,
601
int version = data_store.readInt();
602       if (version != 0x0100) {
603         throw new IOException("Unknown version.");
604       }
605       // Check the sector size is right,
606
int ss_check = data_store.readInt();
607       // If sector_size not set yet, then set it from value in file.
608
if (sector_size <= 0) {
609         sector_size = ss_check;
610       }
611       if (ss_check == sector_size) {
612
613         boolean need_repair = false;
614
615         // Find the head of the deleted sectors linked list.
616
delete_head = (int) data_store.readLong();
617         // Find the number of sectors that are being used.
618
used_sector_count = (int) data_store.readLong();
619         // Did we close down cleanly?
620
need_repair = data_store.readByte() == 0 ? false : true;
621         // The offset where the sector data starts.
622
sector_offset = data_store.readInt();
623
624         sector_buffer = new byte[sector_size];
625         buffered_sector = -2;
626
627         // Write the 'open' flag to indicate store is open
628
// ( Only if we opening in read/write mode )
629
if (!read_only) {
630           data_store.seek(28);
631           data_store.writeByte(1);
632         }
633
634         // Check sector count * sector_size + sector_offset is the same size
635
// as the data store.
636
int pred_size = (sectorCount() * sector_size) + sector_offset;
637 // System.out.println("Sector Count: " + sectorCount());
638
// System.out.println("Sector Size: " + sector_size);
639
// System.out.println("Sector Offset: " + sector_offset);
640
// System.out.println("Data Store Size: " + data_store_size);
641
// System.out.println("Pred Size: " + pred_size);
642

643         if (pred_size != data_store_size) {
644           debug.write(Lvl.ERROR, this,
645                       "The FixedSizeDataStore file size is incorrect.");
646           debug.write(Lvl.ERROR, this,
647                       "File size should be: " + pred_size + "\n" +
648                       "But it's really: " + data_store_size);
649           need_repair = true;
650         }
651
652         // Move seek to sector start offset.
653
data_store.seek(sector_offset);
654
655         // Do we need to repair?
656
if (need_repair) {
657           debug.write(Lvl.ALERT, this, "Store not closed cleanly.");
658         }
659
660         // Return true if we repaired.
661
return need_repair;
662
663       }
664       else {
665         throw new IOException(
666                             "Sector size for this data store does not match.");
667       }
668     }
669     else {
670       throw new IOException("Format invalid; MAGIC number didn't match.");
671     }
672
673   }
674
675   /**
676    * Closes the data store.
677    */

678   public void close() throws IOException {
679     // Sync internal information
680
synch();
681     // Write a '0' in the 'open' header section to indicate the store
682
// closed cleanly.
683
// ( Only if we opening in read/write mode )
684
if (!read_only) {
685       data_store.seek(28);
686       data_store.writeByte(0);
687     }
688     // Check the size
689
long close_size = data_store.length();
690     if (close_size != data_store_size) {
691       debug.write(Lvl.ERROR, this,
692                   "On closing file, data_store_size != close_size (" +
693                   data_store_size + " != " + close_size + ")");
694     }
695
696     // Sync the file with the hardware,
697
try {
698       data_store.getFD().sync();
699     }
700     catch (SyncFailedException e) { /* ignore */ }
701
702     // Close the file
703
data_store.close();
704     // Help the GC
705
data_store = null;
706     sector_buffer = null;
707     buffered_sector = -2;
708
709   }
710
711   /**
712    * Returns true if the store is closed.
713    */

714   public boolean isClosed() {
715     return data_store == null;
716   }
717
718   /**
719    * Deletes the data store from the file system.
720    */

721   public void delete() {
722     if (data_store == null) {
723       data_file.delete();
724     }
725     else {
726       throw new Error JavaDoc("Must close before FixedSizeDataStore is deleted.");
727     }
728   }
729
730
731
732
733   /**
734    * Returns true if the file for this store exists.
735    */

736   public boolean exists() throws IOException {
737     return data_file.exists();
738   }
739
740   /**
741    * Returns the number of bytes that the user may store in a sector. The
742    * actual sector space in the file may be slightly larger.
743    */

744   public int getSectorSize() {
745     return sector_size - EXTRA_SECTOR_SIZE;
746   }
747
748   /**
749    * Returns the number of sectors in the store that are being used (as
750    * opposed to being deleted).
751    */

752   public int getSectorUseCount() {
753     return used_sector_count;
754   }
755
756   /**
757    * Returns the total number of sectors that are currently available
758    * (includes used and deleted sectors).
759    */

760   public int rawSectorCount() throws IOException {
761     return sectorCount();
762   }
763
764
765   // ------- Locking -------
766

767   /**
768    * Locks the store by some process so that we may not reclaim deleted
769    * sectors. The purpose of this is in the situation where we have a slow
770    * thread accessing information from the store, and a seperate thread
771    * is still able to modifying (delete and add) to the store.
772    */

773   public void lock() {
774     ++lock_count;
775   }
776
777   /**
778    * Unlocks the store.
779    */

780   public void unlock() {
781     --lock_count;
782     if (lock_count < 0) {
783       throw new Error JavaDoc("Unlocked more times than we locked.");
784     }
785   }
786
787
788   // ------- Sector queries --------
789

790   /**
791    * Returns true if the sector number is flagged as deleted. If returns false
792    * then the sector is being used.
793    */

794   public boolean isSectorDeleted(int sector) throws IOException {
795     readSector(sector);
796     return ((sector_buffer[0] & DELETED) != 0);
797   }
798
799   // ------- Get a sector from the store -------
800

801   /**
802    * Gets the contents of the sector at the given index.
803    */

804   public byte[] getSector(int sector, byte[] buf,
805                                   int offset, int length) throws IOException {
806     if (sector >= sectorCount()) {
807       throw new IOException("Can't get sector, out of range.");
808     }
809
810     int ssize = getSectorSize();
811     if (length > ssize) {
812       throw new IOException("length > sector size");
813     }
814     readSector(sector);
815     System.arraycopy(sector_buffer, EXTRA_SECTOR_SIZE, buf, offset, length);
816     return buf;
817   }
818
819   /**
820    * Gets the contents of the sector at the given index.
821    */

822   public byte[] getSector(int sector, byte[] buf) throws IOException {
823     return getSector(sector, buf, 0,
824                      Math.min(buf.length, getSectorSize()));
825   }
826
827   /**
828    * Gets the contents of the sector at the given index.
829    */

830   public byte[] getSector(int sector) throws IOException {
831     if (sector >= sectorCount()) {
832       throw new IOException("Can't get sector, out of range.");
833     }
834
835     int ssize = getSectorSize();
836     byte[] buf = new byte[ssize];
837     readSector(sector);
838     System.arraycopy(sector_buffer, EXTRA_SECTOR_SIZE, buf, 0, ssize);
839     return buf;
840   }
841
842   /**
843    * Gets the contents of the sector at the given index as an int[] array.
844    * The array size is /4 of the sector size. If the sector size is not
845    * divisible by 4 then the last 1-3 bytes are truncated.
846    */

847   public int[] getSectorAsIntArray(int sector, int[] buf) throws IOException {
848     if (sector >= sectorCount()) {
849       throw new IOException("Can't get sector, out of range.");
850     }
851
852     int length = buf.length * 4;
853     int ssize = getSectorSize();
854     if (length > ssize) {
855       throw new IOException("length > sector size");
856     }
857     readSector(sector);
858
859     // Convert the sector (as a byte array) to an int array.
860
int p = EXTRA_SECTOR_SIZE;
861     int i = 0;
862     while (i < buf.length) {
863
864       int c1 = (((int) sector_buffer[p++]) & 0x0FF);
865       int c2 = (((int) sector_buffer[p++]) & 0x0FF);
866       int c3 = (((int) sector_buffer[p++]) & 0x0FF);
867       int c4 = (((int) sector_buffer[p++]) & 0x0FF);
868       int v = (c1 << 24) + (c2 << 16) + (c3 << 8) + (c4);
869
870       buf[i++] = v;
871     }
872
873     return buf;
874
875   }
876
877
878
879   /**
880    * Reads information across a chain of sectors and fills the byte[] array
881    * buffer. Returns the number of bytes that were read (should always be
882    * equal to 'length').
883    */

884   public int readAcross(int sector_head, byte[] buf, int offset, int length)
885                                                            throws IOException {
886     if (sector_head >= sectorCount()) {
887       throw new IOException("Can't get sector, out of range.");
888     }
889
890     int to_read = length;
891     int ssize = getSectorSize();
892
893     int walk = sector_head;
894
895     while (walk != -1 && to_read > 0) {
896
897       // Read in the sector
898
readSector(walk);
899       // Is the sector deleted?
900
if ((sector_buffer[0] & DELETED) != 0) {
901         throw new IOException("Can not read across a deleted chain.");
902       }
903       // The next sector in the chain...
904
int next_walk = ByteArrayUtil.getInt(sector_buffer, 1);
905
906       // Fill the byte[] array buffer with what's in the sector.
907
int amount_read = Math.min(to_read, ssize);
908       System.arraycopy(sector_buffer, EXTRA_SECTOR_SIZE, buf, offset,
909                        amount_read);
910
911       offset += amount_read;
912       to_read -= amount_read;
913
914       // Walk to next in chain
915
walk = next_walk;
916
917     }
918
919     return offset;
920
921   }
922
923   /**
924    * Traverses a sector chain and returns an array of all sectors that are
925    * part of the chain.
926    * Useful for diagnostic, repair and statistical operations.
927    */

928   public int[] getSectorChain(int sector_head, int length) throws IOException {
929
930     if (sector_head >= sectorCount()) {
931       throw new IOException("Can't get sector, out of range.");
932     }
933
934     // The number of sectors to traverse.
935
int span_count = calculateSectorSpan(length);
936
937     int[] spans = new int[span_count];
938
939     int ssize = getSectorSize();
940     int walk = sector_head;
941     int chain_count = 0;
942
943     while (chain_count < span_count) {
944
945       spans[chain_count] = walk;
946
947       // Read in the sector
948
readSector(walk);
949       // The next sector in the chain...
950
walk = ByteArrayUtil.getInt(sector_buffer, 1);
951
952       // Increment the chain walk counter.
953
++chain_count;
954
955     }
956
957     return spans;
958
959   }
960
961   /**
962    * Traverses a sector chain and returns an array of all sectors that are
963    * part of the chain.
964    * Useful for diagnostic, repair and statistical operations.
965    */

966   public int[] getSectorChain(int sector_head) throws IOException {
967
968     if (sector_head >= sectorCount()) {
969       throw new IOException("Can't get sector, out of range.");
970     }
971
972     IntegerVector spans = new IntegerVector();
973
974     int ssize = getSectorSize();
975     int walk = sector_head;
976
977     while (walk > -1) {
978       spans.addInt(walk);
979       // Read in the sector
980
readSector(walk);
981       // The next sector in the chain...
982
walk = ByteArrayUtil.getInt(sector_buffer, 1);
983     }
984
985     return spans.toIntArray();
986
987   }
988
989   // ------- Delete a sector from the store -------
990

991   /**
992    * Deletes a sector from the store. The sector is only marked as deleted,
993    * however, and the contents may still be accessed via the 'getSector'
994    * methods. If the store is add locked, then it is guarenteed that no
995    * deleted sectors will be overwritten until the add lock is taken from the
996    * table.
997    * <p>
998    * Throws an IO error if the sector is marked as deleted.
999    */

1000  public void deleteSector(int sector) throws IOException {
1001    deleteAcross(sector);
1002  }
1003
1004  /**
1005   * Deletes a set of sectors that have been chained together. This should
1006   * be used to delete data added via the 'write' method. However, it
1007   * can be used to delete data added via the 'addSector'
1008   */

1009  public void deleteAcross(final int sector_head) throws IOException {
1010
1011    if (sector_head < 0) {
1012      throw new IOException("Sector is out of range.");
1013    }
1014
1015    if (sector_head >= sectorCount()) {
1016      throw new IOException("Can't get sector, out of range.");
1017    }
1018
1019    // How algorithm works:
1020
// delete_head is set to sector_head
1021
// We then walk through the chain until we hit a -1 and then we set
1022
// that to the old delete_head.
1023
// NOTE: This algorithm doesn't change any chained sectors, so the
1024
// 'readAcross' method will still work even on deleted chains (provided
1025
// there's a lock on the store).
1026

1027    int walk = sector_head;
1028
1029    while (walk != -1) {
1030
1031      // Read in the sector
1032
readSector(walk);
1033      if ((sector_buffer[0] & DELETED) != 0) {
1034        // Already been deleted, so throw an IOException
1035
throw new IOException("Sector has already been deleted.");
1036      }
1037
1038      // The next sector in the chain...
1039
int next_walk = ByteArrayUtil.getInt(sector_buffer, 1);
1040
1041      // Mark as deleted
1042
sector_buffer[0] = DELETED;
1043      // If the next chain is -1 (end of chain) then set it to delete_head
1044
if (next_walk == -1) {
1045        ByteArrayUtil.setInt(delete_head, sector_buffer, 1);
1046      }
1047      // Write the new header for the sector.
1048
seekSector(walk);
1049      data_store.write(sector_buffer, 0, 5);
1050      if (sector_cache != null) {
1051        // Remove this from the cache.
1052
sector_cache.remove(new Integer JavaDoc(walk));
1053      }
1054      // Delete 1 from the used sector count.
1055
--used_sector_count;
1056
1057      // Walk to next in chain
1058
walk = next_walk;
1059
1060    }
1061
1062    // Add this chain to the deleted chain.
1063
delete_head = sector_head;
1064    // Synchronize with the file system.
1065
synch();
1066
1067  }
1068
1069  /**
1070   * Deletes all sectors in the entire store. Use with care.
1071   */

1072  public void deleteAllSectors() throws IOException {
1073    int sector_count = sectorCount();
1074    for (int i = 0; i < sector_count; ++i) {
1075      readSector(i);
1076      sector_buffer[0] = DELETED;
1077      int next = i + 1;
1078      if (i == sector_count - 1) {
1079        next = -1;
1080      }
1081      ByteArrayUtil.setInt(next, sector_buffer, 1);
1082      writeSector(i);
1083    }
1084    // Set the head of the delete chain
1085
delete_head = sector_count == 0 ? -1 : 0;
1086    // set 'used_sector_count'
1087
used_sector_count = 0;
1088    // Sync the information with the file
1089
synch();
1090  }
1091
1092  // ------- Adds a new sector into the store -------
1093

1094  /**
1095   * Writes the contents of a sector into the store overwritting any
1096   * other information that may be stored there. This is used as a rough
1097   * data editting command.
1098   */

1099  public int overwriteSector(int sector, byte[] buf, int offset, int length)
1100                                                          throws IOException {
1101    int ssize = getSectorSize();
1102    if (length > ssize) {
1103      throw new IOException("Sector too large to add to store.");
1104    }
1105
1106    // Write the sector entry,
1107
return writeBufToSector(sector, -1, buf, offset, length);
1108
1109  }
1110
1111  /**
1112   * Writes the contents of a sector into the store overwritting any
1113   * other information that may be stored there. This is used as a rough
1114   * data editting command.
1115   */

1116  public int overwriteSector(int sector, byte[] buf) throws IOException {
1117    return overwriteSector(sector, buf, 0, buf.length);
1118  }
1119
1120  /**
1121   * Adds a new sector into the store. It finds a suitable sector to store
1122   * the information and returns the sector number. If lock_count > 0 then
1123   * we do not reclaim deleted sectors, otherwise we do.
1124   */

1125  public int addSector(byte[] buf, int offset, int length) throws IOException {
1126    int ssize = getSectorSize();
1127    if (length > ssize) {
1128      throw new IOException("Sector too large to add to store.");
1129    }
1130    // Find a suitable sector to add the data into.
1131
int sector = findFreeSector();
1132
1133    // Write a new sector buffer entry,
1134
return writeBufToSector(sector, -1, buf, offset, length);
1135
1136  }
1137
1138  /**
1139   * Adds a new sector into the store. It finds a suitable sector to store
1140   * the information and returns the sector number. If lock_count > 0 then
1141   * we do not reclaim deleted sectors, otherwise we do.
1142   */

1143  public int addSector(byte[] buf) throws IOException {
1144    return addSector(buf, 0, buf.length);
1145  }
1146
1147  /**
1148   * Calculates the number of sectors the given length of bytes will span.
1149   */

1150  public int calculateSectorSpan(int length) {
1151    int sector_size = getSectorSize();
1152    int span_count = length / sector_size;
1153    // Special case, if length is zero then still use at least 1 sector,
1154
if (length == 0 || (length % sector_size) != 0) {
1155      ++span_count;
1156    }
1157    return span_count;
1158  }
1159
1160  /**
1161   * Writes a byte[] array of data across as many sectors as it takes to store
1162   * the data. Returns the index to the first sector that contains the
1163   * start of the data.
1164   */

1165  public int writeAcross(byte[] buf, int offset, int length)
1166                                                          throws IOException {
1167
1168    int sector_size = getSectorSize();
1169
1170    // How many sectors does this data span?
1171
int span_count = calculateSectorSpan(length);
1172
1173    // Get free sectors to write this buffer information to.
1174
int[] free_sectors = findFreeSectors(span_count);
1175    // Sort the list so we are writing forward in the file.
1176
Arrays.sort(free_sectors, 0, span_count);
1177
1178    // Write the information to the sectors.
1179
int to_write = length;
1180    int to_offset = 0;
1181
1182    for (int i = 0; i < span_count; ++i) {
1183      int sector = free_sectors[i];
1184      int next_sector;
1185      if (i < span_count - 1) {
1186        next_sector = free_sectors[i + 1];
1187      }
1188      else {
1189        next_sector = -1;
1190      }
1191
1192      // Write the sector part to the store.
1193
writeBufToSector(sector, next_sector, buf, to_offset,
1194                       Math.min(to_write, sector_size));
1195
1196      to_write -= sector_size;
1197      to_offset += sector_size;
1198    }
1199
1200    // Return the first free sector...
1201
return free_sectors[0];
1202
1203  }
1204
1205
1206  /**
1207   * The last sector output stream that was created.
1208   */

1209  private SectorOutputStream sector_output_stream;
1210
1211  /**
1212   * Returns an OutputStream implementation that is used to write a stream
1213   * of information into this data store. As data is written into the stream,
1214   * the data is flushed into this store at the next available sector. When
1215   * the stream is closed, the entire contents of the stream will be contained
1216   * within the store. A call to 'getSectorOfLastOutputStream' can be used to
1217   * return an index that is used to reference this stream of information in
1218   * the store.
1219   * <p>
1220   * NOTE: While an output stream returned by this method is not closed,
1221   * it is unsafe to use any methods in the FixedSizeDataStore object.
1222   */

1223  public OutputStream getSectorOutputStream() throws IOException {
1224    sector_output_stream = new SectorOutputStream();
1225    return sector_output_stream;
1226  }
1227
1228  /**
1229   * Returns the first sector the OutputStream returned by
1230   * 'getSectorOutputStream' wrote to. This is the start of the chain.
1231   */

1232  public int getSectorOfLastOutputStream() {
1233    return sector_output_stream.first_sector;
1234  }
1235
1236  /**
1237   * Returns the number of bytes that were written out by the last closed
1238   * output stream returned by 'getSectorOutputStream'.
1239   */

1240  public int getLengthOfLastOutputStream() {
1241    return sector_output_stream.count;
1242  }
1243
1244  /**
1245   * Wipes the SectorOutputStream from this object. This should be closed
1246   * after the stream is closed.
1247   */

1248  public void wipeLastOutputStream() {
1249    sector_output_stream = null;
1250  }
1251
1252  /**
1253   * Returns an InputStream implementation that is used to read a stream of
1254   * information from the store. This input stream will iterate through the
1255   * sector chain given.
1256   * <p>
1257   * NOTE: Using this InputStream, an end of stream identifier is never
1258   * produced. When the last sector in the chain is reached, the input
1259   * stream will first read padding whitespace, then it will either loop to
1260   * the start of the last sector, or move to another undefined sector.
1261   * You must not rely on this stream reaching an EOF.
1262   */

1263  public InputStream getSectorInputStream(int sector_head) throws IOException {
1264    return new SectorInputStream(sector_head);
1265  }
1266
1267  // ------- Utility methods -------
1268

1269  /**
1270   * Copies the entire contents of this store to a destination directory.
1271   * This can only be called when the data store is open. It makes an
1272   * exact copy of the file.
1273   * <p>
1274   * The purpose of this method is so we can make a copy of the data
1275   * in this store while the store is open and 'live'.
1276   * <p>
1277   * We assume synchronization on this object.
1278   * <p>
1279   * @param path the directory to copy this file to.
1280   */

1281  public void copyTo(File path) throws IOException {
1282    String JavaDoc fname = data_file.getName();
1283
1284    FileOutputStream fout = new FileOutputStream(new File(path, fname));
1285    int BUF_SIZE = 65536; // 64k copy buffer.
1286
byte[] buf = new byte[BUF_SIZE];
1287
1288    data_store.seek(0);
1289    int read = data_store.read(buf, 0, BUF_SIZE);
1290    while (read >= 0) {
1291      fout.write(buf, 0, read);
1292      read = data_store.read(buf, 0, BUF_SIZE);
1293    }
1294
1295    fout.close();
1296
1297  }
1298
1299  /**
1300   * Attempts to repair this data store to a correct state. The UserTerminal
1301   * object can be used to ask the user questions and to output information
1302   * on the progress of the repair.
1303   * <p>
1304   * The store must have been opened before this method is called.
1305   */

1306  public void fix(UserTerminal terminal) throws IOException {
1307
1308    terminal.println("- File: " + data_file);
1309
1310    // First synch with the disk
1311
synch();
1312
1313    // Check the length is correct
1314
if ((data_store_size - (long) sector_offset) %
1315                                                (long) sector_size != 0) {
1316      terminal.println("+ Altering length of file so it is correct " +
1317                       "for sector size");
1318      int row_count = sectorCount() + 1;
1319      long new_size = (row_count * sector_size) + sector_offset;
1320      setDataStoreSize(new_size);
1321    }
1322
1323    IntegerVector sector_info = new IntegerVector();
1324    IntegerVector scc = new IntegerVector();
1325    int null_count = 0;
1326
1327    // The total number of physical sectors in the file,
1328
int sector_count = sectorCount();
1329    terminal.println("- Sector Count: " + sectorCount());
1330
1331    // Go through every sector and mark each one appropriately.
1332
for (int i = 0; i < sector_count; ++i) {
1333      readSector(i);
1334      // Deleted sector
1335
int next_chain = ByteArrayUtil.getInt(sector_buffer, 1);
1336      sector_info.addInt((int) sector_buffer[0]);
1337      sector_info.addInt(next_chain);
1338
1339      if (next_chain == -1) {
1340        ++null_count;
1341      }
1342      else {
1343        int old_val = 0;
1344        if (next_chain < scc.size()) {
1345          old_val = scc.intAt(next_chain);
1346        }
1347        scc.placeIntAt(old_val + 1, next_chain);
1348      }
1349    }
1350
1351    // The number of unchanged sectors...
1352
terminal.println("- unchained sectors = " + null_count);
1353    // Any sectors that are referenced more than once are erroneous.
1354
// These sectors are marked as bad
1355
IntegerVector bad_sectors = new IntegerVector();
1356    for (int i = 0; i < scc.size(); ++i) {
1357      int ref_count = scc.intAt(i);
1358      if (ref_count > 1) {
1359        terminal.println("- [" + i + "] reference count = " + ref_count);
1360        terminal.println("+ Marking all references as bad (except first).");
1361        boolean found_first = false;
1362        for (int n = 0; n < sector_info.size(); n += 2) {
1363          if (sector_info.intAt(n + 1) == i) {
1364            if (found_first) {
1365              bad_sectors.addInt(n / 2);
1366            }
1367            found_first = true;
1368          }
1369        }
1370      }
1371    }
1372
1373    // Any marked as bad?
1374
if (bad_sectors.size() > 0) {
1375      terminal.println("+ Marked " + bad_sectors.size() + " sectors bad.");
1376    }
1377
1378    // Mark the sectors as deleted
1379
for (int i = 0; i < bad_sectors.size(); ++i) {
1380      int sector = bad_sectors.intAt(i);
1381      readSector(sector);
1382      sector_buffer[0] = DELETED;
1383      writeSector(sector);
1384    }
1385
1386    // PENDING: Are there are chains from active to deleted sectors, or
1387
// deleted to active.
1388

1389
1390    // Then go ahead and repair the file,
1391
repair();
1392
1393  }
1394
1395  /**
1396   * Cleans up so all deleted sectors are completely removed from the store.
1397   * This has the effect of reducing the size of the file by the size of every
1398   * deleted sector.
1399   * <p>
1400   * It is extremely important that nothing can be read/written from the file
1401   * while this is happening. And certainly, we can not have any locks on
1402   * this store.
1403   * <p>
1404   * Returns true if the layout of the sectors changed (so we can fix
1405   * indices that point to sectors).
1406   */

1407  public boolean clearDeletedSectors() throws IOException {
1408
1409    if (locked()) {
1410      throw new IOException(
1411                          "Store is locked, can not reclaim deleted sectors.");
1412    }
1413
1414    // Are there any deleted rows to reclaim?
1415
if (delete_head != -1) {
1416
1417      // Yes, so run through the table and move all data over the top of
1418
// deleted rows.
1419
int scount = sectorCount();
1420      int move_to = 0;
1421      int row_count = 0;
1422      for (int i = 0; i < scount; ++i) {
1423        // Read the sector
1424
readSector(i);
1425        // Is it used? (DELETED flag not set)
1426
if ((sector_buffer[0] & DELETED) == 0) {
1427          ++row_count;
1428          // Not deleted, therefore we may have to move. Is move_to < i?
1429
if (move_to < i) {
1430            // Move this sector to 'move_to'
1431
writeSector(move_to);
1432            buffered_sector = move_to;
1433          }
1434          move_to = move_to + 1;
1435        }
1436      }
1437
1438      // Resize the file.
1439
long new_size = (row_count * sector_size) + sector_offset;
1440      setDataStoreSize(new_size);
1441      // Set the delete_head to -1
1442
delete_head = -1;
1443      // The number of sectors that are being used.
1444
used_sector_count = row_count;
1445      // Synchronize the header.
1446
synch();
1447      // Sectors moved around so return true.
1448
return true;
1449
1450    }
1451    else {
1452      // No rows to remove so return false.
1453
return false;
1454    }
1455  }
1456
1457// [ It's a bad idea to use this when there are sector chains because it
1458
// reorganizes the chain of deleted sectors. The order of deleted sectors is
1459
// important when dirty reading deleted information from the store (when a
1460
// table is updated for example).
1461
// In addition, it's debatable whether a 'repair' method is worth it. It
1462
// would probably be better to use 'clearDeletedSectors' to ensure the
1463
// store is in a good state. ]
1464

1465  /**
1466   * Repairs the consistancy of the store. This is an expensive operation
1467   * that runs through every sector and determines if it's deleted or used.
1468   * If it's deleted it is added into the deleted linked list.
1469   * <p>
1470   * Repair assumes we can at least get past the 'open' method. This method
1471   * does not change the order of the sectors in the store. However it may
1472   * change the order in which deleted sectors are reclaimed.
1473   * <p>
1474   * In a perfect world, this should never need to be called. However, it's
1475   * a good idea to call this every so often because we are assured that
1476   * the delete linked list and 'used_sector_count' variables will be
1477   * correct when the method returns.
1478   * <p>
1479   * It is not possible to repair a store that's been opened in read only
1480   * mode.
1481   */

1482  public void repair() throws IOException {
1483
1484    // Init to known states.
1485
delete_head = -1;
1486    int scount = sectorCount();
1487    int row_count = 0;
1488    int delete_count = 0;
1489
1490    byte[] mark_buffer = new byte[5];
1491
1492    for (int i = 0; i < scount; ++i) {
1493      // Read the sector
1494
readSector(i);
1495      // Is it deleted?
1496
if ((sector_buffer[0] & DELETED) != 0) {
1497        // Add this row into the list of deleted rows.
1498
int v = delete_head;
1499        mark_buffer[0] = DELETED;
1500        mark_buffer[1] = (byte) ((v >>> 24) & 0xFF);
1501        mark_buffer[2] = (byte) ((v >>> 16) & 0xFF);
1502        mark_buffer[3] = (byte) ((v >>> 8) & 0xFF);
1503        mark_buffer[4] = (byte) ((v >>> 0) & 0xFF);
1504        seekSector(i);
1505        data_store.write(mark_buffer, 0, 5);
1506        if (sector_cache != null) {
1507          // Remove from cache
1508
sector_cache.remove(new Integer JavaDoc(i));
1509        }
1510        delete_head = i;
1511
1512        ++delete_count;
1513      }
1514      else {
1515        // Add to the used sector count
1516
++row_count;
1517      }
1518    }
1519
1520    // 'delete_head' should be set correctly now,
1521
// set 'used_sector_count'
1522
used_sector_count = row_count;
1523    // Sync the information with the file
1524
synch();
1525
1526    debug.write(Lvl.MESSAGE, this,
1527                "Repair found (" + delete_count + ") deleted, (" +
1528                row_count + ") used sectors.");
1529  }
1530
1531  // ------- Diagnostics for the store -------
1532

1533  /**
1534   * Returns a string that contains diagnostic information.
1535   */

1536  public String JavaDoc statusString() throws IOException {
1537    int sc = sectorCount();
1538
1539    StringBuffer JavaDoc str = new StringBuffer JavaDoc();
1540    str.append("Sector Count: ");
1541    str.append(sc);
1542    str.append("\nSectors Used: ");
1543    str.append(getSectorUseCount());
1544    str.append("\nLocks: ");
1545    str.append(lock_count);
1546    str.append("\nFree Sectors: ");
1547    str.append(sc - getSectorUseCount());
1548    str.append("\n");
1549
1550    return new String JavaDoc(str);
1551  }
1552
1553  // ---------- Inner classes ----------
1554

1555  /**
1556   * A buffered OutputStream object that writes all data written to the stream
1557   * out to the store.
1558   */

1559  private class SectorOutputStream extends OutputStream {
1560
1561    /**
1562     * The sector buffers.
1563     */

1564    private final byte[] buf;
1565
1566    /**
1567     * The first sector we wrote to.
1568     */

1569    private int first_sector = -1;
1570
1571    /**
1572     * The cur sector to use.
1573     */

1574    private int cur_sector = -1;
1575
1576    /**
1577     * The last sector we wrote to.
1578     */

1579    private int last_sector = -1;
1580
1581    /**
1582     * Current index in the buffer
1583     */

1584    private int index;
1585
1586    /**
1587     * Total bytes written.
1588     */

1589    private int count;
1590
1591    SectorOutputStream() throws IOException {
1592      buf = new byte[getSectorSize()];
1593      index = 0;
1594      count = 0;
1595      first_sector = findFreeSector();
1596      cur_sector = first_sector;
1597    }
1598
1599    // ---------- Implemented from OutputStream ----------
1600

1601    public void write(int b) throws IOException {
1602      if (index >= buf.length) {
1603        // Flush to the next sector.
1604

1605        int next_sector = findFreeSector();
1606        if (next_sector == cur_sector) {
1607          // Nasty hack - if next_sector == cur_sector then we reclaiming
1608
// space from end of store, so increment by 1.
1609
next_sector = next_sector + 1;
1610        }
1611
1612        // Write the buffer.
1613
writeBufToSector(cur_sector, next_sector, buf, 0, index);
1614        cur_sector = next_sector;
1615        index = 0;
1616      }
1617
1618      buf[index] = (byte) b;
1619      ++index;
1620      ++count;
1621
1622    }
1623
1624    public void write(byte[] b, int offset, int len) throws IOException {
1625      while (index + len > buf.length) {
1626        // Copy
1627
int to_copy = buf.length - index;
1628        System.arraycopy(b, offset, buf, index, to_copy);
1629        offset += to_copy;
1630        len -= to_copy;
1631        index += to_copy; // Not really necessary - just gets set to 0
1632
count += to_copy;
1633
1634        int next_sector = findFreeSector();
1635        if (next_sector == cur_sector) {
1636          // Nasty hack - if next_sector == cur_sector then we reclaiming
1637
// space from end of store, so increment by 1.
1638
next_sector = next_sector + 1;
1639        }
1640        writeBufToSector(cur_sector, next_sector, buf, 0, index);
1641        cur_sector = next_sector;
1642
1643        index = 0;
1644      }
1645
1646      if (len > 0) {
1647        System.arraycopy(b, offset, buf, index, len);
1648        index += len;
1649        count += len;
1650      }
1651
1652    }
1653
1654    public void flush() throws IOException {
1655      // Flush does nothing...
1656
}
1657
1658    public void close() throws IOException {
1659      writeBufToSector(cur_sector, -1, buf, 0, index);
1660    }
1661
1662  }
1663
1664  /**
1665   * An input stream that reads information across a sector chain starting at
1666   * the given head sector.
1667   */

1668  private final class SectorInputStream extends InputStream {
1669
1670    /**
1671     * The current sector we are traversing.
1672     */

1673    private int sector;
1674
1675    /**
1676     * Current index in buf.
1677     */

1678    private int index;
1679
1680    /**
1681     * The number of bytes we have read.
1682     */

1683    private int count;
1684
1685    /**
1686     * A reference to the sector buffer.
1687     */

1688    private byte[] sector_buffer;
1689
1690
1691    /**
1692     * Constructor.
1693     */

1694    SectorInputStream(int sector_head) throws IOException {
1695      this.sector = sector_head;
1696      this.sector_buffer = FixedSizeDataStore.this.sector_buffer;
1697
1698      // Load the first sector.
1699
loadNextSector();
1700
1701      count = 0;
1702    }
1703
1704    /**
1705     * Loads the next sector in the chain into sector_buffer and sets index
1706     * to the start of the buffer.
1707     */

1708    private void loadNextSector() throws IOException {
1709      if (sector != -1) {
1710        // Read contents into 'sector_buffer'
1711
readSector(sector);
1712      }
1713      index = EXTRA_SECTOR_SIZE;
1714      // The next sector
1715
sector = ByteArrayUtil.getInt(sector_buffer, 1);
1716    }
1717
1718    // ---------- Implemented from InputStream ----------
1719

1720    public final int read() throws IOException {
1721
1722      int b = ((int) sector_buffer[index]) & 0x0FF;
1723      ++index;
1724      ++count;
1725      if (index >= sector_size) {
1726        loadNextSector();
1727      }
1728      return b;
1729
1730    }
1731
1732    public int read(byte[] b, int offset, int len) throws IOException {
1733      int original_len = len;
1734
1735      while (index + len > sector_size) {
1736        // Copy
1737
int to_copy = sector_size - index;
1738        System.arraycopy(sector_buffer, index, b, offset, to_copy);
1739        offset += to_copy;
1740        len -= to_copy;
1741        index += to_copy; // Not really necessary - just gets set to 0
1742
count += to_copy;
1743
1744        // Load the next sector.
1745
loadNextSector();
1746
1747      }
1748
1749      if (len > 0) {
1750        System.arraycopy(sector_buffer, index, b, offset, len);
1751        index += len;
1752        count += len;
1753
1754        if (index >= sector_size) {
1755          loadNextSector();
1756        }
1757
1758      }
1759
1760      return original_len;
1761    }
1762
1763    public long skip(long len) throws IOException {
1764      long original_len = len;
1765
1766      while (index + len > sector_size) {
1767        int to_copy = sector_size - index;
1768        len -= to_copy;
1769        index += to_copy; // Not really necessary - just gets set to 0
1770
count += to_copy;
1771
1772        // Load the next sector.
1773
loadNextSector();
1774
1775      }
1776
1777      if (len > 0) {
1778        index += len;
1779        count += len;
1780
1781        if (index >= sector_size) {
1782          loadNextSector();
1783        }
1784
1785      }
1786
1787      return original_len;
1788    }
1789
1790  }
1791
1792}
1793
Popular Tags