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

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

There is a newer version: 4.14.1
Show newest version
package org.robolectric.res.android;

// transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ResourceTypes.cpp
//   and https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/include/androidfw/ResourceTypes.h

import static org.robolectric.res.android.Errors.BAD_TYPE;
import static org.robolectric.res.android.Errors.NAME_NOT_FOUND;
import static org.robolectric.res.android.Errors.NO_ERROR;
import static org.robolectric.res.android.Errors.NO_INIT;
import static org.robolectric.res.android.ResourceString.decodeString;
import static org.robolectric.res.android.ResourceTypes.validate_chunk;
import static org.robolectric.res.android.Util.ALOGI;
import static org.robolectric.res.android.Util.ALOGW;
import static org.robolectric.res.android.Util.SIZEOF_INT;
import static org.robolectric.res.android.Util.isTruthy;

import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Objects;
import org.robolectric.res.android.ResourceTypes.ResChunk_header;
import org.robolectric.res.android.ResourceTypes.ResStringPool_header;
import org.robolectric.res.android.ResourceTypes.ResStringPool_header.Writer;
import org.robolectric.res.android.ResourceTypes.ResStringPool_ref;
import org.robolectric.res.android.ResourceTypes.ResStringPool_span;
import org.robolectric.res.android.ResourceTypes.WithOffset;

/**
 * Convenience class for accessing data in a ResStringPool resource.
 */
@SuppressWarnings("NewApi")
public class ResStringPool {

  private static boolean kDebugStringPoolNoisy = false;

  private final long myNativePtr;

  private int                    mError;

   byte[]                       mOwnedData;
  //private Object mOwnedData;

  private ResStringPool_header mHeader;
  private int                      mSize;
//    private mutable Mutex               mDecodeLock;
//    const uint32_t*             mEntries;
  private IntArray             mEntries;
//    const uint32_t*             mEntryStyles;
    private IntArray             mEntryStyles;
//    const void*                 mStrings;
    private int                 mStrings;
  //private List mStrings;
  //private String[] mCache;
  //private char16_t mutable**          mCache;
    private int                    mStringPoolSize;    // number of uint16_t
//    const uint32_t*             mStyles;
    private int             mStyles;
    private int                    mStylePoolSize;    // number of int

  public ResStringPool() {
    mError = NO_INIT;
    myNativePtr = Registries.NATIVE_STRING_POOLS.register(new WeakReference<>(this));
  }

  @Override
  protected void finalize() throws Throwable {
    Registries.NATIVE_STRING_POOLS.unregister(myNativePtr);
  }

  public long getNativePtr() {
    return myNativePtr;
  }

  public static ResStringPool getNativeObject(long nativeId) {
    return Registries.NATIVE_STRING_POOLS.getNativeObject(nativeId).get();
  }

  static class IntArray extends WithOffset {
    IntArray(ByteBuffer buf, int offset) {
      super(buf, offset);
    }

    int get(int idx) {
      return myBuf().getInt(myOffset() + idx * SIZEOF_INT);
    }
  }

  void setToEmpty()
  {
    uninit();

    ByteBuffer buf = ByteBuffer.allocate(16 * 1024).order(ByteOrder.LITTLE_ENDIAN);
    Writer resStringPoolWriter = new Writer();
    resStringPoolWriter.write(buf);
    mOwnedData = new byte[buf.position()];
    buf.position();
    buf.get(mOwnedData);

    ResStringPool_header header = new ResStringPool_header(buf, 0);
    mSize = 0;
    mEntries = null;
    mStrings = 0;
    mStringPoolSize = 0;
    mEntryStyles = null;
    mStyles = 0;
    mStylePoolSize = 0;
    mHeader = header;
  }

