001: /*
002: * FileMappedSequence.java
003: *
004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
005: *
006: * Copyright 2002-2008, Thomas Kellerer
007: * No part of this code maybe reused without the permission of the author
008: *
009: * To contact the author please send an email to: support@sql-workbench.net
010: *
011: */
012: package workbench.util;
013:
014: import java.io.File;
015: import java.io.FileInputStream;
016: import java.io.IOException;
017: import java.nio.ByteBuffer;
018: import java.nio.CharBuffer;
019: import java.nio.channels.FileChannel;
020: import java.nio.charset.Charset;
021: import java.nio.charset.CharsetDecoder;
022: import workbench.interfaces.CharacterSequence;
023: import workbench.log.LogMgr;
024:
025: /**
026: * An implementatio of CharacterSequence that does not read the
027: * entire file but only a part of it into memory
028: * @author support@sql-workbench.net
029: */
030: public class FileMappedSequence implements CharacterSequence {
031: // the current size of the chunk read from the file
032: // this will be adjusted dynamically according to the
033: // calls to substring
034: private int chunkSize = 128 * 1024;
035:
036: // The current chunk that has been read from the file
037: // its length will be equal to chunkSize
038: private String chunk;
039:
040: // The decoder used to convert the bytes from the file
041: // into a String object
042: private CharsetDecoder decoder;
043:
044: // Stores the starting position of the current chunk in the file
045: private int chunkStart;
046:
047: // Stores the end position of the current chunk in the file
048: private int chunkEnd;
049:
050: private long fileSize;
051:
052: private FileInputStream input;
053: private FileChannel channel;
054: private ByteBuffer readBuffer;
055:
056: public FileMappedSequence(File f, String characterSet)
057: throws IOException {
058: this .fileSize = f.length();
059: this .input = new FileInputStream(f);
060: this .channel = input.getChannel();
061: this .chunkStart = 0;
062: this .chunkEnd = 0;
063: this .chunk = "";
064: readBuffer = ByteBuffer.allocateDirect(chunkSize);
065: Charset charset = Charset.forName(characterSet);
066: this .decoder = charset.newDecoder();
067: }
068:
069: public int length() {
070: return (int) this .fileSize;
071: }
072:
073: private void ensureWindow(int start, int end) {
074: if (this .chunkStart <= start && this .chunkEnd > end)
075: return;
076:
077: this .chunkStart = start;
078: if ((end - start) > this .chunkSize) {
079: this .chunkSize = end - start;
080: }
081: this .chunkEnd = start + chunkSize;
082: try {
083: if (chunkStart + chunkSize > this .fileSize) {
084: chunkSize = (int) (this .fileSize - chunkStart);
085: }
086:
087: // prepare for requests larger then the chunkSize
088: if (chunkSize > readBuffer.capacity()) {
089: readBuffer = ByteBuffer.allocateDirect(chunkSize);
090: }
091: readBuffer.clear();
092: readBuffer.limit(chunkSize);
093: int read = this .channel.read(readBuffer, chunkStart);
094:
095: // Rewind is necessary because the decoder starts at the
096: // current position
097: readBuffer.rewind();
098:
099: // Setting the limit to the number of bytes read
100: // is also necessary because the decoder uses that
101: // information to find out how many bytes it needs
102: // to decode from the buffer
103: readBuffer.limit(read);
104:
105: CharBuffer cb = decoder.decode(readBuffer);
106: this .chunk = cb.toString();
107: } catch (Exception e) {
108: LogMgr.logError("FileMappedSequence.ensureWindow",
109: "Error reading chunk", e);
110: }
111: }
112:
113: public void done() {
114: try {
115: if (this .input != null)
116: input.close();
117: } catch (Exception e) {
118: LogMgr.logError("FileMappedSequence.done()",
119: "Error closing input stream", e);
120: }
121: }
122:
123: public char charAt(int index) {
124: this .ensureWindow(index, index + 1);
125: int indexInChunk = index - chunkStart;
126: return this .chunk.charAt(indexInChunk);
127: }
128:
129: public String subSequence(int start, int end) {
130: this .ensureWindow(start, end);
131: int startInChunk = start - chunkStart;
132: int endInChunk = end - chunkStart;
133: return this.chunk.substring(startInChunk, endInChunk);
134: }
135: }
|