1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package com.healthmarketscience.jackcess;
29
30 import java.io.Flushable;
31 import java.io.IOException;
32 import java.nio.ByteBuffer;
33 import java.nio.ByteOrder;
34 import java.nio.channels.Channel;
35 import java.nio.channels.FileChannel;
36
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39
40
41
42
43
44 public class PageChannel implements Channel, Flushable {
45
46 private static final Log LOG = LogFactory.getLog(PageChannel.class);
47
48 static final int INVALID_PAGE_NUMBER = -1;
49
50 static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.LITTLE_ENDIAN;
51
52
53
54
55 private static final byte[] INVALID_PAGE_BYTE_HEADER =
56 new byte[]{PageTypes.INVALID, (byte)0, (byte)0, (byte)0};
57
58
59 private static final int PAGE_GLOBAL_USAGE_MAP = 1;
60
61 private static final int ROW_GLOBAL_USAGE_MAP = 0;
62
63
64 private final FileChannel _channel;
65
66 private final JetFormat _format;
67
68 private final boolean _autoSync;
69
70
71 private final ByteBuffer _invalidPageBytes =
72 ByteBuffer.wrap(INVALID_PAGE_BYTE_HEADER);
73
74 private final ByteBuffer _forceBytes = ByteBuffer.allocate(1);
75
76 private UsageMap _globalUsageMap;
77
78
79
80
81
82 public PageChannel(FileChannel channel, JetFormat format, boolean autoSync)
83 throws IOException
84 {
85 _channel = channel;
86 _format = format;
87 _autoSync = autoSync;
88 }
89
90
91
92
93 public void initialize(Database database)
94 throws IOException
95 {
96
97
98 _globalUsageMap = UsageMap.read(database, PAGE_GLOBAL_USAGE_MAP,
99 ROW_GLOBAL_USAGE_MAP, true);
100 }
101
102
103
104
105 PageChannel(boolean testing) {
106 if(!testing) {
107 throw new IllegalArgumentException();
108 }
109 _channel = null;
110 _format = JetFormat.VERSION_4;
111 _autoSync = false;
112 }
113
114 public JetFormat getFormat() {
115 return _format;
116 }
117
118
119
120
121 private int getNextPageNumber(long size) {
122 return (int)(size / getFormat().PAGE_SIZE);
123 }
124
125
126
127
128 private long getPageOffset(int pageNumber) {
129 return((long) pageNumber * (long) getFormat().PAGE_SIZE);
130 }
131
132
133
134
135 private void validatePageNumber(int pageNumber)
136 throws IOException
137 {
138 int nextPageNumber = getNextPageNumber(_channel.size());
139 if((pageNumber <= INVALID_PAGE_NUMBER) || (pageNumber >= nextPageNumber)) {
140 throw new IllegalStateException("invalid page number " + pageNumber);
141 }
142 }
143
144
145
146
147
148 public void readPage(ByteBuffer buffer, int pageNumber)
149 throws IOException
150 {
151 validatePageNumber(pageNumber);
152 if (LOG.isDebugEnabled()) {
153 LOG.debug("Reading in page " + Integer.toHexString(pageNumber));
154 }
155 buffer.clear();
156 int bytesRead = _channel.read(
157 buffer, (long) pageNumber * (long) getFormat().PAGE_SIZE);
158 buffer.flip();
159 if(bytesRead != getFormat().PAGE_SIZE) {
160 throw new IOException("Failed attempting to read " +
161 getFormat().PAGE_SIZE + " bytes from page " +
162 pageNumber + ", only read " + bytesRead);
163 }
164 }
165
166
167
168
169
170
171 public void writePage(ByteBuffer page, int pageNumber) throws IOException {
172 writePage(page, pageNumber, 0);
173 }
174
175
176
177
178
179
180
181
182 public void writePage(ByteBuffer page, int pageNumber,
183 int pageOffset)
184 throws IOException
185 {
186 validatePageNumber(pageNumber);
187
188 page.rewind();
189
190 if((page.remaining() - pageOffset) > getFormat().PAGE_SIZE) {
191 throw new IllegalArgumentException(
192 "Page buffer is too large, size " + (page.remaining() - pageOffset));
193 }
194
195 page.position(pageOffset);
196 _channel.write(page, (getPageOffset(pageNumber) + pageOffset));
197 if(_autoSync) {
198 flush();
199 }
200 }
201
202
203
204
205
206
207 public int writeNewPage(ByteBuffer page) throws IOException
208 {
209 long size = _channel.size();
210 if(size >= getFormat().MAX_DATABASE_SIZE) {
211 throw new IOException("Database is at maximum size " +
212 getFormat().MAX_DATABASE_SIZE);
213 }
214 if((size % getFormat().PAGE_SIZE) != 0L) {
215 throw new IOException("Database corrupted, file size " + size +
216 " is not multiple of page size " +
217 getFormat().PAGE_SIZE);
218 }
219
220 page.rewind();
221
222 if(page.remaining() > getFormat().PAGE_SIZE) {
223 throw new IllegalArgumentException("Page buffer is too large, size " +
224 page.remaining());
225 }
226
227
228
229
230 long offset = size + (getFormat().PAGE_SIZE - page.remaining());
231 _channel.write(page, offset);
232 int pageNumber = getNextPageNumber(size);
233 _globalUsageMap.removePageNumber(pageNumber);
234 return pageNumber;
235 }
236
237
238
239
240
241 public int allocateNewPage() throws IOException {
242
243 return writeNewPage(_forceBytes);
244 }
245
246
247
248
249 public void deallocatePage(int pageNumber) throws IOException {
250 validatePageNumber(pageNumber);
251
252
253
254 _invalidPageBytes.rewind();
255 _channel.write(_invalidPageBytes, getPageOffset(pageNumber));
256
257 _globalUsageMap.addPageNumber(pageNumber);
258 }
259
260
261
262
263 public ByteBuffer createPageBuffer() {
264 return createBuffer(getFormat().PAGE_SIZE);
265 }
266
267
268
269
270
271 public ByteBuffer createBuffer(int size) {
272 return createBuffer(size, ByteOrder.LITTLE_ENDIAN);
273 }
274
275
276
277
278 public ByteBuffer createBuffer(int size, ByteOrder order) {
279 ByteBuffer rtn = ByteBuffer.allocate(size);
280 rtn.order(order);
281 return rtn;
282 }
283
284 public void flush() throws IOException {
285 _channel.force(true);
286 }
287
288 public void close() throws IOException {
289 flush();
290 _channel.close();
291 }
292
293 public boolean isOpen() {
294 return _channel.isOpen();
295 }
296
297
298
299
300
301 public static ByteBuffer narrowBuffer(ByteBuffer buffer, int position,
302 int limit)
303 {
304 return (ByteBuffer)buffer.duplicate()
305 .order(buffer.order())
306 .clear()
307 .limit(limit)
308 .position(position)
309 .mark();
310 }
311
312 }