aboutsummaryrefslogtreecommitdiff
path: root/generator/src/main/java/com/google/archivepatcher/generator/bsdiff/RandomAccessObject.java
diff options
context:
space:
mode:
Diffstat (limited to 'generator/src/main/java/com/google/archivepatcher/generator/bsdiff/RandomAccessObject.java')
-rw-r--r--generator/src/main/java/com/google/archivepatcher/generator/bsdiff/RandomAccessObject.java495
1 files changed, 495 insertions, 0 deletions
diff --git a/generator/src/main/java/com/google/archivepatcher/generator/bsdiff/RandomAccessObject.java b/generator/src/main/java/com/google/archivepatcher/generator/bsdiff/RandomAccessObject.java
new file mode 100644
index 0000000..6a8789a
--- /dev/null
+++ b/generator/src/main/java/com/google/archivepatcher/generator/bsdiff/RandomAccessObject.java
@@ -0,0 +1,495 @@
+// Copyright 2016 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.archivepatcher.generator.bsdiff;
+
+import java.io.Closeable;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+// TODO(andrewhayden): clean up the implementations, we only really need two and they can be in
+// separate files.
+
+/**
+ * Interface which offers random access interface. This class exists to allow BsDiff to use either
+ * a RandomAccessFile for disk-based io, or a byte[] for fully in-memory operation.
+ */
+public interface RandomAccessObject extends DataInput, DataOutput, Closeable {
+
+ /**
+ * Returns the length of the file or byte array associated with the RandomAccessObject.
+ *
+ * @return the length of the file or byte array associated with the RandomAccessObject
+ * @throws IOException if unable to determine the length of the file, when backed by a file
+ */
+ public long length() throws IOException;
+
+ /**
+ * Seeks to a specified position, in bytes, into the file or byte array.
+ *
+ * @param pos the position to seek to
+ * @throws IOException if seeking fails
+ */
+ public void seek(long pos) throws IOException;
+
+ /**
+ * Seek to a specified integer-aligned position in the associated file or byte array. For example,
+ * seekToIntAligned(5) will seek to the beginning of the 5th integer, or in other words the 20th
+ * byte. In general, seekToIntAligned(n) will seek to byte 4n.
+ *
+ * @param pos the position to seek to
+ * @throws IOException if seeking fails
+ */
+ public void seekToIntAligned(long pos) throws IOException;
+
+ /**
+ * A {@link RandomAccessFile}-based implementation of {@link RandomAccessObject} which just
+ * delegates all operations to the equivalents in {@link RandomAccessFile}. Slower than the
+ * {@link RandomAccessMmapObject} implementation.
+ */
+ public static final class RandomAccessFileObject extends RandomAccessFile
+ implements RandomAccessObject {
+ private final boolean mShouldDeleteFileOnClose;
+ private final File mFile;
+
+ /**
+ * This constructor takes in a temporary file. This constructor does not take ownership of the
+ * file, and the file will not be deleted on {@link #close()}.
+ *
+ * @param tempFile the file backing this object
+ * @param mode the mode to use, e.g. "r" or "w" for read or write
+ * @throws IOException if unable to open the file for the specified mode
+ */
+ public RandomAccessFileObject(final File tempFile, final String mode) throws IOException {
+ this(tempFile, mode, false);
+ }
+
+ /**
+ * This constructor takes in a temporary file. If deleteFileOnClose is true, the constructor
+ * takes ownership of that file, and this file is deleted on close().
+ *
+ * @param tempFile the file backing this object
+ * @param mode the mode to use, e.g. "r" or "w" for read or write
+ * @param deleteFileOnClose if true the constructor takes ownership of that file, and this file
+ * is deleted on close().
+ * @throws IOException if unable to open the file for the specified mode
+ * @throws IllegalArgumentException if the size of the file is too great
+ */
+ // TODO(hartmanng): rethink the handling of these temp files. It's confusing and shouldn't
+ // really be the responsibility of RandomAccessObject.
+ public RandomAccessFileObject(final File tempFile, final String mode, boolean deleteFileOnClose)
+ throws IOException {
+ super(tempFile, mode);
+ mShouldDeleteFileOnClose = deleteFileOnClose;
+ mFile = tempFile;
+ if (mShouldDeleteFileOnClose) {
+ mFile.deleteOnExit();
+ }
+ }
+
+ @Override
+ public void seekToIntAligned(long pos) throws IOException {
+ seek(pos * 4);
+ }
+
+ /**
+ * Close the associated file. Also delete the associated temp file if specified in the
+ * constructor. This should be called on every RandomAccessObject when it is no longer needed.
+ */
+ // TODO(hartmanng): rethink the handling of these temp files. It's confusing and shouldn't
+ // really be the responsibility of RandomAccessObject.
+ @Override
+ public void close() throws IOException {
+ super.close();
+ if (mShouldDeleteFileOnClose) {
+ mFile.delete();
+ }
+ }
+ }
+
+ /**
+ * An array-based implementation of {@link RandomAccessObject} for entirely in-memory operations.
+ */
+ public static class RandomAccessByteArrayObject implements RandomAccessObject {
+ protected ByteBuffer mByteBuffer;
+
+ /**
+ * The passed-in byte array will be treated as big-endian when dealing with ints.
+ *
+ * @param byteArray the byte array to wrap
+ */
+ public RandomAccessByteArrayObject(final byte[] byteArray) {
+ mByteBuffer = ByteBuffer.wrap(byteArray);
+ }
+
+ /**
+ * Allocate a new ByteBuffer of given length. This will be treated as big-endian.
+ *
+ * @param length the length of the buffer to allocate
+ */
+ public RandomAccessByteArrayObject(final int length) {
+ mByteBuffer = ByteBuffer.allocate(length);
+ }
+
+ protected RandomAccessByteArrayObject() {
+ // No-op, this is just used by the extending class RandomAccessMmapObject.
+ }
+
+ @Override
+ public long length() {
+ return mByteBuffer.capacity();
+ }
+
+ @Override
+ public byte readByte() {
+ return mByteBuffer.get();
+ }
+
+ /**
+ * Reads an integer from the underlying data array in big-endian order.
+ */
+ @Override
+ public int readInt() {
+ return mByteBuffer.getInt();
+ }
+
+ public void writeByte(byte b) {
+ mByteBuffer.put(b);
+ }
+
+ @Override
+ public void writeInt(int i) {
+ mByteBuffer.putInt(i);
+ }
+
+ /**
+ * RandomAccessByteArrayObject.seek() only supports addresses up to 2^31-1, due to the fact
+ * that it needs to support byte[] backend (and in Java, arrays are indexable only by int).
+ * This means that it can only seek up to a 2GiB byte[].
+ */
+ @Override
+ public void seek(long pos) {
+ if (pos > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException(
+ "RandomAccessByteArrayObject can only handle seek() "
+ + "addresses up to Integer.MAX_VALUE.");
+ }
+
+ mByteBuffer.position((int) pos);
+ }
+
+ @Override
+ public void seekToIntAligned(long pos) {
+ seek(pos * 4);
+ }
+
+ @Override
+ public void close() throws IOException {
+ // Nothing necessary.
+ }
+
+ @Override
+ public boolean readBoolean() {
+ return readByte() != 0;
+ }
+
+ @Override
+ public char readChar() {
+ return mByteBuffer.getChar();
+ }
+
+ @Override
+ public double readDouble() {
+ return mByteBuffer.getDouble();
+ }
+
+ @Override
+ public float readFloat() {
+ return mByteBuffer.getFloat();
+ }
+
+ @Override
+ public void readFully(byte[] b) {
+ mByteBuffer.get(b);
+ }
+
+ @Override
+ public void readFully(byte[] b, int off, int len) {
+ mByteBuffer.get(b, off, len);
+ }
+
+ @Override
+ public String readLine() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long readLong() {
+ return mByteBuffer.getLong();
+ }
+
+ @Override
+ public short readShort() {
+ return mByteBuffer.getShort();
+ }
+
+ @Override
+ public int readUnsignedByte() {
+ return mByteBuffer.get() & 0xff;
+ }
+
+ @Override
+ public int readUnsignedShort() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String readUTF() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int skipBytes(int n) {
+ mByteBuffer.position(mByteBuffer.position() + n);
+ return n;
+ }
+
+ @Override
+ public void write(byte[] b) {
+ mByteBuffer.put(b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) {
+ mByteBuffer.put(b, off, len);
+ }
+
+ @Override
+ public void write(int b) {
+ writeByte((byte) b);
+ }
+
+ @Override
+ public void writeBoolean(boolean v) {
+ writeByte(v ? (byte) 1 : (byte) 0);
+ }
+
+ @Override
+ public void writeByte(int v) {
+ writeByte((byte) v);
+ }
+
+ @Override
+ public void writeBytes(String s) {
+ for (int x = 0; x < s.length(); x++) {
+ writeByte((byte) s.charAt(x));
+ }
+ }
+
+ @Override
+ public void writeChar(int v) {
+ mByteBuffer.putChar((char) v);
+ }
+
+ @Override
+ public void writeChars(String s) {
+ for (int x = 0; x < s.length(); x++) {
+ writeChar(s.charAt(x));
+ }
+ }
+
+ @Override
+ public void writeDouble(double v) {
+ mByteBuffer.putDouble(v);
+ }
+
+ @Override
+ public void writeFloat(float v) {
+ mByteBuffer.putFloat(v);
+ }
+
+ @Override
+ public void writeLong(long v) {
+ mByteBuffer.putLong(v);
+ }
+
+ @Override
+ public void writeShort(int v) {
+ mByteBuffer.putShort((short) v);
+ }
+
+ @Override
+ public void writeUTF(String s) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * A {@link ByteBuffer}-based implementation of {@link RandomAccessObject} that uses files on
+ * disk, but is significantly faster than the RandomAccessFile implementation.
+ */
+ public static final class RandomAccessMmapObject extends RandomAccessByteArrayObject {
+ private final boolean mShouldDeleteFileOnRelease;
+ private final File mFile;
+ private final FileChannel mFileChannel;
+
+ public RandomAccessMmapObject(final RandomAccessFile randomAccessFile, String mode)
+ throws IOException, IllegalArgumentException {
+ if (randomAccessFile.length() > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("Only files up to 2GiB in size are supported.");
+ }
+
+ FileChannel.MapMode mapMode;
+ if (mode.equals("r")) {
+ mapMode = FileChannel.MapMode.READ_ONLY;
+ } else {
+ mapMode = FileChannel.MapMode.READ_WRITE;
+ }
+
+ mFileChannel = randomAccessFile.getChannel();
+ mByteBuffer = mFileChannel.map(mapMode, 0, randomAccessFile.length());
+ mByteBuffer.position(0);
+ mShouldDeleteFileOnRelease = false;
+ mFile = null;
+ }
+
+ /**
+ * This constructor creates a temporary file. This file is deleted on close(), so be sure to
+ * call it when you're done, otherwise it'll leave stray files.
+ *
+ * @param tempFileName the path to the file backing this object
+ * @param mode the mode to use, e.g. "r" or "w" for read or write
+ * @param length the size of the file to be read or written
+ * @throws IOException if unable to open the file for the specified mode
+ * @throws IllegalArgumentException if the size of the file is too great
+ */
+ // TODO(hartmanng): rethink the handling of these temp files. It's confusing and shouldn't
+ // really be the responsibility of RandomAccessObject.
+ @SuppressWarnings("resource") // RandomAccessFile deliberately left open
+ public RandomAccessMmapObject(final String tempFileName, final String mode, long length)
+ throws IOException, IllegalArgumentException {
+ if (length > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException(
+ "RandomAccessMmapObject only supports file sizes up to " + "Integer.MAX_VALUE.");
+ }
+
+ mFile = File.createTempFile(tempFileName, "temp");
+ mFile.deleteOnExit();
+ mShouldDeleteFileOnRelease = true;
+
+ FileChannel.MapMode mapMode;
+ if (mode.equals("r")) {
+ mapMode = FileChannel.MapMode.READ_ONLY;
+ } else {
+ mapMode = FileChannel.MapMode.READ_WRITE;
+ }
+
+ RandomAccessFile file = null;
+ try {
+ file = new RandomAccessFile(mFile, mode);
+ mFileChannel = file.getChannel();
+ mByteBuffer = mFileChannel.map(mapMode, 0, (int) length);
+ mByteBuffer.position(0);
+ } catch (IOException e) {
+ if (file != null) {
+ try {
+ file.close();
+ } catch (Exception ignored) {
+ // Nothing more can be done
+ }
+ }
+ close();
+ throw new IOException("Unable to open file", e);
+ }
+ }
+
+ /**
+ * This constructor takes in a temporary file, and takes ownership of that file. This file is
+ * deleted on close() OR IF THE CONSTRUCTOR FAILS. The main purpose of this constructor is to
+ * test close() on the passed-in file.
+ *
+ * @param tempFile the the file backing this object
+ * @param mode the mode to use, e.g. "r" or "w" for read or write
+ * @throws IOException if unable to open the file for the specified mode
+ * @throws IllegalArgumentException if the size of the file is too great
+ */
+ // TODO(hartmanng): rethink the handling of these temp files. It's confusing and shouldn't
+ // really be the responsibility of RandomAccessObject.
+ @SuppressWarnings("resource") // RandomAccessFile deliberately left open
+ public RandomAccessMmapObject(final File tempFile, final String mode)
+ throws IOException, IllegalArgumentException {
+ if (tempFile.length() > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("Only files up to 2GiB in size are supported.");
+ }
+
+ mFile = tempFile;
+ mFile.deleteOnExit();
+ mShouldDeleteFileOnRelease = true;
+
+ FileChannel.MapMode mapMode;
+ if (mode.equals("r")) {
+ mapMode = FileChannel.MapMode.READ_ONLY;
+ } else {
+ mapMode = FileChannel.MapMode.READ_WRITE;
+ }
+
+ RandomAccessFile file = null;
+ try {
+ file = new RandomAccessFile(mFile, mode);
+ mFileChannel = file.getChannel();
+ mByteBuffer = mFileChannel.map(mapMode, 0, tempFile.length());
+ mByteBuffer.position(0);
+ } catch (IOException e) {
+ if (file != null) {
+ try {
+ file.close();
+ } catch (Exception ignored) {
+ // Nothing more can be done
+ }
+ }
+ close();
+ throw new IOException("Unable to open file", e);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (mFileChannel != null) {
+ mFileChannel.close();
+ }
+
+ // There is a long-standing bug with memory mapped objects in Java that requires the JVM to
+ // finalize the MappedByteBuffer reference before the unmap operation is performed. This leaks
+ // file handles and fills the virtual address space. Worse, on some systems (Windows for one)
+ // the active mmap prevents the temp file from being deleted - even if File.deleteOnExit() is
+ // used. The only safe way to ensure that file handles and actual files are not leaked by this
+ // class is to force an explicit full gc after explicitly nulling the MappedByteBuffer
+ // reference. This has to be done before attempting file deletion.
+ //
+ // See https://github.com/andrewhayden/archive-patcher/issues/5 for more information.
+ mByteBuffer = null;
+ System.gc();
+
+ if (mShouldDeleteFileOnRelease && mFile != null) {
+ mFile.delete();
+ }
+
+ }
+ }
+}