  //  status_t setTo(const void* data, size_t size, bool copyData=false);
  public int setTo(ByteBuffer buf, int offset, int size, boolean copyData) {
    if (!isTruthy(buf) || !isTruthy(size)) {
      return (mError=BAD_TYPE);
    }

    uninit();

    // The chunk must be at least the size of the string pool header.
    if (size < ResStringPool_header.SIZEOF) {
      ALOGW("Bad string block: data size %d is too small to be a string block", size);
      return (mError=BAD_TYPE);
    }

    // The data is at least as big as a ResChunk_header, so we can safely validate the other
    // header fields.
    // `data + size` is safe because the source of `size` comes from the kernel/filesystem.
    if (validate_chunk(new ResChunk_header(buf, offset), ResStringPool_header.SIZEOF,
        size,
        "ResStringPool_header") != NO_ERROR) {
      ALOGW("Bad string block: malformed block dimensions");
      return (mError=BAD_TYPE);
    }

//    final boolean notDeviceEndian = htods((short) 0xf0) != 0xf0;
//
//    if (copyData || notDeviceEndian) {
//      mOwnedData = data;
//      if (mOwnedData == null) {
//        return (mError=NO_MEMORY);
//      }
////      memcpy(mOwnedData, data, size);
//      data = mOwnedData;
//    }

    // The size has been checked, so it is safe to read the data in the ResStringPool_header
    // data structure.
    mHeader = new ResStringPool_header(buf, offset);

//    if (notDeviceEndian) {
//      ResStringPool_header h = final_cast(mHeader);
//      h.header.headerSize = dtohs(mHeader.header.headerSize);
//      h.header.type = dtohs(mHeader.header.type);
//      h.header.size = dtohl(mHeader.header.size);
//      h.stringCount = dtohl(mHeader.stringCount);
//      h.styleCount = dtohl(mHeader.styleCount);
//      h.flags = dtohl(mHeader.flags);
//      h.stringsStart = dtohl(mHeader.stringsStart);
//      h.stylesStart = dtohl(mHeader.stylesStart);
//    }

    if (mHeader.header.headerSize > mHeader.header.size
        || mHeader.header.size > size) {
      ALOGW("Bad string block: header size %d or total size %d is larger than data size %d\n",
          (int)mHeader.header.headerSize, (int)mHeader.header.size, (int)size);
      return (mError=BAD_TYPE);
    }
    mSize = mHeader.header.size;
    mEntries = new IntArray(mHeader.myBuf(), mHeader.myOffset() + mHeader.header.headerSize);

    if (mHeader.stringCount > 0) {
      if ((mHeader.stringCount*4 /*sizeof(uint32_t)*/ < mHeader.stringCount)  // uint32 overflow?
          || (mHeader.header.headerSize+(mHeader.stringCount*4 /*sizeof(uint32_t)*/))
          > size) {
        ALOGW("Bad string block: entry of %d items extends past data size %d\n",
            (int)(mHeader.header.headerSize+(mHeader.stringCount*4/*sizeof(uint32_t)*/)),
            (int)size);
        return (mError=BAD_TYPE);
      }

      int charSize;
      if (isTruthy(mHeader.flags & ResStringPool_header.UTF8_FLAG)) {
        charSize = 1 /*sizeof(uint8_t)*/;
      } else {
        charSize = 2 /*sizeof(uint16_t)*/;
      }

      // There should be at least space for the smallest string
      // (2 bytes length, null terminator).
      if (mHeader.stringsStart >= (mSize - 2 /*sizeof(uint16_t)*/)) {
        ALOGW("Bad string block: string pool starts at %d, after total size %d\n",
            (int)mHeader.stringsStart, (int)mHeader.header.size);
        return (mError=BAD_TYPE);
      }

      mStrings = mHeader.stringsStart;

      if (mHeader.styleCount == 0) {
        mStringPoolSize = (mSize - mHeader.stringsStart) / charSize;
      } else {
        // check invariant: styles starts before end of data
        if (mHeader.stylesStart >= (mSize - 2 /*sizeof(uint16_t)*/)) {
          ALOGW("Bad style block: style block starts at %d past data size of %d\n",
              (int)mHeader.stylesStart, (int)mHeader.header.size);
          return (mError=BAD_TYPE);
        }
        // check invariant: styles follow the strings
        if (mHeader.stylesStart <= mHeader.stringsStart) {
          ALOGW("Bad style block: style block starts at %d, before strings at %d\n",
              (int)mHeader.stylesStart, (int)mHeader.stringsStart);
          return (mError=BAD_TYPE);
        }
        mStringPoolSize =
            (mHeader.stylesStart-mHeader.stringsStart)/charSize;
      }

      // check invariant: stringCount > 0 requires a string pool to exist
      if (mStringPoolSize == 0) {
        ALOGW("Bad string block: stringCount is %d but pool size is 0\n", (int)mHeader.stringCount);
        return (mError=BAD_TYPE);
      }

      //      if (notDeviceEndian) {
      //        int i;
      //        uint32_t* e = final_cast(mEntries);
      //        for (i=0; i(strings);
      //          for (i=0; iflags&ResStringPool_header::UTF8_FLAG &&
      //          ((uint8_t*)mStrings)[mStringPoolSize-1] != 0) ||
      //      (!(mHeader->flags&ResStringPool_header::UTF8_FLAG) &&
      //          ((uint16_t*)mStrings)[mStringPoolSize-1] != 0)) {

      if ((isTruthy(mHeader.flags & ResStringPool_header.UTF8_FLAG)
              && (mHeader.getByte(mStrings + mStringPoolSize - 1) != 0))
          || (!isTruthy(mHeader.flags & ResStringPool_header.UTF8_FLAG)
              && (mHeader.getShort(mStrings + mStringPoolSize * 2 - 2) != 0))) {
        ALOGW("Bad string block: last string is not 0-terminated\n");
        return (mError=BAD_TYPE);
      }
    } else {
      mStrings = -1;
      mStringPoolSize = 0;
    }

    if (mHeader.styleCount > 0) {
      mEntryStyles = new IntArray(mEntries.myBuf(), mEntries.myOffset() + mHeader.stringCount * SIZEOF_INT);
      // invariant: integer overflow in calculating mEntryStyles
      if (mEntryStyles.myOffset() < mEntries.myOffset()) {
        ALOGW("Bad string block: integer overflow finding styles\n");
        return (mError=BAD_TYPE);
      }

//      if (((const uint8_t*)mEntryStyles-(const uint8_t*)mHeader) > (int)size) {
      if ((mEntryStyles.myOffset() - mHeader.myOffset()) > (int)size) {
        ALOGW(
            "Bad string block: entry of %d styles extends past data size %d\n",
            (int) mEntryStyles.myOffset(), (int) size);
        return (mError=BAD_TYPE);
      }
      mStyles = mHeader.stylesStart;
      if (mHeader.stylesStart >= mHeader.header.size) {
        ALOGW("Bad string block: style pool starts %d, after total size %d\n",
            (int)mHeader.stylesStart, (int)mHeader.header.size);
        return (mError=BAD_TYPE);
      }
      mStylePoolSize =
          (mHeader.header.size-mHeader.stylesStart) /* / sizeof(uint32_t)*/;

//      if (notDeviceEndian) {
//        size_t i;
//        uint32_t* e = final_cast(mEntryStyles);
//        for (i=0; i(mStyles);
//        for (i=0; i();
//    Collections.addAll(mStrings, xmlStringPool.strings());
//  }

  private int setError(int error) {
    mError = error;
    return mError;
  }

  void uninit() {
    setError(NO_INIT);
    mHeader = null;
  }

  public String stringAt(int idx) {
    if (mError == NO_ERROR && idx < mHeader.stringCount) {
        final boolean isUTF8 = (mHeader.flags&ResStringPool_header.UTF8_FLAG) != 0;
//        const uint32_t off = mEntries[idx]/(isUTF8?sizeof(uint8_t):sizeof(uint16_t));
      ByteBuffer buf = mHeader.myBuf();
      int bufOffset = mHeader.myOffset();
      // const uint32_t off = mEntries[idx]/(isUTF8?sizeof(uint8_t):sizeof(uint16_t));
      final int off = mEntries.get(idx)
            /(isUTF8?1/*sizeof(uint8_t)*/:2/*sizeof(uint16_t)*/);
      if (off < (mStringPoolSize-1)) {
        if (!isUTF8) {
          final int strings = mStrings;
          final int str = strings+off*2;
          return decodeString(buf, bufOffset + str, ResourceString.Type.UTF16);
//          int u16len = decodeLengthUTF16(buf, bufOffset + str);
//          if ((str+u16len*2-strings) < mStringPoolSize) {
//            // Reject malformed (non null-terminated) strings
//            if (buf.getShort(bufOffset + str + u16len*2) != 0x0000) {
//              ALOGW("Bad string block: string #%d is not null-terminated",
//                  (int)idx);
//              return null;
//            }
//            byte[] bytes = new byte[u16len * 2];
//            buf.position(bufOffset + str);
//            buf.get(bytes);
//               // Reject malformed (non null-terminated) strings
//               if (str[encLen] != 0x00) {
//                   ALOGW("Bad string block: string #%d is not null-terminated",
//                         (int)idx);
//                   return NULL;
//               }
//            return new String(bytes, StandardCharsets.UTF_16);
//          } else {
//            ALOGW("Bad string block: string #%d extends to %d, past end at %d\n",
//                (int)idx, (int)(str+u16len-strings), (int)mStringPoolSize);
//          }
        } else {
          final int strings = mStrings;
          final int u8str = strings+off;
          return decodeString(buf, bufOffset + u8str, ResourceString.Type.UTF8);

//                *u16len = decodeLength(&u8str);
//          size_t u8len = decodeLength(&u8str);
//
//          // encLen must be less than 0x7FFF due to encoding.
//          if ((uint32_t)(u8str+u8len-strings) < mStringPoolSize) {
//            AutoMutex lock(mDecodeLock);
//
//            if (mCache != NULL && mCache[idx] != NULL) {
//              return mCache[idx];
//            }
//
//            // Retrieve the actual length of the utf8 string if the
//            // encoded length was truncated
//            if (stringDecodeAt(idx, u8str, u8len, &u8len) == NULL) {
//                return NULL;
//            }
//
//            // Since AAPT truncated lengths longer than 0x7FFF, check
//            // that the bits that remain after truncation at least match
//            // the bits of the actual length
//            ssize_t actualLen = utf8_to_utf16_length(u8str, u8len);
//            if (actualLen < 0 || ((size_t)actualLen & 0x7FFF) != *u16len) {
//              ALOGW("Bad string block: string #%lld decoded length is not correct "
//                  "%lld vs %llu\n",
//                  (long long)idx, (long long)actualLen, (long long)*u16len);
//              return NULL;
//            }
//
//            utf8_to_utf16(u8str, u8len, u16str, *u16len + 1);
//
//            if (mCache == NULL) {
// #ifndef __ANDROID__
//                if (kDebugStringPoolNoisy) {
//                    ALOGI("CREATING STRING CACHE OF %zu bytes",
//                          mHeader->stringCount*sizeof(char16_t**));
//                }
// #else
//                // We do not want to be in this case when actually running Android.
//                ALOGW("CREATING STRING CACHE OF %zu bytes",
//                        static_cast(mHeader->stringCount*sizeof(char16_t**)));
// #endif
//                mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t*));
//                if (mCache == NULL) {
//                    ALOGW("No memory trying to allocate decode cache table of %d bytes\n",
//                          (int)(mHeader->stringCount*sizeof(char16_t**)));
//                    return NULL;
//                }
//            }
//            *u16len = (size_t) actualLen;
//            char16_t *u16str = (char16_t *)calloc(*u16len+1, sizeof(char16_t));
//            if (!u16str) {
//              ALOGW("No memory when trying to allocate decode cache for string #%d\n",
//                  (int)idx);
//              return NULL;
//            }
//
//            if (kDebugStringPoolNoisy) {
//              ALOGI("Caching UTF8 string: %s", u8str);
//            }
//
//            mCache[idx] = u16str;
//            return u16str;
//          } else {
//            ALOGW("Bad string block: string #%lld extends to %lld, past end at %lld\n",
//                (long long)idx, (long long)(u8str+u8len-strings),
//                (long long)mStringPoolSize);
//          }
        }
      } else {
        ALOGW("Bad string block: string #%d entry is at %d, past end at %d\n",
            (int)idx, (int)(off*2/*sizeof(uint16_t)*/),
            (int)(mStringPoolSize*2/*sizeof(uint16_t)*/));
      }
    }
    return null;
  }

  String stringAt(int idx, Ref outLen) {
    String s = stringAt(idx);
    if (s != null && outLen != null) {
      outLen.set(s.length());
    }
    return s;
  }

  public String string8At(int id, Ref outLen) {
    return stringAt(id, outLen);
  }

  final ResStringPool_span styleAt(final ResStringPool_ref ref) {
    return styleAt(ref.index);
  }

  public final ResStringPool_span styleAt(int idx) {
    if (mError == NO_ERROR && idx < mHeader.styleCount) {
      // const uint32_t off = (mEntryStyles[idx]/sizeof(uint32_t));
      final int off = mEntryStyles.get(idx) / SIZEOF_INT;
      if (off < mStylePoolSize) {
        // return (const ResStringPool_span*)(mStyles+off);
        return new ResStringPool_span(
            mHeader.myBuf(), mHeader.myOffset() + mStyles + off * SIZEOF_INT);
      } else {
        ALOGW("Bad string block: style #%d entry is at %d, past end at %d\n",
            (int)idx, (int)(off*SIZEOF_INT),
            (int)(mStylePoolSize*SIZEOF_INT));
      }
    }
    return null;
  }

  public int indexOfString(String str) {
    if (mError != NO_ERROR) {
      return mError;
    }

    if (kDebugStringPoolNoisy) {
      ALOGI("indexOfString : %s", str);
    }

    if ( (mHeader.flags&ResStringPoolHeader.SORTED_FLAG) != 0) {
      // Do a binary search for the string...  this is a little tricky,
      // because the strings are sorted with strzcmp16().  So to match
      // the ordering, we need to convert strings in the pool to UTF-16.
      // But we don't want to hit the cache, so instead we will have a
      // local temporary allocation for the conversions.
      int l = 0;
      int h = mHeader.stringCount-1;

      int mid;
      while (l <= h) {
        mid = l + (h - l)/2;
        String s = stringAt(mid);
        int c = s != null ? s.compareTo(str) : -1;
        if (kDebugStringPoolNoisy) {
          ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
              s, c, (int)l, (int)mid, (int)h);
        }
        if (c == 0) {
          if (kDebugStringPoolNoisy) {
            ALOGI("MATCH!");
          }
          return mid;
        } else if (c < 0) {
          l = mid + 1;
        } else {
          h = mid - 1;
        }
      }
    } else {
      // It is unusual to get the ID from an unsorted string block...
      // most often this happens because we want to get IDs for style
      // span tags; since those always appear at the end of the string
      // block, start searching at the back.
      for (int i = mHeader.stringCount; i>=0; i--) {
        String s = stringAt(i);
        if (kDebugStringPoolNoisy) {
          ALOGI("Looking at %s, i=%d\n", s, i);
        }
        if (Objects.equals(s, str)) {
          if (kDebugStringPoolNoisy) {
            ALOGI("MATCH!");
          }
          return i;
        }
      }
    }

    return NAME_NOT_FOUND;
  }
//
    public int size() {
      return mError == NO_ERROR ? mHeader.stringCount : 0;
    }

    int styleCount() {
      return mError == NO_ERROR ? mHeader.styleCount : 0;
    }

    int bytes() {
      return mError == NO_ERROR ? mHeader.header.size : 0;
    }

  public boolean isUTF8() {
    return true;
  }

  public int getError() {
    return mError;
  }

//    int styleCount() final;
//    int bytes() final;
//
//    boolean isSorted() final;
//    boolean isUTF8() final;
//

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy