View Javadoc

1   /**
2    * JDBM LICENSE v1.00
3    *
4    * Redistribution and use of this software and associated documentation
5    * ("Software"), with or without modification, are permitted provided
6    * that the following conditions are met:
7    *
8    * 1. Redistributions of source code must retain copyright
9    *    statements and notices.  Redistributions must also contain a
10   *    copy of this document.
11   *
12   * 2. Redistributions in binary form must reproduce the
13   *    above copyright notice, this list of conditions and the
14   *    following disclaimer in the documentation and/or other
15   *    materials provided with the distribution.
16   *
17   * 3. The name "JDBM" must not be used to endorse or promote
18   *    products derived from this Software without prior written
19   *    permission of Cees de Groot.  For written permission,
20   *    please contact cg@cdegroot.com.
21   *
22   * 4. Products derived from this Software may not be called "JDBM"
23   *    nor may "JDBM" appear in their names without prior written
24   *    permission of Cees de Groot.
25   *
26   * 5. Due credit should be given to the JDBM Project
27   *    (http://jdbm.sourceforge.net/).
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
30   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
31   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
32   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
33   * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
34   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
35   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
36   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
38   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
39   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
40   * OF THE POSSIBILITY OF SUCH DAMAGE.
41   *
42   * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
43   * Contributions are Copyright (C) 2000 by their associated contributors.
44   *
45   * $Id: PhysicalRowIdManager.java,v 1.3 2003/03/21 03:00:09 boisvert Exp $
46   */
47  
48  package jdbm.recman;
49  
50  import java.io.IOException;
51  
52  /**
53   *  This class manages physical row ids, and their data.
54   */
55  final class PhysicalRowIdManager
56  {
57  
58      // The file we're talking to and the associated page manager.
59      private RecordFile file;
60      private PageManager pageman;
61      private FreePhysicalRowIdPageManager freeman;
62  
63      /**
64       *  Creates a new rowid manager using the indicated record file.
65       *  and page manager.
66       */
67      PhysicalRowIdManager( RecordFile file, PageManager pageManager )
68          throws IOException
69      {
70          this.file = file;
71          this.pageman = pageManager;
72          this.freeman = new FreePhysicalRowIdPageManager(file, pageman);
73      }
74  
75      /**
76       *  Inserts a new record. Returns the new physical rowid.
77       */
78      Location insert( byte[] data, int start, int length )
79          throws IOException
80      {
81          Location retval = alloc( length );
82          write( retval, data, start, length );
83          return retval;
84      }
85  
86      /**
87       *  Updates an existing record. Returns the possibly changed
88       *  physical rowid.
89       */
90      Location update( Location rowid, byte[] data, int start, int length )
91          throws IOException
92      {
93          // fetch the record header
94          BlockIo block = file.get( rowid.getBlock() );
95          RecordHeader head = new RecordHeader( block, rowid.getOffset() );
96          if ( length > head.getAvailableSize() ) {
97              // not enough space - we need to copy to a new rowid.
98              file.release( block );
99              free( rowid );
100             rowid = alloc( length );
101         } else {
102             file.release( block );
103         }
104 
105         // 'nuff space, write it in and return the rowid.
106         write( rowid, data, start, length );
107         return rowid;
108     }
109 
110     /**
111      *  Deletes a record.
112      */
113     void delete( Location rowid )
114         throws IOException
115     {
116         free( rowid );
117     }
118 
119     /**
120      *  Retrieves a record.
121      */
122     byte[] fetch( Location rowid )
123         throws IOException 
124     {
125         // fetch the record header
126         PageCursor curs = new PageCursor( pageman, rowid.getBlock() );
127         BlockIo block = file.get( curs.getCurrent() );
128         RecordHeader head = new RecordHeader( block, rowid.getOffset() );
129 
130         // allocate a return buffer
131         byte[] retval = new byte[ head.getCurrentSize() ];
132         if ( retval.length == 0 ) {
133             file.release( curs.getCurrent(), false );
134             return retval;
135         }
136 
137         // copy bytes in
138         int offsetInBuffer = 0;
139         int leftToRead = retval.length;
140         short dataOffset = (short) (rowid.getOffset() + RecordHeader.SIZE);
141         while ( leftToRead > 0 ) {
142             // copy current page's data to return buffer
143             int toCopy = RecordFile.BLOCK_SIZE - dataOffset;
144             if ( leftToRead < toCopy ) {
145                 toCopy = leftToRead;
146             }
147             System.arraycopy( block.getData(), dataOffset,
148                               retval, offsetInBuffer,
149                               toCopy );
150 
151             // Go to the next block
152             leftToRead -= toCopy;
153             offsetInBuffer += toCopy;
154 
155             file.release( block );
156 
157             if ( leftToRead > 0 ) {
158                 block = file.get( curs.next() );
159                 dataOffset = DataPage.O_DATA;
160             }
161 
162         }
163 
164         return retval;
165     }
166 
167     /**
168      *  Allocate a new rowid with the indicated size.
169      */
170     private Location alloc( int size )
171         throws IOException
172     {
173         Location retval = freeman.get( size );
174         if ( retval == null ) {
175             retval = allocNew( size, pageman.getLast( Magic.USED_PAGE ) );
176         }
177         return retval;
178     }
179 
180     /**
181      *  Allocates a new rowid. The second parameter is there to
182      *  allow for a recursive call - it indicates where the search
183      *  should start.
184      */
185     private Location allocNew( int size, long start )
186         throws IOException
187     {
188         BlockIo curBlock;
189         DataPage curPage;
190         if ( start == 0 ) {
191             // we need to create a new page.
192             start = pageman.allocate( Magic.USED_PAGE );
193             curBlock = file.get( start );
194             curPage = DataPage.getDataPageView( curBlock );
195             curPage.setFirst( DataPage.O_DATA );
196             RecordHeader hdr = new RecordHeader( curBlock, DataPage.O_DATA );
197             hdr.setAvailableSize( 0 );
198             hdr.setCurrentSize( 0 );
199         } else {
200             curBlock = file.get( start );
201             curPage = DataPage.getDataPageView( curBlock );
202         }
203 
204         // follow the rowids on this page to get to the last one. We don't
205         // fall off, because this is the last page, remember?
206         short pos = curPage.getFirst();
207         if ( pos == 0 ) {
208             // page is exactly filled by the last block of a record
209             file.release( curBlock );
210             return allocNew( size, 0 );
211         }
212 
213         RecordHeader hdr = new RecordHeader( curBlock, pos );
214         while ( hdr.getAvailableSize() != 0 && pos < RecordFile.BLOCK_SIZE ) {
215             pos += hdr.getAvailableSize() + RecordHeader.SIZE;
216             if ( pos == RecordFile.BLOCK_SIZE ) {
217                 // Again, a filled page.
218                 file.release( curBlock );
219                 return allocNew( size, 0 );
220             }
221 
222             hdr = new RecordHeader( curBlock, pos );
223         }
224 
225         if ( pos == RecordHeader.SIZE ) {
226             // the last record exactly filled the page. Restart forcing
227             // a new page.
228             file.release( curBlock );
229         }
230 
231         // we have the position, now tack on extra pages until we've got
232         // enough space.
233         Location retval = new Location( start, pos );
234         int freeHere = RecordFile.BLOCK_SIZE - pos - RecordHeader.SIZE;
235         if ( freeHere < size ) {
236             // check whether the last page would have only a small bit left.
237             // if yes, increase the allocation. A small bit is a record
238             // header plus 16 bytes.
239             int lastSize = (size - freeHere) % DataPage.DATA_PER_PAGE;
240             if (( DataPage.DATA_PER_PAGE - lastSize ) < (RecordHeader.SIZE + 16) ) {
241                 size += (DataPage.DATA_PER_PAGE - lastSize);
242             }
243 
244             // write out the header now so we don't have to come back.
245             hdr.setAvailableSize( size );
246             file.release( start, true );
247 
248             int neededLeft = size - freeHere;
249             // Refactor these two blocks!
250             while ( neededLeft >= DataPage.DATA_PER_PAGE ) {
251                 start = pageman.allocate( Magic.USED_PAGE );
252                 curBlock = file.get( start );
253                 curPage = DataPage.getDataPageView( curBlock );
254                 curPage.setFirst( (short) 0 ); // no rowids, just data
255                 file.release( start, true );
256                 neededLeft -= DataPage.DATA_PER_PAGE;
257             }
258             if ( neededLeft > 0 ) {
259                 // done with whole chunks, allocate last fragment.
260                 start = pageman.allocate( Magic.USED_PAGE );
261                 curBlock = file.get( start );
262                 curPage = DataPage.getDataPageView( curBlock );
263                 curPage.setFirst( (short) (DataPage.O_DATA + neededLeft) );
264                 file.release( start, true );
265             }
266         } else {
267             // just update the current page. If there's less than 16 bytes
268             // left, we increase the allocation (16 bytes is an arbitrary
269             // number).
270             if ( freeHere - size <= (16 + RecordHeader.SIZE) ) {
271                 size = freeHere;
272             }
273             hdr.setAvailableSize( size );
274             file.release( start, true );
275         }
276         return retval;
277 
278     }
279 
280 
281     private void free( Location id )
282         throws IOException
283     {
284         // get the rowid, and write a zero current size into it.
285         BlockIo curBlock = file.get( id.getBlock() );
286         DataPage curPage = DataPage.getDataPageView( curBlock );
287         RecordHeader hdr = new RecordHeader( curBlock, id.getOffset() );
288         hdr.setCurrentSize( 0 );
289         file.release( id.getBlock(), true );
290 
291         // write the rowid to the free list
292         freeman.put( id, hdr.getAvailableSize() );
293     }
294 
295     /**
296      *  Writes out data to a rowid. Assumes that any resizing has been
297      *  done.
298      */
299     private void write(Location rowid, byte[] data, int start, int length )
300         throws IOException
301     {
302         PageCursor curs = new PageCursor( pageman, rowid.getBlock() );
303         BlockIo block = file.get( curs.getCurrent() );
304         RecordHeader hdr = new RecordHeader( block, rowid.getOffset() );
305         hdr.setCurrentSize( length );
306         if ( length == 0 ) {
307             file.release( curs.getCurrent(), true );
308             return;
309         }
310 
311         // copy bytes in
312         int offsetInBuffer = start;
313         int leftToWrite = length;
314         short dataOffset = (short) (rowid.getOffset() + RecordHeader.SIZE);
315         while ( leftToWrite > 0 ) {
316             // copy current page's data to return buffer
317             int toCopy = RecordFile.BLOCK_SIZE - dataOffset;
318 
319             if ( leftToWrite < toCopy ) {
320                 toCopy = leftToWrite;
321             }
322             System.arraycopy( data, offsetInBuffer, block.getData(), 
323                               dataOffset, toCopy );
324 
325             // Go to the next block
326             leftToWrite -= toCopy;
327             offsetInBuffer += toCopy;
328 
329             file.release( curs.getCurrent(), true );
330 
331             if ( leftToWrite > 0 ) {
332                 block = file.get( curs.next() );
333                 dataOffset = DataPage.O_DATA;
334             }
335         }
336     }
337 }
338