comparison src/org/tmatesoft/hg/internal/DataAccessProvider.java @ 74:6f1b88693d48

Complete refactoring to org.tmatesoft
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Mon, 24 Jan 2011 03:14:45 +0100
parents src/com/tmate/hgkit/fs/DataAccessProvider.java@b0a15cefdfd6
children a3a2e5deb320
comparison
equal deleted inserted replaced
73:0d279bcc4442 74:6f1b88693d48
1 /*
2 * Copyright (c) 2010-2011 TMate Software Ltd
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * For information on how to redistribute this software under
14 * the terms of a license other than GNU General Public License
15 * contact TMate Software at support@svnkit.com
16 */
17 package org.tmatesoft.hg.internal;
18
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.IOException;
22 import java.nio.ByteBuffer;
23 import java.nio.MappedByteBuffer;
24 import java.nio.channels.FileChannel;
25
26 /**
27 *
28 * @author Artem Tikhomirov
29 * @author TMate Software Ltd.
30 */
31 public class DataAccessProvider {
32
33 private final int mapioMagicBoundary;
34 private final int bufferSize;
35
36 public DataAccessProvider() {
37 this(100 * 1024, 8 * 1024);
38 }
39
40 public DataAccessProvider(int mapioBoundary, int regularBufferSize) {
41 mapioMagicBoundary = mapioBoundary;
42 bufferSize = regularBufferSize;
43 }
44
45 public DataAccess create(File f) {
46 if (!f.exists()) {
47 return new DataAccess();
48 }
49 try {
50 FileChannel fc = new FileInputStream(f).getChannel();
51 if (fc.size() > mapioMagicBoundary) {
52 // TESTS: bufLen of 1024 was used to test MemMapFileAccess
53 return new MemoryMapFileAccess(fc, fc.size(), mapioMagicBoundary);
54 } else {
55 // XXX once implementation is more or less stable,
56 // may want to try ByteBuffer.allocateDirect() to see
57 // if there's any performance gain.
58 boolean useDirectBuffer = false;
59 // TESTS: bufferSize of 100 was used to check buffer underflow states when readBytes reads chunks bigger than bufSize
60 return new FileAccess(fc, fc.size(), bufferSize, useDirectBuffer);
61 }
62 } catch (IOException ex) {
63 // unlikely to happen, we've made sure file exists.
64 ex.printStackTrace(); // FIXME log error
65 }
66 return new DataAccess(); // non-null, empty.
67 }
68
69 // DOESN'T WORK YET
70 private static class MemoryMapFileAccess extends DataAccess {
71 private FileChannel fileChannel;
72 private final long size;
73 private long position = 0; // always points to buffer's absolute position in the file
74 private final int memBufferSize;
75 private MappedByteBuffer buffer;
76
77 public MemoryMapFileAccess(FileChannel fc, long channelSize, int /*long?*/ bufferSize) {
78 fileChannel = fc;
79 size = channelSize;
80 memBufferSize = bufferSize;
81 }
82
83 @Override
84 public boolean isEmpty() {
85 return position + (buffer == null ? 0 : buffer.position()) >= size;
86 }
87
88 @Override
89 public void seek(long offset) {
90 assert offset >= 0;
91 // offset may not necessarily be further than current position in the file (e.g. rewind)
92 if (buffer != null && /*offset is within buffer*/ offset >= position && (offset - position) < buffer.limit()) {
93 buffer.position((int) (offset - position));
94 } else {
95 position = offset;
96 buffer = null;
97 }
98 }
99
100 @Override
101 public void skip(int bytes) throws IOException {
102 assert bytes >= 0;
103 if (buffer == null) {
104 position += bytes;
105 return;
106 }
107 if (buffer.remaining() > bytes) {
108 buffer.position(buffer.position() + bytes);
109 } else {
110 position += buffer.position() + bytes;
111 buffer = null;
112 }
113 }
114
115 private void fill() throws IOException {
116 if (buffer != null) {
117 position += buffer.position();
118 }
119 long left = size - position;
120 buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, position, left < memBufferSize ? left : memBufferSize);
121 }
122
123 @Override
124 public void readBytes(byte[] buf, int offset, int length) throws IOException {
125 if (buffer == null || !buffer.hasRemaining()) {
126 fill();
127 }
128 // XXX in fact, we may try to create a MappedByteBuffer of exactly length size here, and read right away
129 while (length > 0) {
130 int tail = buffer.remaining();
131 if (tail == 0) {
132 throw new IOException();
133 }
134 if (tail >= length) {
135 buffer.get(buf, offset, length);
136 } else {
137 buffer.get(buf, offset, tail);
138 fill();
139 }
140 offset += tail;
141 length -= tail;
142 }
143 }
144
145 @Override
146 public byte readByte() throws IOException {
147 if (buffer == null || !buffer.hasRemaining()) {
148 fill();
149 }
150 if (buffer.hasRemaining()) {
151 return buffer.get();
152 }
153 throw new IOException();
154 }
155
156 @Override
157 public void done() {
158 buffer = null;
159 if (fileChannel != null) {
160 try {
161 fileChannel.close();
162 } catch (IOException ex) {
163 ex.printStackTrace(); // log debug
164 }
165 fileChannel = null;
166 }
167 }
168 }
169
170 // (almost) regular file access - FileChannel and buffers.
171 private static class FileAccess extends DataAccess {
172 private FileChannel fileChannel;
173 private final long size;
174 private ByteBuffer buffer;
175 private long bufferStartInFile = 0; // offset of this.buffer in the file.
176
177 public FileAccess(FileChannel fc, long channelSize, int bufferSizeHint, boolean useDirect) {
178 fileChannel = fc;
179 size = channelSize;
180 final int capacity = size < bufferSizeHint ? (int) size : bufferSizeHint;
181 buffer = useDirect ? ByteBuffer.allocateDirect(capacity) : ByteBuffer.allocate(capacity);
182 buffer.flip(); // or .limit(0) to indicate it's empty
183 }
184
185 @Override
186 public boolean isEmpty() {
187 return bufferStartInFile + buffer.position() >= size;
188 }
189
190 @Override
191 public void seek(long offset) throws IOException {
192 if (offset > size) {
193 throw new IllegalArgumentException();
194 }
195 if (offset < bufferStartInFile + buffer.limit() && offset >= bufferStartInFile) {
196 buffer.position((int) (offset - bufferStartInFile));
197 } else {
198 // out of current buffer, invalidate it (force re-read)
199 // XXX or ever re-read it right away?
200 bufferStartInFile = offset;
201 buffer.clear();
202 buffer.limit(0); // or .flip() to indicate we switch to reading
203 fileChannel.position(offset);
204 }
205 }
206
207 @Override
208 public void skip(int bytes) throws IOException {
209 final int newPos = buffer.position() + bytes;
210 if (newPos >= 0 && newPos < buffer.limit()) {
211 // no need to move file pointer, just rewind/seek buffer
212 buffer.position(newPos);
213 } else {
214 //
215 seek(bufferStartInFile + newPos);
216 }
217 }
218
219 private boolean fill() throws IOException {
220 if (!buffer.hasRemaining()) {
221 bufferStartInFile += buffer.limit();
222 buffer.clear();
223 if (bufferStartInFile < size) { // just in case there'd be any exception on EOF, not -1
224 fileChannel.read(buffer);
225 // may return -1 when EOF, but empty will reflect this, hence no explicit support here
226 }
227 buffer.flip();
228 }
229 return buffer.hasRemaining();
230 }
231
232 @Override
233 public void readBytes(byte[] buf, int offset, int length) throws IOException {
234 if (!buffer.hasRemaining()) {
235 fill();
236 }
237 while (length > 0) {
238 int tail = buffer.remaining();
239 if (tail == 0) {
240 throw new IOException(); // shall not happen provided stream contains expected data and no attempts to read past isEmpty() == true are made.
241 }
242 if (tail >= length) {
243 buffer.get(buf, offset, length);
244 } else {
245 buffer.get(buf, offset, tail);
246 fill();
247 }
248 offset += tail;
249 length -= tail;
250 }
251 }
252
253 @Override
254 public byte readByte() throws IOException {
255 if (buffer.hasRemaining()) {
256 return buffer.get();
257 }
258 if (fill()) {
259 return buffer.get();
260 }
261 throw new IOException();
262 }
263
264 @Override
265 public void done() {
266 if (buffer != null) {
267 buffer = null;
268 }
269 if (fileChannel != null) {
270 try {
271 fileChannel.close();
272 } catch (IOException ex) {
273 ex.printStackTrace(); // log debug
274 }
275 fileChannel = null;
276 }
277 }
278 }
279 }