All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.res.android.Asset Maven / Gradle / Ivy

There is a newer version: 4.14.1
Show newest version
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