KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > db > store > Store


1 /*
2  * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
3  *
4  * This file is part of Resin(R) Open Source
5  *
6  * Each copy or derived work must preserve the copyright notice and this
7  * notice unmodified.
8  *
9  * Resin Open Source is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Resin Open Source is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
17  * of NON-INFRINGEMENT. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Resin Open Source; if not, write to the
22  *
23  * Free Software Foundation, Inc.
24  * 59 Temple Place, Suite 330
25  * Boston, MA 02111-1307 USA
26  *
27  * @author Scott Ferguson
28  */

29
30 package com.caucho.db.store;
31
32 import com.caucho.db.Database;
33 import com.caucho.lifecycle.Lifecycle;
34 import com.caucho.log.Log;
35 import com.caucho.sql.SQLExceptionWrapper;
36 import com.caucho.util.L10N;
37 import com.caucho.vfs.Path;
38 import com.caucho.vfs.RandomAccessStream;
39
40 import java.io.IOException JavaDoc;
41 import java.lang.ref.SoftReference JavaDoc;
42 import java.sql.SQLException JavaDoc;
43 import java.util.logging.Level JavaDoc;
44 import java.util.logging.Logger JavaDoc;
45
46 /**
47  * The store manages the block-based persistent store file. Each table
48  * will have its own store file, table.db.
49  *
50  * The store is block-based, where each block is 64k. Block allocation
51  * is tracked by a free block, block 0. Each block is represented as a
52  * two-byte value. The first byte is the allocation code: free, row,
53  * or used. The second byte is a fragment allocation mask.
54  *
55  * Since 64k stores 32k entries, the allocation block can handle
56  * a 2G database size. If the database is larger, another free block
57  * occurs at block 32k handling another 2G.
58  *
59  * The blocks are marked as free (00), row (01), used (10) or fragment(11).
60  * Row-blocks are table rows, so a table iterator will only look at
61  * the row blocks. Used blocks are for special blocks like the
62  * free list. Fragments are for blobs.
63  *
64  * Each store has a unique id in the database. The store id is merged with
65  * the block number in the store to create a unique block id. There are
66  * 64k allowed stores (and therefore 64k tables), leaving 64 - 16 = 48 bits
67  * for the blocks in a table, i.e. 2 ^ 48 blocks = 256T blocks.
68  *
69  * block index: the block number in the file.
70  *
71  * address: the address of a byte within the store, treating the file as a
72  * flat file.
73  *
74  * block id: the unique id of the block in the database.
75  *
76  * <h3>Blobs and fragments</h3>
77  *
78  * Fragments are stored in 8k chunks with a single byte prefix indicating
79  * its use.
80  *
81  * <h3>Transactions</h3>
82  *
83  * Fragments are not associated with transactions. The rollback is
84  * associated with a transaction.
85  */

