org.robolectric.res.android.Asset Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of resources Show documentation
Show all versions of resources Show documentation
An alternative Android testing framework.
package org.robolectric.res.android;
import static org.robolectric.res.android.Asset.AccessMode.ACCESS_BUFFER;
import static org.robolectric.res.android.Errors.NO_ERROR;
import static org.robolectric.res.android.Util.ALOGE;
import static org.robolectric.res.android.Util.ALOGV;
import static org.robolectric.res.android.Util.ALOGW;
import static org.robolectric.res.android.Util.isTruthy;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.robolectric.res.FileTypedResource;
import org.robolectric.res.Fs;
// transliterated from
// https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/Asset.cpp
// and
// https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/Asset.h
/*
* Instances of this class provide read-only operations on a byte stream.
*
* Access may be optimized for streaming, random, or whole buffer modes. All
* operations are supported regardless of how the file was opened, but some
* things will be less efficient. [pass that in??]
*
* "Asset" is the base class for all types of assets. The classes below
* provide most of the implementation. The AssetManager uses one of the
* static "create" functions defined here to create a new instance.
*/
@SuppressWarnings("NewApi")
public abstract class Asset {
public static final Asset EXCLUDED_ASSET = new _FileAsset();
public Runnable onClose;
public static Asset newFileAsset(FileTypedResource fileTypedResource) throws IOException {
_FileAsset fileAsset = new _FileAsset();
Path path = fileTypedResource.getPath();
fileAsset.mFileName = Fs.externalize(path);
fileAsset.mLength = Files.size(path);
fileAsset.mBuf = Fs.getBytes(path);
return fileAsset;
}
// public:
// virtual ~Asset(void) = default;
// static int getGlobalCount();
// static String8 getAssetAllocations();
public enum AccessMode {
ACCESS_UNKNOWN(0),
/* read chunks, and seek forward and backward */
ACCESS_RANDOM(1),
/* read sequentially, with an occasional forward seek */
ACCESS_STREAMING(2),
/* caller plans to ask for a read-only buffer with all data */
ACCESS_BUFFER(3);
private final int mode;
AccessMode(int mode) {
this.mode = mode;
}
public int mode() {
return mode;
}
public static AccessMode fromInt(int mode) {
for (AccessMode enumMode : values()) {
if (mode == enumMode.mode()) {
return enumMode;
}
}
throw new IllegalArgumentException("invalid mode " + Integer.toString(mode));
}
}
public static final int SEEK_SET = 0;
public static final int SEEK_CUR = 1;
public static final int SEEK_END = 2;
public final int read(byte[] buf, int count) {
return read(buf, 0, count);
}
/*
* Read data from the current offset. Returns the actual number of
* bytes read, 0 on EOF, or -1 on error.
*
* Transliteration note: added bufOffset to translate to: index into buf to start writing at
*/
public abstract int read(byte[] buf, int bufOffset, int count);
/*
* Seek to the specified offset. "whence" uses the same values as
* lseek/fseek. Returns the new position on success, or (long) -1
* on failure.
*/
public abstract long seek(long offset, int whence);
/*
* Close the asset, freeing all associated resources.
*/
public abstract void close();
/*
* Get a pointer to a buffer with the entire contents of the file.
*/
public abstract byte[] getBuffer(boolean wordAligned);
/*
* Get the total amount of data that can be read.
*/
public abstract long getLength();
/*
* Get the total amount of data that can be read from the current position.
*/
public abstract long getRemainingLength();
/*
* Open a new file descriptor that can be used to read this asset.
* Returns -1 if you can not use the file descriptor (for example if the
* asset is compressed).
*/
public abstract FileDescriptor openFileDescriptor(Ref outStart, Ref outLength);
public abstract File getFile();
public abstract String getFileName();
/*
* Return whether this asset's buffer is allocated in RAM (not mmapped).
* Note: not virtual so it is safe to call even when being destroyed.
*/
abstract boolean isAllocated(); // { return false; }
/*
* Get a string identifying the asset's source. This might be a full
* path, it might be a colon-separated list of identifiers.
*
* This is NOT intended to be used for anything except debug output.
* DO NOT try to parse this or use it to open a file.
*/
final String getAssetSource() { return mAssetSource.string(); }
public abstract boolean isNinePatch();
// protected:
// /*
// * Adds this Asset to the global Asset list for debugging and
// * accounting.
// * Concrete subclasses must call this in their finalructor.
// */
// static void registerAsset(Asset asset);
//
// /*
// * Removes this Asset from the global Asset list.
// * Concrete subclasses must call this in their destructor.
// */
// static void unregisterAsset(Asset asset);
//
// Asset(void); // finalructor; only invoked indirectly
//
// /* handle common seek() housekeeping */
// long handleSeek(long offset, int whence, long curPosn, long maxPosn);
/* set the asset source string */
void setAssetSource(final String8 path) { mAssetSource = path; }
AccessMode getAccessMode() { return mAccessMode; }
// private:
// /* these operations are not implemented */
// Asset(final Asset& src);
// Asset& operator=(final Asset& src);
//
// /* AssetManager needs access to our "create" functions */
// friend class AssetManager;
//
// /*
// * Create the asset from a named file on disk.
// */
// static Asset createFromFile(final String fileName, AccessMode mode);
//
// /*
// * Create the asset from a named, compressed file on disk (e.g. ".gz").
// */
// static Asset createFromCompressedFile(final String fileName,
// AccessMode mode);
//
// #if 0
// /*
// * Create the asset from a segment of an open file. This will fail
// * if "offset" and "length" don't fit within the bounds of the file.
// *
// * The asset takes ownership of the file descriptor.
// */
// static Asset createFromFileSegment(int fd, long offset, int length,
// AccessMode mode);
//
// /*
// * Create from compressed data. "fd" should be seeked to the start of
// * the compressed data. This could be inside a gzip file or part of a
// * Zip archive.
// *
// * The asset takes ownership of the file descriptor.
// *
// * This may not verify the validity of the compressed data until first
// * use.
// */
// static Asset createFromCompressedData(int fd, long offset,
// int compressionMethod, int compressedLength,
// int uncompressedLength, AccessMode mode);
// #endif
//
// /*
// * Create the asset from a memory-mapped file segment.
// *
// * The asset takes ownership of the FileMap.
// */
// static Asset createFromUncompressedMap(FileMap dataMap, AccessMode mode);
//
// /*
// * Create the asset from a memory-mapped file segment with compressed
// * data.
// *
// * The asset takes ownership of the FileMap.
// */
// static Asset createFromCompressedMap(FileMap dataMap,
// int uncompressedLen, AccessMode mode);
//
//
// /*
// * Create from a reference-counted chunk of shared memory.
// */
// // TODO
AccessMode mAccessMode; // how the asset was opened
String8 mAssetSource; // debug string
Asset mNext; // linked list.
Asset mPrev;
static final boolean kIsDebug = false;
final static Object gAssetLock = new Object();
static int gCount = 0;
static Asset gHead = null;
static Asset gTail = null;
void registerAsset(Asset asset)
{
// AutoMutex _l(gAssetLock);
// gCount++;
// asset.mNext = asset.mPrev = null;
// if (gTail == null) {
// gHead = gTail = asset;
// } else {
// asset.mPrev = gTail;
// gTail.mNext = asset;
// gTail = asset;
// }
//
// if (kIsDebug) {
// ALOGI("Creating Asset %s #%d\n", asset, gCount);
// }
}
void unregisterAsset(Asset asset)
{
// AutoMutex _l(gAssetLock);
// gCount--;
// if (gHead == asset) {
// gHead = asset.mNext;
// }
// if (gTail == asset) {
// gTail = asset.mPrev;
// }
// if (asset.mNext != null) {
// asset.mNext.mPrev = asset.mPrev;
// }
// if (asset.mPrev != null) {
// asset.mPrev.mNext = asset.mNext;
// }
// asset.mNext = asset.mPrev = null;
//
// if (kIsDebug) {
// ALOGI("Destroying Asset in %s #%d\n", asset, gCount);
// }
}
public static int getGlobalCount()
{
// AutoMutex _l(gAssetLock);
synchronized (gAssetLock) {
return gCount;
}
}
public static String getAssetAllocations()
{
// AutoMutex _l(gAssetLock);
synchronized (gAssetLock) {
StringBuilder res = new StringBuilder();
Asset cur = gHead;
while (cur != null) {
if (cur.isAllocated()) {
res.append(" ");
res.append(cur.getAssetSource());
long size = (cur.getLength()+512)/1024;
String buf = String.format(": %dK\n", (int)size);
res.append(buf);
}
cur = cur.mNext;
}
return res.toString();
}
}
Asset() {
// : mAccessMode(ACCESS_UNKNOWN), mNext(null), mPrev(null)
mAccessMode = AccessMode.ACCESS_UNKNOWN;
}
/*
* Create a new Asset from a file on disk. There is a fair chance that
* the file doesn't actually exist.
*
* We can use "mode" to decide how we want to go about it.
*/
static Asset createFromFile(final String fileName, AccessMode mode)
{
File file = new File(fileName);
if (!file.exists()) {
return null;
}
throw new UnsupportedOperationException();
// _FileAsset pAsset;
// int result;
// long length;
// int fd;
//
// fd = open(fileName, O_RDONLY | O_BINARY);
// if (fd < 0)
// return null;
//
// /*
// * Under Linux, the lseek fails if we actually opened a directory. To
// * be correct we should test the file type explicitly, but since we
// * always open things read-only it doesn't really matter, so there's
// * no value in incurring the extra overhead of an fstat() call.
// */
// // TODO(kroot): replace this with fstat despite the plea above.
// #if 1
// length = lseek64(fd, 0, SEEK_END);
// if (length < 0) {
// ::close(fd);
// return null;
// }
// (void) lseek64(fd, 0, SEEK_SET);
// #else
// struct stat st;
// if (fstat(fd, &st) < 0) {
// ::close(fd);
// return null;
// }
//
// if (!S_ISREG(st.st_mode)) {
// ::close(fd);
// return null;
// }
// #endif
//
// pAsset = new _FileAsset;
// result = pAsset.openChunk(fileName, fd, 0, length);
// if (result != NO_ERROR) {
// delete pAsset;
// return null;
// }
//
// pAsset.mAccessMode = mode;
// return pAsset;
}
/*
* Create a new Asset from a compressed file on disk. There is a fair chance
* that the file doesn't actually exist.
*
* We currently support gzip files. We might want to handle .bz2 someday.
*/
@SuppressWarnings("DoNotCallSuggester")
static Asset createFromCompressedFile(final String fileName, AccessMode mode) {
throw new UnsupportedOperationException();
// _CompressedAsset pAsset;
// int result;
// long fileLen;
// boolean scanResult;
// long offset;
// int method;
// long uncompressedLen, compressedLen;
// int fd;
//
// fd = open(fileName, O_RDONLY | O_BINARY);
// if (fd < 0)
// return null;
//
// fileLen = lseek(fd, 0, SEEK_END);
// if (fileLen < 0) {
// ::close(fd);
// return null;
// }
// (void) lseek(fd, 0, SEEK_SET);
//
// /* want buffered I/O for the file scan; must dup so fclose() is safe */
// FILE* fp = fdopen(dup(fd), "rb");
// if (fp == null) {
// ::close(fd);
// return null;
// }
//
// unsigned long crc32;
// scanResult = ZipUtils::examineGzip(fp, &method, &uncompressedLen,
// &compressedLen, &crc32);
// offset = ftell(fp);
// fclose(fp);
// if (!scanResult) {
// ALOGD("File '%s' is not in gzip format\n", fileName);
// ::close(fd);
// return null;
// }
//
// pAsset = new _CompressedAsset;
// result = pAsset.openChunk(fd, offset, method, uncompressedLen,
// compressedLen);
// if (result != NO_ERROR) {
// delete pAsset;
// return null;
// }
//
// pAsset.mAccessMode = mode;
// return pAsset;
}
// #if 0
// /*
// * Create a new Asset from part of an open file.
// */
// /*static*/ Asset createFromFileSegment(int fd, long offset,
// int length, AccessMode mode)
// {
// _FileAsset pAsset;
// int result;
//
// pAsset = new _FileAsset;
// result = pAsset.openChunk(null, fd, offset, length);
// if (result != NO_ERROR)
// return null;
//
// pAsset.mAccessMode = mode;
// return pAsset;
// }
//
// /*
// * Create a new Asset from compressed data in an open file.
// */
// /*static*/ Asset createFromCompressedData(int fd, long offset,
// int compressionMethod, int uncompressedLen, int compressedLen,
// AccessMode mode)
// {
// _CompressedAsset pAsset;
// int result;
//
// pAsset = new _CompressedAsset;
// result = pAsset.openChunk(fd, offset, compressionMethod,
// uncompressedLen, compressedLen);
// if (result != NO_ERROR)
// return null;
//
// pAsset.mAccessMode = mode;
// return pAsset;
// }
// #endif
/*
* Create a new Asset from a memory mapping.
*/
static Asset createFromUncompressedMap(FileMap dataMap,
AccessMode mode)
{
_FileAsset pAsset;
int result;
pAsset = new _FileAsset();
result = pAsset.openChunk(dataMap);
if (result != NO_ERROR)
return null;
pAsset.mAccessMode = mode;
return pAsset;
}
/*
* Create a new Asset from compressed data in a memory mapping.
*/
static Asset createFromCompressedMap(FileMap dataMap,
int uncompressedLen, AccessMode mode)
{
_CompressedAsset pAsset;
int result;
pAsset = new _CompressedAsset();
result = pAsset.openChunk(dataMap, uncompressedLen);
if (result != NO_ERROR)
return null;
pAsset.mAccessMode = mode;
return pAsset;
}
/*
* Do generic seek() housekeeping. Pass in the offset/whence values from
* the seek request, along with the current chunk offset and the chunk
* length.
*
* Returns the new chunk offset, or -1 if the seek is illegal.
*/
long handleSeek(long offset, int whence, long curPosn, long maxPosn)
{
long newOffset;
switch (whence) {
case SEEK_SET:
newOffset = offset;
break;
case SEEK_CUR:
newOffset = curPosn + offset;
break;
case SEEK_END:
newOffset = maxPosn + offset;
break;
default:
ALOGW("unexpected whence %d\n", whence);
// this was happening due to an long size mismatch
assert(false);
return (long) -1;
}
if (newOffset < 0 || newOffset > maxPosn) {
ALOGW("seek out of range: want %d, end=%d\n",
(long) newOffset, (long) maxPosn);
return (long) -1;
}
return newOffset;
}
/*
* An asset based on an uncompressed file on disk. It may encompass the
* entire file or just a piece of it. Access is through fread/fseek.
*/
static class _FileAsset extends Asset {
// public:
// _FileAsset(void);
// virtual ~_FileAsset(void);
//
// /*
// * Use a piece of an already-open file.
// *
// * On success, the object takes ownership of "fd".
// */
// int openChunk(final String fileName, int fd, long offset, int length);
//
// /*
// * Use a memory-mapped region.
// *
// * On success, the object takes ownership of "dataMap".
// */
// int openChunk(FileMap dataMap);
//
// /*
// * Standard Asset interfaces.
// */
// virtual ssize_t read(void* buf, int count);
// virtual long seek(long offset, int whence);
// virtual void close(void);
// virtual final void* getBuffer(boolean wordAligned);
@Override
public long getLength() { return mLength; }
@Override
public long getRemainingLength() { return mLength-mOffset; }
// virtual int openFileDescriptor(long* outStart, long* outLength) final;
@Override
boolean isAllocated() { return mBuf != null; }
@Override
public boolean isNinePatch() {
String fileName = getFileName();
if (mMap != null) {
fileName = mMap.getZipEntry().getName();
}
return fileName != null && fileName.toLowerCase().endsWith(".9.png");
}
//
// private:
long mStart; // absolute file offset of start of chunk
long mLength; // length of the chunk
long mOffset; // current local offset, 0 == mStart
// FILE* mFp; // for read/seek
RandomAccessFile mFp; // for read/seek
String mFileName; // for opening
/*
* To support getBuffer() we either need to read the entire thing into
* a buffer or memory-map it. For small files it's probably best to
* just read them in.
*/
// enum {
public static int kReadVsMapThreshold = 4096;
// };
FileMap mMap; // for memory map
byte[] mBuf; // for read
// final void* ensureAlignment(FileMap map);
/*
* ===========================================================================
* _FileAsset
* ===========================================================================
*/
/*
* Constructor.
*/
_FileAsset()
// : mStart(0), mLength(0), mOffset(0), mFp(null), mFileName(null), mMap(null), mBuf(null)
{
// Register the Asset with the global list here after it is fully constructed and its
// vtable pointer points to this concrete type.
registerAsset(this);
}
/*
* Destructor. Release resources.
*/
@Override
protected void finalize() {
close();
// Unregister the Asset from the global list here before it is destructed and while its vtable
// pointer still points to this concrete type.
unregisterAsset(this);
}
/*
* Operate on a chunk of an uncompressed file.
*
* Zero-length chunks are allowed.
*/
int openChunk(final String fileName, int fd, long offset, int length) {
throw new UnsupportedOperationException();
// assert(mFp == null); // no reopen
// assert(mMap == null);
// assert(fd >= 0);
// assert(offset >= 0);
//
// /*
// * Seek to end to get file length.
// */
// long fileLength;
// fileLength = lseek64(fd, 0, SEEK_END);
// if (fileLength == (long) -1) {
// // probably a bad file descriptor
// ALOGD("failed lseek (errno=%d)\n", errno);
// return UNKNOWN_ERROR;
// }
//
// if ((long) (offset + length) > fileLength) {
// ALOGD("start (%ld) + len (%ld) > end (%ld)\n",
// (long) offset, (long) length, (long) fileLength);
// return BAD_INDEX;
// }
//
// /* after fdopen, the fd will be closed on fclose() */
// mFp = fdopen(fd, "rb");
// if (mFp == null)
// return UNKNOWN_ERROR;
//
// mStart = offset;
// mLength = length;
// assert(mOffset == 0);
//
// /* seek the FILE* to the start of chunk */
// if (fseek(mFp, mStart, SEEK_SET) != 0) {
// assert(false);
// }
//
// mFileName = fileName != null ? strdup(fileName) : null;
//
// return NO_ERROR;
}
/*
* Create the chunk from the map.
*/
int openChunk(FileMap dataMap) {
assert(mFp == null); // no reopen
assert(mMap == null);
assert(dataMap != null);
mMap = dataMap;
mStart = -1; // not used
mLength = dataMap.getDataLength();
assert(mOffset == 0);
mBuf = dataMap.getDataPtr();
return NO_ERROR;
}
/*
* Read a chunk of data.
*/
@Override
public int read(byte[] buf, int bufOffset, int count) {
int maxLen;
int actual;
assert(mOffset >= 0 && mOffset <= mLength);
if (getAccessMode() == ACCESS_BUFFER) {
/*
* On first access, read or map the entire file. The caller has
* requested buffer access, either because they're going to be
* using the buffer or because what they're doing has appropriate
* performance needs and access patterns.
*/
if (mBuf == null)
getBuffer(false);
}
/* adjust count if we're near EOF */
maxLen = toIntExact(mLength - mOffset);
if (count > maxLen)
count = maxLen;
if (!isTruthy(count)) {
return 0;
}
if (mMap != null) {
/* copy from mapped area */
//printf("map read\n");
// memcpy(buf, (String)mMap.getDataPtr() + mOffset, count);
System.arraycopy(mMap.getDataPtr(), toIntExact(mOffset), buf, bufOffset, count);
actual = count;
} else if (mBuf != null) {
/* copy from buffer */
//printf("buf read\n");
// memcpy(buf, (String)mBuf + mOffset, count);
System.arraycopy(mBuf, toIntExact(mOffset), buf, bufOffset, count);
actual = count;
} else {
/* read from the file */
//printf("file read\n");
// if (ftell(mFp) != mStart + mOffset) {
try {
if (mFp.getFilePointer() != mStart + mOffset) {
ALOGE("Hosed: %d != %d+%d\n",
mFp.getFilePointer(), (long) mStart, (long) mOffset);
assert(false);
}
/*
* This returns 0 on error or eof. We need to use ferror() or feof()
* to tell the difference, but we don't currently have those on the
* device. However, we know how much data is *supposed* to be in the
* file, so if we don't read the full amount we know something is
* hosed.
*/
actual = mFp.read(buf, 0, count);
if (actual == 0) // something failed -- I/O error?
return -1;
assert(actual == count);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
mOffset += actual;
return actual;
}
/*
* Seek to a new position.
*/
@Override
public long seek(long offset, int whence) {
long newPosn;
long actualOffset;
// compute new position within chunk
newPosn = handleSeek(offset, whence, mOffset, mLength);
if (newPosn == (long) -1)
return newPosn;
actualOffset = mStart + newPosn;
if (mFp != null) {
throw new UnsupportedOperationException();
// if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0)
// return (long) -1;
}
mOffset = actualOffset - mStart;
return mOffset;
}
/*
* Close the asset.
*/
@Override
public void close() {
throw new UnsupportedOperationException();
// if (mMap != null) {
// delete mMap;
// mMap = null;
// }
// if (mBuf != null) {
// delete[] mBuf;
// mBuf = null;
// }
//
// if (mFileName != null) {
// free(mFileName);
// mFileName = null;
// }
//
// if (mFp != null) {
// // can only be null when called from destructor
// // (otherwise we would never return this object)
// fclose(mFp);
// mFp = null;
// }
}
/*
* Return a read-only pointer to a buffer.
*
* We can either read the whole thing in or map the relevant piece of
* the source file. Ideally a map would be established at a higher
* level and we'd be using a different object, but we didn't, so we
* deal with it here.
*/
@Override
public final byte[] getBuffer(boolean wordAligned) {
/* subsequent requests just use what we did previously */
if (mBuf != null)
return mBuf;
if (mMap != null) {
// if (!wordAligned) {
return mMap.getDataPtr();
// }
// return ensureAlignment(mMap);
}
// assert(mFp != null);
if (true /*mLength < kReadVsMapThreshold*/) {
byte[] buf;
int allocLen;
/* zero-length files are allowed; not sure about zero-len allocs */
/* (works fine with gcc + x86linux) */
allocLen = toIntExact(mLength);
if (mLength == 0)
allocLen = 1;
buf = new byte[allocLen];
if (buf == null) {
ALOGE("alloc of %d bytes failed\n", (long) allocLen);
return null;
}
ALOGV("Asset %s allocating buffer size %d (smaller than threshold)", this, (int)allocLen);
if (mLength > 0) {
try {
// long oldPosn = ftell(mFp);
long oldPosn = mFp.getFilePointer();
// fseek(mFp, mStart, SEEK_SET);
mFp.seek(mStart);
// if (fread(buf, 1, mLength, mFp) != (size_t) mLength) {
if (mFp.read(buf, 0, toIntExact(mLength)) != (int) mLength) {
ALOGE("failed reading %d bytes\n", (long) mLength);
// delete[] buf;
return null;
}
// fseek(mFp, oldPosn, SEEK_SET);
mFp.seek(oldPosn);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
ALOGV(" getBuffer: loaded into buffer\n");
mBuf = buf;
return mBuf;
} else {
FileMap map;
map = new FileMap();
// if (!map.create(null, fileno(mFp), mStart, mLength, true)) {
if (!map.create(null, -1, mStart, toIntExact(mLength), true)) {
// delete map;
return null;
}
ALOGV(" getBuffer: mapped\n");
mMap = map;
// if (!wordAligned) {
// return mMap.getDataPtr();
// }
return ensureAlignment(mMap);
}
}
/**
* Return the file on disk representing this asset.
*
* Non-Android framework method. Based on {@link #openFileDescriptor(Ref, Ref)}.
*/
@Override
public File getFile() {
if (mMap != null) {
String fname = mMap.getFileName();
if (fname == null) {
fname = mFileName;
}
if (fname == null) {
return null;
}
// return open(fname, O_RDONLY | O_BINARY);
return new File(fname);
}
if (mFileName == null) {
return null;
}
return new File(mFileName);
}
@Override
public String getFileName() {
File file = getFile();
return file == null ? null : file.getName();
}
@Override
public FileDescriptor openFileDescriptor(Ref outStart, Ref outLength) {
if (mMap != null) {
String fname = mMap.getFileName();
if (fname == null) {
fname = mFileName;
}
if (fname == null) {
return null;
}
outStart.set(mMap.getDataOffset());
outLength.set((long) mMap.getDataLength());
// return open(fname, O_RDONLY | O_BINARY);
return open(fname);
}
if (mFileName == null) {
return null;
}
outStart.set(mStart);
outLength.set(mLength);
// return open(mFileName, O_RDONLY | O_BINARY);
return open(mFileName);
}
private static FileDescriptor open(String fname) {
try {
return new FileInputStream(new File(fname)).getFD();
} catch (IOException e) {
return null;
}
}
@SuppressWarnings("DoNotCallSuggester")
final byte[] ensureAlignment(FileMap map) {
throw new UnsupportedOperationException();
// void* data = map.getDataPtr();
// if ((((int)data)&0x3) == 0) {
// // We can return this directly if it is aligned on a word
// // boundary.
// ALOGV("Returning aligned FileAsset %s (%s).", this,
// getAssetSource());
// return data;
// }
// // If not aligned on a word boundary, then we need to copy it into
// // our own buffer.
// ALOGV("Copying FileAsset %s (%s) to buffer size %d to make it aligned.", this,
// getAssetSource(), (int)mLength);
// unsigned String buf = new unsigned char[mLength];
// if (buf == null) {
// ALOGE("alloc of %ld bytes failed\n", (long) mLength);
// return null;
// }
// memcpy(buf, data, mLength);
// mBuf = buf;
// return buf;
// }
}
@Override
public String toString() {
if (mFileName == null) {
return "_FileAsset{" +
"mMap=" + mMap +
'}';
} else {
return "_FileAsset{" +
"mFileName='" + mFileName + '\'' +
'}';
}
}
}
/*
* An asset based on compressed data in a file.
*/
static class _CompressedAsset extends Asset {
// public:
// _CompressedAsset(void);
// virtual ~_CompressedAsset(void);
//
// /*
// * Use a piece of an already-open file.
// *
// * On success, the object takes ownership of "fd".
// */
// int openChunk(int fd, long offset, int compressionMethod,
// int uncompressedLen, int compressedLen);
//
// /*
// * Use a memory-mapped region.
// *
// * On success, the object takes ownership of "fd".
// */
// int openChunk(FileMap dataMap, int uncompressedLen);
//
// /*
// * Standard Asset interfaces.
// */
// virtual ssize_t read(void* buf, int count);
// virtual long seek(long offset, int whence);
// virtual void close(void);
// virtual final void* getBuffer(boolean wordAligned);
@Override
public long getLength() { return mUncompressedLen; }
@Override
public long getRemainingLength() { return mUncompressedLen-mOffset; }
@Override
public File getFile() {
return null;
}
@Override
public String getFileName() {
ZipEntry zipEntry = mMap.getZipEntry();
return zipEntry == null ? null : zipEntry.getName();
}
@Override
public FileDescriptor openFileDescriptor(Ref outStart, Ref outLength) { return null; }
@Override
boolean isAllocated() { return mBuf != null; }
@Override
public boolean isNinePatch() {
String fileName = getFileName();
return fileName != null && fileName.toLowerCase().endsWith(".9.png");
}
// private:
long mStart; // offset to start of compressed data
long mCompressedLen; // length of the compressed data
long mUncompressedLen; // length of the uncompressed data
long mOffset; // current offset, 0 == start of uncomp data
FileMap mMap; // for memory-mapped input
int mFd; // for file input
// class StreamingZipInflater mZipInflater; // for streaming large compressed assets
byte[] mBuf; // for getBuffer()
/*
* ===========================================================================
* _CompressedAsset
* ===========================================================================
*/
/*
* Constructor.
*/
_CompressedAsset()
// : mStart(0), mCompressedLen(0), mUncompressedLen(0), mOffset(0),
// mMap(null), mFd(-1), mZipInflater(null), mBuf(null)
{
mFd = -1;
// Register the Asset with the global list here after it is fully constructed and its
// vtable pointer points to this concrete type.
registerAsset(this);
}
ZipFile zipFile;
String entryName;
// @Override
// public byte[] getBuffer(boolean wordAligned) {
// ZipEntry zipEntry = zipFile.getEntry(entryName);
// int size = (int) zipEntry.getSize();
// byte[] buf = new byte[size];
// try (InputStream in = zipFile.getInputStream(zipEntry)) {
// if (in.read(buf) != size) {
// throw new IOException(
// "Failed to read " + size + " bytes from " + zipFile + "!" + entryName);
// }
// return buf;
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// }
/*
* Destructor. Release resources.
*/
@Override
protected void finalize() {
close();
// Unregister the Asset from the global list here before it is destructed and while its vtable
// pointer still points to this concrete type.
unregisterAsset(this);
}
/*
* Open a chunk of compressed data inside a file.
*
* This currently just sets up some values and returns. On the first
* read, we expand the entire file into a buffer and return data from it.
*/
int openChunk(int fd, long offset,
int compressionMethod, int uncompressedLen, int compressedLen) {
throw new UnsupportedOperationException();
// assert(mFd < 0); // no re-open
// assert(mMap == null);
// assert(fd >= 0);
// assert(offset >= 0);
// assert(compressedLen > 0);
//
// if (compressionMethod != ZipFileRO::kCompressDeflated) {
// assert(false);
// return UNKNOWN_ERROR;
// }
//
// mStart = offset;
// mCompressedLen = compressedLen;
// mUncompressedLen = uncompressedLen;
// assert(mOffset == 0);
// mFd = fd;
// assert(mBuf == null);
//
// if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
// mZipInflater = new StreamingZipInflater(mFd, offset, uncompressedLen, compressedLen);
// }
//
// return NO_ERROR;
}
/*
* Open a chunk of compressed data in a mapped region.
*
* Nothing is expanded until the first read call.
*/
int openChunk(FileMap dataMap, int uncompressedLen) {
assert(mFd < 0); // no re-open
assert(mMap == null);
assert(dataMap != null);
mMap = dataMap;
mStart = -1; // not used
mCompressedLen = dataMap.getDataLength();
mUncompressedLen = uncompressedLen;
assert(mOffset == 0);
// if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
// mZipInflater = new StreamingZipInflater(dataMap, uncompressedLen);
// }
return NO_ERROR;
}
/*
* Read data from a chunk of compressed data.
*
* [For now, that's just copying data out of a buffer.]
*/
@Override
public int read(byte[] buf, int bufOffset, int count) {
int maxLen;
int actual;
assert(mOffset >= 0 && mOffset <= mUncompressedLen);
/* If we're relying on a streaming inflater, go through that */
// if (mZipInflater) {
// actual = mZipInflater.read(buf, count);
// } else {
if (mBuf == null) {
if (getBuffer(false) == null)
return -1;
}
assert(mBuf != null);
/* adjust count if we're near EOF */
maxLen = toIntExact(mUncompressedLen - mOffset);
if (count > maxLen)
count = maxLen;
if (!isTruthy(count))
return 0;
/* copy from buffer */
//printf("comp buf read\n");
// memcpy(buf, (String)mBuf + mOffset, count);
System.arraycopy(mBuf, toIntExact(mOffset), buf, bufOffset, count);
actual = count;
// }
mOffset += actual;
return actual;
}
/*
* Handle a seek request.
*
* If we're working in a streaming mode, this is going to be fairly
* expensive, because it requires plowing through a bunch of compressed
* data.
*/
@Override
public long seek(long offset, int whence) {
long newPosn;
// compute new position within chunk
newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen);
if (newPosn == -1) return newPosn;
// if (mZipInflater) {
// mZipInflater.seekAbsolute(newPosn);
// }
mOffset = newPosn;
return mOffset;
}
/*
* Close the asset.
*/
@Override
public void close() {
if (mMap != null) {
// delete mMap;
mMap = null;
}
// delete[] mBuf;
mBuf = null;
// delete mZipInflater;
// mZipInflater = null;
if (mFd > 0) {
// ::close(mFd);
mFd = -1;
}
}
/*
* Get a pointer to a read-only buffer of data.
*
* The first time this is called, we expand the compressed data into a
* buffer.
*/
@Override
public byte[] getBuffer(boolean wordAligned) {
// return mBuf = mMap.getDataPtr();
byte[] buf = null;
if (mBuf != null)
return mBuf;
/*
* Allocate a buffer and read the file into it.
*/
// buf = new byte[(int) mUncompressedLen];
// if (buf == null) {
// ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen);
// return null;
// }
if (mMap != null) {
buf = mMap.getDataPtr();
// if (!ZipUtils::inflateToBuffer(mMap.getDataPtr(), buf,
// mUncompressedLen, mCompressedLen))
// return null;
} else {
throw new UnsupportedOperationException();
// assert(mFd >= 0);
//
// /*
// * Seek to the start of the compressed data.
// */
// if (lseek(mFd, mStart, SEEK_SET) != mStart)
// goto bail;
//
// /*
// * Expand the data into it.
// */
// if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen,
// mCompressedLen))
// goto bail;
}
/*
* Success - now that we have the full asset in RAM we
* no longer need the streaming inflater
*/
// delete mZipInflater;
// mZipInflater = null;
mBuf = buf;
// buf = null;
// bail:
// delete[] buf;
return mBuf;
}
@Override
public String toString() {
return "_CompressedAsset{" +
"mMap=" + mMap +
'}';
}
}
// todo: remove when Android supports this
static int toIntExact(long value) {
if ((int)value != value) {
throw new ArithmeticException("integer overflow");
}
return (int)value;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy