diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2012-06-13 02:58:03 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2012-06-13 02:58:03 +0000 |
commit | fb3533f16221e78f9f9af42cef5153e72d7285bd (patch) | |
tree | 88e02f4255722fece5ebfd1a58f2f1d35a56a59e | |
parent | 4fd4eb6ea2a312689de4b4dfaadc1e49ef090a05 (diff) | |
download | jackcess-fb3533f16221e78f9f9af42cef5153e72d7285bd.tar.gz jackcess-fb3533f16221e78f9f9af42cef5153e72d7285bd.zip |
rework partial page writing and interacting with CodecHandlers (issue #3532250)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@628 f203690c-595d-4dc9-a70b-905162fa7fd2
4 files changed, 68 insertions, 33 deletions
diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 389314b..7cf0ebc 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -15,6 +15,12 @@ <action dev="jahlborn" type="fix" issue="3529534"> Fix NPE when running unit tests with db format MSISAM. </action> + <action dev="jahlborn" type="fix" issue="3532250"> + Fix writing partial pages when CodecHandler is in use. Note, this fix + involves a backwards incompatible change to the CodecHandler interface + (Jackcess Encrypt 1.0.3 or later is compatible with this version of + Jackcess). + </action> </release> <release version="1.2.7" date="2012-04-02"> <action dev="jahlborn" type="update" issue="3479560"> diff --git a/src/java/com/healthmarketscience/jackcess/CodecHandler.java b/src/java/com/healthmarketscience/jackcess/CodecHandler.java index c0f6170..c448668 100644 --- a/src/java/com/healthmarketscience/jackcess/CodecHandler.java +++ b/src/java/com/healthmarketscience/jackcess/CodecHandler.java @@ -30,6 +30,13 @@ import java.nio.ByteBuffer; */ public interface CodecHandler { + /** + * Returns {@code true} if this handler can encode partial pages, + * {@code false} otherwise. If this method returns {@code false}, the + * {@link #encodePage} method will never be called with a non-zero + * pageOffset. + */ + public boolean canEncodePartialPage(); /** * Decodes the given page buffer inline. diff --git a/src/java/com/healthmarketscience/jackcess/DefaultCodecProvider.java b/src/java/com/healthmarketscience/jackcess/DefaultCodecProvider.java index e24634a..7694617 100644 --- a/src/java/com/healthmarketscience/jackcess/DefaultCodecProvider.java +++ b/src/java/com/healthmarketscience/jackcess/DefaultCodecProvider.java @@ -83,8 +83,11 @@ public class DefaultCodecProvider implements CodecProvider */ public static class DummyHandler implements CodecHandler { - public void decodePage(ByteBuffer page, int pageNumber) throws IOException - { + public boolean canEncodePartialPage() { + return true; + } + + public void decodePage(ByteBuffer page, int pageNumber) throws IOException { // does nothing } @@ -104,8 +107,11 @@ public class DefaultCodecProvider implements CodecProvider */ public static class UnsupportedHandler implements CodecHandler { - public void decodePage(ByteBuffer page, int pageNumber) throws IOException - { + public boolean canEncodePartialPage() { + return true; + } + + public void decodePage(ByteBuffer page, int pageNumber) throws IOException { throw new UnsupportedCodecException("Decoding not supported. Please choose a CodecProvider which supports reading the current database encoding."); } diff --git a/src/java/com/healthmarketscience/jackcess/PageChannel.java b/src/java/com/healthmarketscience/jackcess/PageChannel.java index 68ba5cd..8759fcf 100644 --- a/src/java/com/healthmarketscience/jackcess/PageChannel.java +++ b/src/java/com/healthmarketscience/jackcess/PageChannel.java @@ -76,6 +76,9 @@ public class PageChannel implements Channel, Flushable { private UsageMap _globalUsageMap; /** handler for the current database encoding type */ private CodecHandler _codecHandler = DefaultCodecProvider.DUMMY_HANDLER; + /** temp page buffer used when pages cannot be partially encoded */ + private final TempPageHolder _fullPageEncodeBufferH = + TempPageHolder.newHolder(TempBufferHolder.Type.SOFT); /** * @param channel Channel containing the database @@ -200,11 +203,12 @@ public class PageChannel implements Channel, Flushable { { validatePageNumber(pageNumber); - page.rewind(); + page.rewind().position(pageOffset); - if((page.remaining() - pageOffset) > getFormat().PAGE_SIZE) { + int writeLen = page.remaining(); + if((writeLen + pageOffset) > getFormat().PAGE_SIZE) { throw new IllegalArgumentException( - "Page buffer is too large, size " + (page.remaining() - pageOffset)); + "Page buffer is too large, size " + (writeLen + pageOffset)); } ByteBuffer encodedPage = page; @@ -212,9 +216,35 @@ public class PageChannel implements Channel, Flushable { // re-mask header applyHeaderMask(page); } else { + + if(!_codecHandler.canEncodePartialPage()) { + if((pageOffset > 0) && (writeLen < getFormat().PAGE_SIZE)) { + + // current codec handler cannot encode part of a page, so need to + // copy the modified part into the current page contents in a temp + // buffer so that we can encode the entire page + ByteBuffer fullPage = _fullPageEncodeBufferH.setPage( + this, pageNumber); + + // copy the modified part to the full page + fullPage.position(pageOffset); + fullPage.put(page); + fullPage.rewind(); + + // reset so we can write the whole page + page = fullPage; + pageOffset = 0; + + } else { + + _fullPageEncodeBufferH.possiblyInvalidate(pageNumber, null); + } + } + // re-encode page encodedPage = _codecHandler.encodePage(page, pageNumber, pageOffset); } + try { encodedPage.position(pageOffset); _channel.write(encodedPage, (getPageOffset(pageNumber) + pageOffset)); @@ -230,12 +260,11 @@ public class PageChannel implements Channel, Flushable { } /** - * Write a page to disk as a new page, appending it to the database - * @param page Page to write - * @return Page number at which the page was written + * Allocates a new page in the database. Data in the page is undefined + * until it is written in a call to {@link #writePage(ByteBuffer,int)}. */ - public int writeNewPage(ByteBuffer page) throws IOException - { + public int allocateNewPage() throws IOException { + // this will force the file to be extended with mostly undefined bytes long size = _channel.size(); if(size >= getFormat().MAX_DATABASE_SIZE) { throw new IOException("Database is at maximum size " + @@ -247,21 +276,18 @@ public class PageChannel implements Channel, Flushable { getFormat().PAGE_SIZE); } - page.rewind(); - - if(page.remaining() > getFormat().PAGE_SIZE) { - throw new IllegalArgumentException("Page buffer is too large, size " + - page.remaining()); - } + _forceBytes.rewind(); // push the buffer to the end of the page, so that a full page's worth of - // data is written regardless of the incoming buffer size (we use a tiny - // buffer in allocateNewPage) - int pageOffset = (getFormat().PAGE_SIZE - page.remaining()); + // data is written + int pageOffset = (getFormat().PAGE_SIZE - _forceBytes.remaining()); long offset = size + pageOffset; int pageNumber = getNextPageNumber(size); - _channel.write(_codecHandler.encodePage(page, pageNumber, pageOffset), - offset); + + // since we are just allocating page space at this point and not writing + // meaningful data, we do _not_ encode the page. + _channel.write(_forceBytes, offset); + // note, we "force" page removal because we know that this is an unused // page (since we just added it to the file) _globalUsageMap.removePageNumber(pageNumber, true); @@ -269,15 +295,6 @@ public class PageChannel implements Channel, Flushable { } /** - * Allocates a new page in the database. Data in the page is undefined - * until it is written in a call to {@link #writePage(ByteBuffer,int)}. - */ - public int allocateNewPage() throws IOException { - // this will force the file to be extended with mostly undefined bytes - return writeNewPage(_forceBytes); - } - - /** * Deallocate a previously used page in the database. */ public void deallocatePage(int pageNumber) throws IOException { @@ -353,5 +370,4 @@ public class PageChannel implements Channel, Flushable { .position(position) .mark(); } - } |