86 public class Store {
87   private final static Logger JavaDoc log = Log.open(Store.class);
88   private final static L10N L = new L10N(Store.class);
89   
90   public final static int BLOCK_BITS = 16;
91   public final static int BLOCK_SIZE = 1 << BLOCK_BITS;
92   public final static long BLOCK_INDEX_MASK = BLOCK_SIZE - 1;
93   public final static long BLOCK_MASK = ~ BLOCK_INDEX_MASK;
94   public final static long BLOCK_OFFSET_MASK = BLOCK_SIZE - 1;
95
96   private final static int ALLOC_BYTES_PER_BLOCK = 2;
97
98   private final static int ALLOC_CHUNK_SIZE = 1024 * ALLOC_BYTES_PER_BLOCK;
99   
100   public final static int ALLOC_FREE = 0x00;
101   public final static int ALLOC_ROW = 0x01;
102   public final static int ALLOC_USED = 0x02;
103   public final static int ALLOC_FRAGMENT = 0x03;
104   public final static int ALLOC_INDEX = 0x04;
105   public final static int ALLOC_MASK = 0x0f;
106
107   public final static int FRAGMENT_SIZE = 8 * 1024;
108   public final static int FRAGMENT_PER_BLOCK
109     = (int) (BLOCK_SIZE / FRAGMENT_SIZE);
110   
111   public final static long DATA_START = BLOCK_SIZE;
112   
113   public final static int STORE_CREATE_END = 1024;
114   
115   protected final Database _database;
116   protected final BlockManager _blockManager;
117
118   private final String JavaDoc _name;
119   
120   private int _id;
121
122   private Path _path;
123
124   // If true, dirty blocks are written at the end of the transaction.
125
// Otherwise, they are buffered
126
private boolean _isFlushDirtyBlocksOnCommit = true;
127
128   private long _fileSize;
129   private long _blockCount;
130
131   private final Object JavaDoc _allocationLock = new Object JavaDoc();
132   private byte []_allocationTable;
133   
134   private final Object JavaDoc _allocationWriteLock = new Object JavaDoc();
135   private int _allocDirtyMin = Integer.MAX_VALUE;
136   private int _allocDirtyMax;
137   
138   private final Object JavaDoc _fragmentLock = new Object JavaDoc();
139
140   private final Object JavaDoc _statLock = new Object JavaDoc();
141   // number of fragments currently used
142
private long _fragmentUseCount;
143
144   private SoftReference JavaDoc<RandomAccessWrapper> _cachedRowFile;
145   
146   private Lock _rowLock;
147   
148   private final Lifecycle _lifecycle = new Lifecycle();
149
150   public Store(Database database, String JavaDoc name, Lock tableLock)
151   {
152     this(database, name, tableLock, database.getPath().lookup(name + ".db"));
153   }
154
155   /**
156    * Creates a new store.
157    *
158    * @param database the owning database.
159    * @param name the store name
160    * @param lock the table lock
161    * @param path the path to the files
162    */

163   public Store(Database database, String JavaDoc name, Lock rowLock, Path path)
164   {
165     _database = database;
166     _blockManager = _database.getBlockManager();
167     
168     _name = name;
169     _path = path;
170
171     if (path == null)
172       throw new NullPointerException JavaDoc();
173
174     _id = _blockManager.allocateStoreId();
175
176     if (rowLock == null)
177       rowLock = new Lock("row-lock:" + _name + ":" + _id);
178
179     _rowLock = rowLock;
180   }
181
182   /**
183    * Creates an independent store.
184    */

185   public static Store create(Path path)
186     throws IOException JavaDoc, SQLException JavaDoc
187   {
188     Database db = new Database();
189     db.init();
190
191     Store store = new Store(db, "temp", null, path);
192
193     if (path.canRead())
194       store.init();
195     else
196       store.create();
197
198     return store;
199   }
200
201   /**
202    * If true, dirty blocks are written at commit time.
203    */

204   public void setFlushDirtyBlocksOnCommit(boolean flushOnCommit)
205   {
206     _isFlushDirtyBlocksOnCommit = flushOnCommit;
207   }
208
209   /**
210    * If true, dirty blocks are written at commit time.
211    */

212   public boolean isFlushDirtyBlocksOnCommit()
213   {
214     return _isFlushDirtyBlocksOnCommit;
215   }
216
217   /**
218    * Returns the store's name.
219    */

220   public String JavaDoc getName()
221   {
222     return _name;
223   }
224
225   /**
226    * Returns the store's id.
227    */

228   public int getId()
229   {
230     return _id;
231   }
232
233   /**
234    * Returns the table's lock.
235    */

236   public Lock getLock()
237   {
238     return _rowLock;
239   }
240
241   /**
242    * Returns the block manager.
243    */

244   public BlockManager getBlockManager()
245   {
246     return _blockManager;
247   }
248
249   /**
250    * Returns the file size.
251    */

252   public long getFileSize()
253   {
254     return _fileSize;
255   }
256
257   /**
258    * Returns the block count.
259    */

260   public long getBlockCount()
261   {
262     return _blockCount;
263   }
264
265   /**
266    * Converts from the block index to the address for database
267    * storage.
268    */

269   private static long blockIndexToAddr(long blockIndex)
270   {
271     return blockIndex << BLOCK_BITS;
272   }
273
274   /**
275    * Converts from the block index to the unique block id.
276    */

277   private final long blockIndexToBlockId(long blockIndex)
278   {
279     return (blockIndex << BLOCK_BITS) + _id;
280   }
281
282   /**
283    * Converts from the block index to the address for database
284    * storage.
285    */

286   private static long blockIdToIndex(long blockId)
287   {
288     return blockId >> BLOCK_BITS;
289   }
290
291   /**
292    * Converts from the block index to the unique block id.
293    */

294   public final long addressToBlockId(long address)
295   {
296     return (address & BLOCK_MASK) + _id;
297   }
298
299   /**
300    * Converts from the block index to the unique block id.
301    */

302   public static long blockIdToAddress(long blockId)
303   {
304     return (blockId & BLOCK_MASK);
305   }
306
307   /**
308    * Converts from the block index to the unique block id.
309    */

310   public static long blockIdToAddress(long blockId, int offset)
311   {
312     return (blockId & BLOCK_MASK) + offset;
313   }
314
315   /**
316    * Returns the current number of fragments used.
317    */

318   public long getTotalFragmentSize()
319   {
320     return _fragmentUseCount * FRAGMENT_SIZE;
321   }
322
323   /**
324    * Creates the store.
325    */

326   public void create()
327     throws IOException JavaDoc, SQLException JavaDoc
328   {
329     if (! _lifecycle.toActive())
330       return;
331     
332     log.finer(this + " create");
333
334     _path.getParent().mkdirs();
335
336     if (_path.exists())
337       throw new SQLException JavaDoc(L.l("Table `{0}' already exists. CREATE can not override an existing table.", _name));
338
339     _allocationTable = new byte[ALLOC_CHUNK_SIZE];
340
341     // allocates the allocation table itself
342
setAllocation(0, ALLOC_USED);
343     // allocates the header information
344
setAllocation(1, ALLOC_USED);
345
346     byte []buffer = new byte[BLOCK_SIZE];
347     writeBlock(0, buffer, 0, BLOCK_SIZE);
348     writeBlock(BLOCK_SIZE, buffer, 0, BLOCK_SIZE);
349     
350     writeBlock(0, _allocationTable, 0, _allocationTable.length);
351
352     _blockCount = 2;
353   }
354   
355   public void init()
356     throws IOException JavaDoc
357   {
358     if (! _lifecycle.toActive())
359       return;
360     
361     log.finer(this + " init");
362
363     RandomAccessWrapper wrapper = openRowFile();
364
365     try {
366       RandomAccessStream file = wrapper.getFile();
367       
368       _fileSize = file.getLength();
369       _blockCount = ((_fileSize + BLOCK_SIZE - 1) / BLOCK_SIZE);
370
371       int allocCount = (int) (_blockCount * ALLOC_BYTES_PER_BLOCK);
372
373       allocCount += ALLOC_CHUNK_SIZE - allocCount % ALLOC_CHUNK_SIZE;
374
375       _allocationTable = new byte[allocCount];
376
377       for (int i = 0; i < allocCount; i += BLOCK_SIZE) {
378     int len = allocCount - i;
379     
380     if (BLOCK_SIZE < len)
381       len = BLOCK_SIZE;
382     
383     readBlock((long) i / ALLOC_BYTES_PER_BLOCK * BLOCK_SIZE,
384           _allocationTable, i, len);
385       }
386     } finally {
387       wrapper.close();
388     }
389   }
390
391   public void remove()
392     throws SQLException JavaDoc
393   {
394     try {
395       _path.remove();
396     } catch (IOException JavaDoc e) {
397       throw new SQLExceptionWrapper(e);
398     }
399   }
400
401   /**
402    * Returns the first block id which contains a row.
403    *
404    * @return the block id of the first row block
405    */

406   public long firstRow(long blockId)
407     throws IOException JavaDoc
408   {
409     return firstBlock(blockId, ALLOC_ROW);
410   }
411
412   /**
413    * Returns the first block id which contains a fragment.
414    *
415    * @return the block id of the first row block
416    */

417   public long firstFragment(long blockId)
418     throws IOException JavaDoc
419   {
420     return firstBlock(blockId, ALLOC_FRAGMENT);
421   }
422
423   /**
424    * Returns the first block id which contains a row.
425    *
426    * @return the block id of the first row block
427    */

428   public long firstBlock(long blockId, int type)
429     throws IOException JavaDoc
430   {
431     if (blockId <= BLOCK_SIZE)
432       blockId = BLOCK_SIZE;
433     
434     long blockIndex = blockId >> BLOCK_BITS;
435
436     synchronized (_allocationLock) {
437       for (; blockIndex < _blockCount; blockIndex++) {
438     if (getAllocation(blockIndex) == type)
439       return blockIndexToBlockId(blockIndex);
440       }
441     }
442
443     return -1;
444   }
445
446   /**
447    * Returns the matching block.
448    */

449   public final Block readBlock(long blockAddress)
450     throws IOException JavaDoc
451   {
452     long blockId = addressToBlockId(blockAddress);
453     
454     Block block = _blockManager.getBlock(this, blockId);
455
456     try {
457       block.read();
458     
459       return block;
460     } catch (IOException JavaDoc e) {
461       block.free();
462
463       throw e;
464     } catch (RuntimeException JavaDoc e) {
465       block.free();
466
467       throw e;
468     }
469   }
470
471   /**
472    * Allocates a new block for a row.
473    *
474    * @return the block id of the allocated block.
475    */

476   public Block allocateRow()
477     throws IOException JavaDoc
478   {
479     return allocateBlock(ALLOC_ROW);
480   }
481
482   /**
483    * Return true if the block is a row block.
484    */

485   public boolean isRowBlock(long blockAddress)
486   {
487     return getAllocation(blockAddress / BLOCK_SIZE) == ALLOC_ROW;
488   }
489
490   /**
491    * Allocates a new block for a non-row.
492    *
493    * @return the block id of the allocated block.
494    */

495   public Block allocateBlock()
496     throws IOException JavaDoc
497   {
498     return allocateBlock(ALLOC_USED);
499   }
500
501   /**
502    * Allocates a new block for a fragment
503    *
504    * @return the block id of the allocated block.
505    */

506   private Block allocateFragmentBlock()
507     throws IOException JavaDoc
508   {
509     return allocateBlock(ALLOC_FRAGMENT);
510   }
511
512   /**
513    * Allocates a new block for an index
514    *
515    * @return the block id of the allocated block.
516    */

517   public Block allocateIndexBlock()
518     throws IOException JavaDoc
519   {
520     return allocateBlock(ALLOC_INDEX);
521   }
522
523   /**
524    * Return true if the block is an index block.
525    */

526   public boolean isIndexBlock(long blockAddress)
527   {
528     return getAllocation(blockAddress / BLOCK_SIZE) == ALLOC_INDEX;
529   }
530
531   /**
532    * Allocates a new block.
533    *
534    * @return the block id of the allocated block.
535    */

536   private Block allocateBlock(int code)
537     throws IOException JavaDoc
538   {
539     long blockIndex;
540     boolean isFileExtended = false;
541
542     /*
543     switch (code) {
544     case ALLOC_ROW:
545       System.out.println("ALLOC_ROW:");
546       break;
547     case ALLOC_INDEX:
548       System.out.println("ALLOC_INDEX:");
549       break;
550     case ALLOC_FRAGMENT:
551       System.out.println("ALLOC_FRAG:");
552       break;
553     }
554     */

555
556     synchronized (_allocationLock) {
557       long end = _blockCount;
558
559       if (_allocationTable.length < ALLOC_BYTES_PER_BLOCK * end)
560     end = _allocationTable.length / ALLOC_BYTES_PER_BLOCK;
561
562       for (blockIndex = 0; blockIndex < end; blockIndex++) {
563     if (getAllocation(blockIndex) == ALLOC_FREE)
564       break;
565       }
566
567       if (_allocationTable.length <= ALLOC_BYTES_PER_BLOCK * blockIndex) {
568     // expand the allocation table
569
byte []newTable = new byte[_allocationTable.length + ALLOC_CHUNK_SIZE];
570     System.arraycopy(_allocationTable, 0,
571              newTable, 0,
572              _allocationTable.length);
573     _allocationTable = newTable;
574
575     // if the allocation table is over 32k, allocate the block for the
576
// extension (each allocation block of 32k allocates 2G)
577
if (blockIndex % (BLOCK_SIZE / ALLOC_BYTES_PER_BLOCK) == 0) {
578       setAllocation(blockIndex, ALLOC_USED);
579       blockIndex++;
580     }
581       }
582
583       // mark USED before actual code so it's properly initialized
584
setAllocation(blockIndex, ALLOC_USED);
585
586       if (log.isLoggable(Level.FINE))
587     log.fine(this + " allocating block " + blockIndex + " " + codeToName(code));
588
589       if (_blockCount <= blockIndex) {
590     isFileExtended = true;
591     _blockCount = blockIndex + 1;
592       }
593     }
594
595     long blockId = blockIndexToBlockId(blockIndex);
596
597     Block block = _blockManager.getBlock(this, blockId);
598
599     byte []buffer = block.getBuffer();
600
601     for (int i = BLOCK_SIZE - 1; i >= 0; i--)
602       buffer[i] = 0;
603
604     block.setDirty(0, BLOCK_SIZE);
605
606     // if extending file, write the contents now
607
if (isFileExtended) {
608       try {
609     block.write();
610       } catch (IOException JavaDoc e) {
611     log.log(Level.WARNING, e.toString(), e);
612       }
613     }
614
615     synchronized (_allocationLock) {
616       setAllocation(blockIndex, code);
617     }
618     
619     saveAllocation();
620
621     return block;
622   }
623
624   /**
625    * Check that an allocated block is valid.
626    */

627   protected void validateBlockId(long blockId)
628     throws IllegalArgumentException JavaDoc, IllegalStateException JavaDoc
629   {
630     RuntimeException JavaDoc e = null;
631     
632     if (isClosed())
633       e = new IllegalStateException JavaDoc(L.l("store {0} is closing.", this));
634     else if (getId() <= 0)
635       e = new IllegalStateException JavaDoc(L.l("invalid store {0}.", this));
636     else if (getId() != (blockId & BLOCK_INDEX_MASK)) {
637       e = new IllegalArgumentException JavaDoc(L.l("block {0} must match store {1}.",
638                          blockId & BLOCK_INDEX_MASK,
639                          this));
640     }
641
642     if (e != null)
643       throw e;
644   }
645
646   /**
647    * Check that an allocated block is valid.
648    */

649   protected void assertStoreActive()
650     throws IllegalStateException JavaDoc
651   {
652     RuntimeException JavaDoc e = null;
653     
654     if (isClosed())
655       e = new IllegalStateException JavaDoc(L.l("store {0} is closing.", this));
656     else if (getId() <= 0)
657       e = new IllegalStateException JavaDoc(L.l("invalid store {0}.", this));
658
659     if (e != null)
660       throw e;
661   }
662   
663   /**
664    * Frees a block.
665    *
666    * @return the block id of the allocated block.
667    */

668   protected void freeBlock(long blockId)
669     throws IOException JavaDoc
670   {
671     if (blockId == 0)
672       return;
673     
674     synchronized (_allocationLock) {
675       setAllocation(blockIdToIndex(blockId), ALLOC_FREE);
676     }
677
678     saveAllocation();
679   }
680
681   /**
682    * Sets the allocation for a block.
683    */

684   private final int getAllocation(long blockIndex)
685   {
686     int allocOffset = (int) (ALLOC_BYTES_PER_BLOCK * blockIndex);
687
688     return _allocationTable[allocOffset] & ALLOC_MASK;
689   }
690
691   /**
692    * Sets the allocation for a block.
693    */

694   private void setAllocation(long blockIndex, int code)
695   {
696     int allocOffset = (int) (ALLOC_BYTES_PER_BLOCK * blockIndex);
697     
698     for (int i = 1; i < ALLOC_BYTES_PER_BLOCK; i++)
699       _allocationTable[allocOffset + i] = 0;
700
701     _allocationTable[allocOffset] = (byte) code;
702
703     setAllocDirty(allocOffset, allocOffset + ALLOC_BYTES_PER_BLOCK);
704   }
705
706   /**
707    * Sets the dirty range for the allocation table.
708    */

709   private void setAllocDirty(int min, int max)
710   {
711     if (min < _allocDirtyMin)
712       _allocDirtyMin = min;
713     
714     if (_allocDirtyMax < max)
715       _allocDirtyMax = max;
716   }
717
718   /**
719    * Sets the allocation for a block.
720    */

721   void saveAllocation()
722     throws IOException JavaDoc
723   {
724     // cache doesn't actually need to write this data
725
if (! _isFlushDirtyBlocksOnCommit)
726       return;
727     
728     synchronized (_allocationWriteLock) {
729       int dirtyMin;
730       int dirtyMax;
731
732       synchronized (_allocationLock) {
733     dirtyMin = _allocDirtyMin;
734     _allocDirtyMin = Integer.MAX_VALUE;
735     
736     dirtyMax = _allocDirtyMax;
737     _allocDirtyMax = 0;
738       }
739
740       // Write each dirty block to disk. The physical blocks are
741
// broken up each BLOCK_SIZE / ALLOC_BYTES_PER_BLOCK.
742
for (;
743        dirtyMin < dirtyMax;
744        dirtyMin = (dirtyMin + BLOCK_SIZE) - dirtyMin % BLOCK_SIZE) {
745     int block = dirtyMin / (BLOCK_SIZE / ALLOC_BYTES_PER_BLOCK);
746     
747     int offset = dirtyMin % BLOCK_SIZE;
748     int length;
749
750     if (dirtyMin / BLOCK_SIZE != dirtyMax / BLOCK_SIZE)
751       length = BLOCK_SIZE - offset;
752     else
753       length = dirtyMax - dirtyMin;
754
755     writeBlock((long) block * BLOCK_SIZE + offset,
756            _allocationTable, offset, length);
757       }
758     }
759   }
760   
761   /**
762    * Reads a fragment.
763    *
764    * @param fragmentAddress the address of the fragment
765    * @param fragmentOffset the offset inside the fragment to start reading
766    * @param buffer the result buffer
767    * @param offset offset into the result buffer
768    * @param length the number of bytes to read
769    *
770    * @return the number of bytes read
771    */

772   public int readFragment(long fragmentAddress, int fragmentOffset,
773               byte []buffer, int offset, int length)
774     throws IOException JavaDoc
775   {
776     if (FRAGMENT_SIZE - fragmentOffset < length) {
777       // server/13df
778
throw new IllegalArgumentException JavaDoc(L.l("read offset {0} length {1} too long",
779                          fragmentOffset, length));
780     }
781
782     Block block = readBlock(addressToBlockId(fragmentAddress));
783
784     try {
785       int blockOffset = getFragmentOffset(fragmentAddress);
786
787       byte []blockBuffer = block.getBuffer();
788
789       synchronized (blockBuffer) {
790     System.arraycopy(blockBuffer, blockOffset + fragmentOffset,
791              buffer, offset, length);
792       }
793
794       return length;
795     } finally {
796       block.free();
797     }
798   }
799   
800   /**
801    * Reads a fragment for a clob.
802    *
803    * @param fragmentAddress the address of the fragment
804    * @param fragmentOffset the offset inside the fragment to start reading
805    * @param buffer the result buffer
806    * @param offset offset into the result buffer
807    * @param length the length of the fragment in characters
808    *
809    * @return the number of characters read
810    */

811   public int readFragment(long fragmentAddress, int fragmentOffset,
812               char []buffer, int offset, int length)
813     throws IOException JavaDoc
814   {
815     if (FRAGMENT_SIZE - fragmentOffset < 2 * length) {
816       // server/13df
817
throw new IllegalArgumentException JavaDoc(L.l("read offset {0} length {1} too long",
818                          fragmentOffset, length));
819     }
820
821     Block block = readBlock(addressToBlockId(fragmentAddress));
822
823     try {
824       int blockOffset = getFragmentOffset(fragmentAddress);
825       blockOffset += fragmentOffset;
826
827       byte []blockBuffer = block.getBuffer();
828       
829       synchronized (blockBuffer) {
830     for (int i = 0; i < length; i++) {
831       int ch1 = blockBuffer[blockOffset] & 0xff;
832       int ch2 = blockBuffer[blockOffset + 1] & 0xff;
833
834       buffer[offset + i] = (char) ((ch1 << 8) + ch2);
835
836       blockOffset += 2;
837     }
838       }
839
840       return length;
841     } finally {
842       block.free();
843     }
844   }
845   
846   /**
847    * Reads a long value from a fragment.
848    *
849    * @return the long value
850    */

851   public long readFragmentLong(long fragmentAddress,
852                    int fragmentOffset)
853     throws IOException JavaDoc
854   {
855     Block block = readBlock(addressToBlockId(fragmentAddress));
856
857     try {
858       int blockOffset = getFragmentOffset(fragmentAddress);
859
860       byte []blockBuffer = block.getBuffer();
861
862       synchronized (blockBuffer) {
863     return readLong(blockBuffer, blockOffset + fragmentOffset);
864       }
865     } finally {
866       block.free();
867     }
868   }
869   
870   /**
871    * Allocates a new fragment.
872    *
873    * @return the fragment address
874    */

875   public long allocateFragment(StoreTransaction xa)
876     throws IOException JavaDoc
877   {
878     while (true) {
879       synchronized (_allocationLock) {
880     byte []allocationTable = _allocationTable;
881       
882     for (int i = 0; i < allocationTable.length; i += ALLOC_BYTES_PER_BLOCK) {
883       int fragMask = allocationTable[i + 1] & 0xff;
884
885       if (allocationTable[i] == ALLOC_FRAGMENT && fragMask != 0xff) {
886         for (int j = 0; j < FRAGMENT_PER_BLOCK; j++) {
887           if ((fragMask & (1 << j)) == 0) {
888         allocationTable[i + 1] = (byte) (fragMask | (1 << j));
889
890         setAllocDirty(i + 1, i + 2);
891
892         _fragmentUseCount++;
893
894         long fragmentAddress
895           = BLOCK_SIZE * ((long) i / ALLOC_BYTES_PER_BLOCK) + j;
896         
897         //System.out.println((fragmentAddress / BLOCK_SIZE) + ":" + j + " ALLOCATE");
898
return fragmentAddress;
899           }
900         }
901       }
902     }
903       }
904
905       // if no fragment, allocate a new one.
906

907       Block block = allocateFragmentBlock();
908       block.free();
909     }
910   }
911   
912   /**
913    * Deletes a fragment.
914    */

915   public void deleteFragment(StoreTransaction xa, long fragmentAddress)
916     throws IOException JavaDoc
917   {
918     synchronized (_allocationLock) {
919       int i = (int) (ALLOC_BYTES_PER_BLOCK * (fragmentAddress / BLOCK_SIZE));
920       int j = (int) (fragmentAddress & 0xff);
921
922       int fragMask = _allocationTable[i + 1] & 0xff;
923       //System.out.println((fragmentAddress / BLOCK_SIZE) + ":" + j + " DELETE");
924

925       if (_allocationTable[i] != ALLOC_FRAGMENT)
926     System.out.println("BAD ENTRY: " + fragMask);
927
928       if (j >= 8)
929     System.out.println("BAD J: " + fragMask);
930
931       if ((fragMask & (1 << j)) == 0) {
932     log.fine("BAD J-MASK: " + fragMask + " " + j);
933       }
934
935       _allocationTable[i + 1] = (byte) (fragMask & ~(1 << j));
936
937       _fragmentUseCount--;
938     
939       setAllocDirty(i + 1, i + 2);
940     }
941   }
942   
943   /**
944    * Writes a fragment.
945    *
946    * @param xa the owning transaction
947    * @param fragmentAddress the fragment to write
948    * @param fragmentOffset the offset into the fragment
949    * @param buffer the write buffer
950    * @param offset offset into the write buffer
951    * @param length the number of bytes to write
952    *
953    * @return the fragment id
954    */

955   public void writeFragment(StoreTransaction xa,
956                 long fragmentAddress, int fragmentOffset,
957                 byte []buffer, int offset, int length)
958     throws IOException JavaDoc
959   {
960     if (FRAGMENT_SIZE - fragmentOffset < length)
961       throw new IllegalArgumentException JavaDoc(L.l("write offset {0} length {1} too long",
962                          fragmentOffset, length));
963     
964     Block block = xa.readBlock(this, addressToBlockId(fragmentAddress));
965
966     try {
967       xa.addUpdateFragmentBlock(block);
968       
969       int blockOffset = getFragmentOffset(fragmentAddress);
970
971       byte []blockBuffer = block.getBuffer();
972
973       blockOffset += fragmentOffset;
974
975       synchronized (blockBuffer) {
976     System.arraycopy(buffer, offset,
977              blockBuffer, blockOffset,
978              length);
979
980     block.setDirty(blockOffset, blockOffset + length);
981       }
982     } finally {
983       block.free();
984     }
985   }
986   
987   /**
988    * Writes a character based
989    *
990    * @param fragmentAddress the fragment to write
991    * @param fragmentOffset the offset into the fragment
992    * @param buffer the write buffer
993    * @param offset offset into the write buffer
994    * @param length the number of bytes to write
995    */

996   public void writeFragment(StoreTransaction xa,
997                 long fragmentAddress, int fragmentOffset,
998                 char []buffer, int offset, int length)
999     throws IOException JavaDoc
1000  {
1001    if (FRAGMENT_SIZE - fragmentOffset < length)
1002      throw new IllegalArgumentException JavaDoc(L.l("write offset {0} length {1} too long",
1003                         fragmentOffset, length));
1004    
1005    Block block = xa.readBlock(this, addressToBlockId(fragmentAddress));
1006
1007    try {
1008      block = xa.createAutoCommitWriteBlock(block);
1009    
1010      int blockOffset = getFragmentOffset(fragmentAddress);
1011
1012      byte []blockBuffer = block.getBuffer();
1013
1014      blockOffset += fragmentOffset;
1015
1016      synchronized (blockBuffer) {
1017    int blockTail = blockOffset;
1018    
1019    for (int i = 0; i < length; i++) {
1020      char ch = buffer[offset + i];
1021
1022      blockBuffer[blockTail] = (byte) (ch >> 8);
1023      blockBuffer[blockTail + 1] = (byte) (ch);
1024
1025      blockTail += 2;
1026    }
1027
1028    block.setDirty(blockOffset, blockTail);
1029      }
1030    } finally {
1031      block.free();
1032    }
1033  }
1034  
1035  /**
1036   * Writes a long value to a fragment.
1037   *
1038   * @return the long value
1039   */

1040  public void writeFragmentLong(StoreTransaction xa,
1041                long fragmentAddress, int fragmentOffset,
1042                long value)
1043    throws IOException JavaDoc
1044  {
1045    Block block = xa.readBlock(this, addressToBlockId(fragmentAddress));
1046
1047    try {
1048      xa.addUpdateBlock(block);
1049      
1050      int blockOffset = getFragmentOffset(fragmentAddress);
1051
1052      byte []blockBuffer = block.getBuffer();
1053      int offset = blockOffset + fragmentOffset;
1054
1055      synchronized (blockBuffer) {
1056    writeLong(blockBuffer, offset, value);
1057
1058    block.setDirty(offset, offset + 8);
1059      }
1060    } finally {
1061      block.free();
1062    }
1063  }
1064
1065  /**
1066   * Returns the fragment offset for an id.
1067   */

1068  private int getFragmentOffset(long fragmentAddress)
1069  {
1070    int id = (int) (fragmentAddress & BLOCK_OFFSET_MASK);
1071
1072    return (int) (FRAGMENT_SIZE * id);
1073  }
1074
1075  /**
1076   * Reads a block into the buffer.
1077   */

1078  public void readBlock(long blockId, byte []buffer, int offset, int length)
1079    throws IOException JavaDoc
1080  {
1081    RandomAccessWrapper wrapper = openRowFile();
1082    RandomAccessStream is = wrapper.getFile();
1083
1084    long blockAddress = blockId & BLOCK_MASK;
1085
1086    try {
1087      if (blockAddress < 0 || _fileSize < blockAddress + length) {
1088    throw new IllegalStateException JavaDoc(L.l("block at {0} is invalid for file {1} (length {2})",
1089                        Long.toHexString(blockAddress),
1090                        _path,
1091                        Long.toHexString(_fileSize)));
1092      }
1093
1094      int readLen = is.read(blockAddress, buffer, offset, length);
1095
1096      if (readLen < 0) {
1097    for (int i = 0; i < BLOCK_SIZE; i++)
1098      buffer[i] = 0;
1099      }
1100      
1101      freeRowFile(wrapper);
1102      wrapper = null;
1103    } finally {
1104      if (wrapper != null)
1105    wrapper.close();
1106    }
1107  }
1108
1109  /**
1110   * Saves the buffer to the database.
1111   */

1112  public void writeBlock(long blockAddress,
1113             byte []buffer, int offset, int length)
1114    throws IOException JavaDoc
1115  {
1116    RandomAccessWrapper wrapper = openRowFile();
1117    RandomAccessStream os = wrapper.getFile();
1118    
1119    try {
1120      os.write(blockAddress, buffer, offset, length);
1121      
1122      freeRowFile(wrapper);
1123      wrapper = null;
1124      
1125      if (_fileSize < blockAddress + length) {
1126    _fileSize = blockAddress + length;
1127      }
1128      
1129    } finally {
1130      if (wrapper != null)
1131    wrapper.close();
1132    }
1133  }
1134
1135  /**
1136   * Opens the underlying file to the database.
1137   */

1138  private RandomAccessWrapper openRowFile()
1139    throws IOException JavaDoc
1140  {
1141    RandomAccessStream file = null;
1142    RandomAccessWrapper wrapper = null;
1143    
1144    synchronized (this) {
1145      SoftReference JavaDoc<RandomAccessWrapper> ref = _cachedRowFile;
1146      _cachedRowFile = null;
1147      
1148      if (ref != null) {
1149    wrapper = ref.get();
1150      }
1151    }
1152
1153    if (wrapper != null)
1154      file = wrapper.getFile();
1155
1156    if (file == null) {
1157      file = _path.openRandomAccess();
1158
1159      wrapper = new RandomAccessWrapper(file);
1160    }
1161
1162    return wrapper;
1163  }
1164
1165  private void freeRowFile(RandomAccessWrapper wrapper)
1166    throws IOException JavaDoc
1167  {
1168    synchronized (this) {
1169      if (_cachedRowFile == null) {
1170    _cachedRowFile = new SoftReference JavaDoc<RandomAccessWrapper>(wrapper);
1171    return;
1172      }
1173    }
1174
1175    wrapper.close();
1176  }
1177
1178  /**
1179   * Writes the short.
1180   */

1181  private static void writeShort(byte []buffer, int offset, int v)
1182  {
1183    buffer[offset + 0] = (byte) (v >> 8);
1184    buffer[offset + 1] = (byte) (v);
1185  }
1186
1187  /**
1188   * Reads a short.
1189   */

1190  private static int readShort(byte []buffer, int offset)
1191  {
1192    return (((buffer[offset + 0] & 0xff) << 8) |
1193        ((buffer[offset + 1] & 0xff)));
1194  }
1195
1196  /**
1197   * Flush the store.
1198   */

1199  public void flush()
1200  {
1201    if (_lifecycle.isActive()) {
1202      if (_blockManager != null) {
1203    _blockManager.flush(this);
1204      }
1205    }
1206  }
1207
1208  /**
1209   * True if destroyed.
1210   */

1211  public boolean isClosed()
1212  {
1213    return _lifecycle.isDestroyed();
1214  }
1215  
1216  /**
1217   * Closes the store.
1218   */

1219  public void close()
1220  {
1221    if (! _lifecycle.toDestroy())
1222      return;
1223
1224    log.finer(this + " closing");
1225
1226    if (_blockManager != null) {
1227      _blockManager.freeStore(this);
1228      _blockManager.freeStoreId(_id);
1229    }
1230
1231    long id = _id;
1232    _id = 0;
1233
1234    _path = null;
1235    
1236    RandomAccessWrapper wrapper = null;
1237    
1238    SoftReference JavaDoc<RandomAccessWrapper> ref = _cachedRowFile;
1239    _cachedRowFile = null;
1240      
1241    if (ref != null)
1242      wrapper = ref.get();
1243
1244    if (wrapper != null) {
1245      try {
1246    wrapper.close();
1247      } catch (Throwable JavaDoc e) {
1248      }
1249    }
1250  }
1251
1252  // debugging stuff.
1253
/**
1254   * Returns a copy of the allocation table.
1255   */

1256  public byte []getAllocationTable()
1257  {
1258    byte []table = new byte[_allocationTable.length];
1259
1260    System.arraycopy(_allocationTable, 0, table, 0, table.length);
1261
1262    return table;
1263  }
1264
1265  private static IllegalStateException JavaDoc stateError(String JavaDoc msg)
1266  {
1267    IllegalStateException JavaDoc e = new IllegalStateException JavaDoc(msg);
1268    e.fillInStackTrace();
1269    log.log(Level.WARNING, e.toString(), e);
1270    return e;
1271  }
1272
1273  /**
1274   * Reads the long.
1275   */

1276  public static long readLong(byte []buffer, int offset)
1277  {
1278    return (((buffer[offset + 0] & 0xffL) << 56) +
1279        ((buffer[offset + 1] & 0xffL) << 48) +
1280        ((buffer[offset + 2] & 0xffL) << 40) +
1281        ((buffer[offset + 3] & 0xffL) << 32) +
1282        ((buffer[offset + 4] & 0xffL) << 24) +
1283        ((buffer[offset + 5] & 0xffL) << 16) +
1284        ((buffer[offset + 6] & 0xffL) << 8) +
1285        ((buffer[offset + 7] & 0xffL)));
1286  }
1287
1288  /**
1289   * Writes the long.
1290   */

1291  public static void writeLong(byte []buffer, int offset, long v)
1292  {
1293    buffer[offset + 0] = (byte) (v >> 56);
1294    buffer[offset + 1] = (byte) (v >> 48);
1295    buffer[offset + 2] = (byte) (v >> 40);
1296    buffer[offset + 3] = (byte) (v >> 32);
1297    
1298    buffer[offset + 4] = (byte) (v >> 24);
1299    buffer[offset + 5] = (byte) (v >> 16);
1300    buffer[offset + 6] = (byte) (v >> 8);
1301    buffer[offset + 7] = (byte) (v);
1302  }
1303
1304  /**
1305   * Debug names for the allocation.
1306   */

1307  public static String JavaDoc codeToName(int code)
1308  {
1309    switch (code) {
1310    case ALLOC_FREE:
1311      return "free";
1312    case ALLOC_ROW:
1313      return "row";
1314    case ALLOC_USED:
1315      return "used";
1316    case ALLOC_FRAGMENT:
1317      return "fragment";
1318    case ALLOC_INDEX:
1319      return "index";
1320    default:
1321      return String.valueOf(code);
1322    }
1323  }
1324  
1325  public String JavaDoc toString()
1326  {
1327    return "Store[" + _id + "]";
1328  }
1329
1330  static class RandomAccessWrapper {
1331    private RandomAccessStream _file;
1332
1333    RandomAccessWrapper(RandomAccessStream file)
1334    {
1335      _file = file;
1336    }
1337
1338    RandomAccessStream getFile()
1339    {
1340      return _file;
1341    }
1342
1343    void close()
1344      throws IOException JavaDoc
1345    {
1346      RandomAccessStream file = _file;
1347      _file = null;
1348
1349      if (file != null)
1350    file.close();
1351    }
1352
1353    protected void finalize()
1354      throws Throwable JavaDoc
1355    {
1356      super.finalize();
1357      
1358      close();
1359    }
1360  }
1361}
1362
Popular Tags