diff --git a/README.md b/README.md
index ef8522ac..3f9234bc 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# PojavLauncher_iOS
-Minecraft: Java Edition launcher for iOS
+Minecraft: Java Edition launcher for iOS, based on [PojavLauncher Android](https://github.com/PojavLauncherTeam/PojavLauncher).
## Navigation
- [Introduction](#introduction)
@@ -41,3 +41,6 @@ dpkg -i /path/to/openjdk-16-jre.deb
## Credits & Third party components and their licenses
- [OpenJDK 16](https://www.ios-repo-updates.com/repository/procursus/package/openjdk-16-jre): GNU GPLv2 license.
+- GL4ES
+- Boardwalk JVM Launcher.
+
diff --git a/src/main/java/android/util/ArrayMap.java b/src/main/java/android/util/ArrayMap.java
new file mode 100644
index 00000000..a28c3432
--- /dev/null
+++ b/src/main/java/android/util/ArrayMap.java
@@ -0,0 +1,884 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 android.util;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * ArrayMap is a generic key->value mapping data structure that is
+ * designed to be more memory efficient than a traditional {@link java.util.HashMap}.
+ * It keeps its mappings in an array data structure -- an integer array of hash
+ * codes for each item, and an Object array of the key/value pairs. This allows it to
+ * avoid having to create an extra object for every entry put in to the map, and it
+ * also tries to control the growth of the size of these arrays more aggressively
+ * (since growing them only requires copying the entries in the array, not rebuilding
+ * a hash map).
+ *
+ *
Note that this implementation is not intended to be appropriate for data structures
+ * that may contain large numbers of items. It is generally slower than a traditional
+ * HashMap, since lookups require a binary search and adds and removes require inserting
+ * and deleting entries in the array. For containers holding up to hundreds of items,
+ * the performance difference is not significant, less than 50%.
+ *
+ * Because this container is intended to better balance memory use, unlike most other
+ * standard Java containers it will shrink its array as items are removed from it. Currently
+ * you have no control over this shrinking -- if you set a capacity and then remove an
+ * item, it may reduce the capacity to better match the current size. In the future an
+ * explicit call to set the capacity should turn off this aggressive shrinking behavior.
+ */
+public final class ArrayMap implements Map {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "ArrayMap";
+
+ /**
+ * The minimum amount by which the capacity of a ArrayMap will increase.
+ * This is tuned to be relatively space-efficient.
+ */
+ private static final int BASE_SIZE = 4;
+
+ /**
+ * Maximum number of entries to have in array caches.
+ */
+ private static final int CACHE_SIZE = 10;
+
+ /**
+ * Special hash array value that indicates the container is immutable.
+ */
+ static final int[] EMPTY_IMMUTABLE_INTS = new int[0];
+
+ /**
+ * @hide Special immutable empty ArrayMap.
+ */
+ public static final ArrayMap EMPTY = new ArrayMap(true);
+
+ /**
+ * Caches of small array objects to avoid spamming garbage. The cache
+ * Object[] variable is a pointer to a linked list of array objects.
+ * The first entry in the array is a pointer to the next array in the
+ * list; the second entry is a pointer to the int[] hash code array for it.
+ */
+ static Object[] mBaseCache;
+ static int mBaseCacheSize;
+ static Object[] mTwiceBaseCache;
+ static int mTwiceBaseCacheSize;
+
+ int[] mHashes;
+ Object[] mArray;
+ int mSize;
+ MapCollections mCollections;
+
+ int indexOf(Object key, int hash) {
+ final int N = mSize;
+
+ // Important fast case: if nothing is in here, nothing to look for.
+ if (N == 0) {
+ return ~0;
+ }
+
+ int index = ContainerHelpers.binarySearch(mHashes, N, hash);
+
+ // If the hash code wasn't found, then we have no entry for this key.
+ if (index < 0) {
+ return index;
+ }
+
+ // If the key at the returned index matches, that's what we want.
+ if (key.equals(mArray[index<<1])) {
+ return index;
+ }
+
+ // Search for a matching key after the index.
+ int end;
+ for (end = index + 1; end < N && mHashes[end] == hash; end++) {
+ if (key.equals(mArray[end << 1])) return end;
+ }
+
+ // Search for a matching key before the index.
+ for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
+ if (key.equals(mArray[i << 1])) return i;
+ }
+
+ // Key not found -- return negative value indicating where a
+ // new entry for this key should go. We use the end of the
+ // hash chain to reduce the number of array entries that will
+ // need to be copied when inserting.
+ return ~end;
+ }
+
+ int indexOfNull() {
+ final int N = mSize;
+
+ // Important fast case: if nothing is in here, nothing to look for.
+ if (N == 0) {
+ return ~0;
+ }
+
+ int index = ContainerHelpers.binarySearch(mHashes, N, 0);
+
+ // If the hash code wasn't found, then we have no entry for this key.
+ if (index < 0) {
+ return index;
+ }
+
+ // If the key at the returned index matches, that's what we want.
+ if (null == mArray[index<<1]) {
+ return index;
+ }
+
+ // Search for a matching key after the index.
+ int end;
+ for (end = index + 1; end < N && mHashes[end] == 0; end++) {
+ if (null == mArray[end << 1]) return end;
+ }
+
+ // Search for a matching key before the index.
+ for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
+ if (null == mArray[i << 1]) return i;
+ }
+
+ // Key not found -- return negative value indicating where a
+ // new entry for this key should go. We use the end of the
+ // hash chain to reduce the number of array entries that will
+ // need to be copied when inserting.
+ return ~end;
+ }
+
+ private void allocArrays(final int size) {
+ if (mHashes == EMPTY_IMMUTABLE_INTS) {
+ throw new UnsupportedOperationException("ArrayMap is immutable");
+ }
+ if (size == (BASE_SIZE*2)) {
+ synchronized (ArrayMap.class) {
+ if (mTwiceBaseCache != null) {
+ final Object[] array = mTwiceBaseCache;
+ mArray = array;
+ mTwiceBaseCache = (Object[])array[0];
+ mHashes = (int[])array[1];
+ array[0] = array[1] = null;
+ mTwiceBaseCacheSize--;
+ if (DEBUG) System.out.println("Retrieving 2x cache " + mHashes
+ + " now have " + mTwiceBaseCacheSize + " entries");
+ return;
+ }
+ }
+ } else if (size == BASE_SIZE) {
+ synchronized (ArrayMap.class) {
+ if (mBaseCache != null) {
+ final Object[] array = mBaseCache;
+ mArray = array;
+ mBaseCache = (Object[])array[0];
+ mHashes = (int[])array[1];
+ array[0] = array[1] = null;
+ mBaseCacheSize--;
+ if (DEBUG) System.out.println("Retrieving 1x cache " + mHashes
+ + " now have " + mBaseCacheSize + " entries");
+ return;
+ }
+ }
+ }
+
+ mHashes = new int[size];
+ mArray = new Object[size<<1];
+ }
+
+ private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
+ if (hashes.length == (BASE_SIZE*2)) {
+ synchronized (ArrayMap.class) {
+ if (mTwiceBaseCacheSize < CACHE_SIZE) {
+ array[0] = mTwiceBaseCache;
+ array[1] = hashes;
+ for (int i=(size<<1)-1; i>=2; i--) {
+ array[i] = null;
+ }
+ mTwiceBaseCache = array;
+ mTwiceBaseCacheSize++;
+ if (DEBUG) System.out.println("Storing 2x cache " + array
+ + " now have " + mTwiceBaseCacheSize + " entries");
+ }
+ }
+ } else if (hashes.length == BASE_SIZE) {
+ synchronized (ArrayMap.class) {
+ if (mBaseCacheSize < CACHE_SIZE) {
+ array[0] = mBaseCache;
+ array[1] = hashes;
+ for (int i=(size<<1)-1; i>=2; i--) {
+ array[i] = null;
+ }
+ mBaseCache = array;
+ mBaseCacheSize++;
+ if (DEBUG) System.out.println("Storing 1x cache " + array
+ + " now have " + mBaseCacheSize + " entries");
+ }
+ }
+ }
+ }
+
+ /**
+ * Create a new empty ArrayMap. The default capacity of an array map is 0, and
+ * will grow once items are added to it.
+ */
+ public ArrayMap() {
+ mHashes = EmptyArray.INT;
+ mArray = EmptyArray.OBJECT;
+ mSize = 0;
+ }
+
+ /**
+ * Create a new ArrayMap with a given initial capacity.
+ */
+ public ArrayMap(int capacity) {
+ if (capacity == 0) {
+ mHashes = EmptyArray.INT;
+ mArray = EmptyArray.OBJECT;
+ } else {
+ allocArrays(capacity);
+ }
+ mSize = 0;
+ }
+
+ private ArrayMap(boolean immutable) {
+ // If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS
+ // instance instead of the usual EmptyArray.INT. The reference
+ // is checked later to see if the array is allowed to grow.
+ mHashes = immutable ? EMPTY_IMMUTABLE_INTS : EmptyArray.INT;
+ mArray = EmptyArray.OBJECT;
+ mSize = 0;
+ }
+
+ /**
+ * Create a new ArrayMap with the mappings from the given ArrayMap.
+ */
+ public ArrayMap(ArrayMap map) {
+ this();
+ if (map != null) {
+ putAll(map);
+ }
+ }
+
+ /**
+ * Make the array map empty. All storage is released.
+ */
+ @Override
+ public void clear() {
+ if (mSize > 0) {
+ freeArrays(mHashes, mArray, mSize);
+ mHashes = EmptyArray.INT;
+ mArray = EmptyArray.OBJECT;
+ mSize = 0;
+ }
+ }
+
+ /**
+ * @hide
+ * Like {@link #clear}, but doesn't reduce the capacity of the ArrayMap.
+ */
+ public void erase() {
+ if (mSize > 0) {
+ final int N = mSize<<1;
+ final Object[] array = mArray;
+ for (int i=0; iminimumCapacity
+ * items.
+ */
+ public void ensureCapacity(int minimumCapacity) {
+ if (mHashes.length < minimumCapacity) {
+ final int[] ohashes = mHashes;
+ final Object[] oarray = mArray;
+ allocArrays(minimumCapacity);
+ if (mSize > 0) {
+ System.arraycopy(ohashes, 0, mHashes, 0, mSize);
+ System.arraycopy(oarray, 0, mArray, 0, mSize<<1);
+ }
+ freeArrays(ohashes, oarray, mSize);
+ }
+ }
+
+ /**
+ * Check whether a key exists in the array.
+ *
+ * @param key The key to search for.
+ * @return Returns true if the key exists, else false.
+ */
+ @Override
+ public boolean containsKey(Object key) {
+ return indexOfKey(key) >= 0;
+ }
+
+ /**
+ * Returns the index of a key in the set.
+ *
+ * @param key The key to search for.
+ * @return Returns the index of the key if it exists, else a negative integer.
+ */
+ public int indexOfKey(Object key) {
+ return key == null ? indexOfNull() : indexOf(key, key.hashCode());
+ }
+
+ int indexOfValue(Object value) {
+ final int N = mSize*2;
+ final Object[] array = mArray;
+ if (value == null) {
+ for (int i=1; i>1;
+ }
+ }
+ } else {
+ for (int i=1; i>1;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Check whether a value exists in the array. This requires a linear search
+ * through the entire array.
+ *
+ * @param value The value to search for.
+ * @return Returns true if the value exists, else false.
+ */
+ @Override
+ public boolean containsValue(Object value) {
+ return indexOfValue(value) >= 0;
+ }
+
+ /**
+ * Retrieve a value from the array.
+ * @param key The key of the value to retrieve.
+ * @return Returns the value associated with the given key,
+ * or null if there is no such key.
+ */
+ @Override
+ public V get(Object key) {
+ final int index = indexOfKey(key);
+ return index >= 0 ? (V)mArray[(index<<1)+1] : null;
+ }
+
+ /**
+ * Return the key at the given index in the array.
+ * @param index The desired index, must be between 0 and {@link #size()}-1.
+ * @return Returns the key stored at the given index.
+ */
+ public K keyAt(int index) {
+ return (K)mArray[index << 1];
+ }
+
+ /**
+ * Return the value at the given index in the array.
+ * @param index The desired index, must be between 0 and {@link #size()}-1.
+ * @return Returns the value stored at the given index.
+ */
+ public V valueAt(int index) {
+ return (V)mArray[(index << 1) + 1];
+ }
+
+ /**
+ * Set the value at a given index in the array.
+ * @param index The desired index, must be between 0 and {@link #size()}-1.
+ * @param value The new value to store at this index.
+ * @return Returns the previous value at the given index.
+ */
+ public V setValueAt(int index, V value) {
+ index = (index << 1) + 1;
+ V old = (V)mArray[index];
+ mArray[index] = value;
+ return old;
+ }
+
+ /**
+ * Return true if the array map contains no items.
+ */
+ @Override
+ public boolean isEmpty() {
+ return mSize <= 0;
+ }
+
+ /**
+ * Add a new value to the array map.
+ * @param key The key under which to store the value. If
+ * this key already exists in the array, its value will be replaced.
+ * @param value The value to store for the given key.
+ * @return Returns the old value that was stored for the given key, or null if there
+ * was no such key.
+ */
+ @Override
+ public V put(K key, V value) {
+ final int hash;
+ int index;
+ if (key == null) {
+ hash = 0;
+ index = indexOfNull();
+ } else {
+ hash = key.hashCode();
+ index = indexOf(key, hash);
+ }
+ if (index >= 0) {
+ index = (index<<1) + 1;
+ final V old = (V)mArray[index];
+ mArray[index] = value;
+ return old;
+ }
+
+ index = ~index;
+ if (mSize >= mHashes.length) {
+ final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))
+ : (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
+
+ if (DEBUG) System.out.println("put: grow from " + mHashes.length + " to " + n);
+
+ final int[] ohashes = mHashes;
+ final Object[] oarray = mArray;
+ allocArrays(n);
+
+ if (mHashes.length > 0) {
+ if (DEBUG) System.out.println("put: copy 0-" + mSize + " to 0");
+ System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
+ System.arraycopy(oarray, 0, mArray, 0, oarray.length);
+ }
+
+ freeArrays(ohashes, oarray, mSize);
+ }
+
+ if (index < mSize) {
+ if (DEBUG) System.out.println("put: move " + index + "-" + (mSize-index)
+ + " to " + (index+1));
+ System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
+ System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
+ }
+
+ mHashes[index] = hash;
+ mArray[index<<1] = key;
+ mArray[(index<<1)+1] = value;
+ mSize++;
+ return null;
+ }
+
+ /**
+ * Special fast path for appending items to the end of the array without validation.
+ * The array must already be large enough to contain the item.
+ * @hide
+ */
+ public void append(K key, V value) {
+ int index = mSize;
+ final int hash = key == null ? 0 : key.hashCode();
+ if (index >= mHashes.length) {
+ throw new IllegalStateException("Array is full");
+ }
+ if (index > 0 && mHashes[index-1] > hash) {
+ RuntimeException e = new RuntimeException("here");
+ e.fillInStackTrace();
+ System.out.println("New hash " + hash
+ + " is before end of array hash " + mHashes[index-1]
+ + " at index " + index + " key " + key);
+ e.printStackTrace();
+ put(key, value);
+ return;
+ }
+ mSize = index+1;
+ mHashes[index] = hash;
+ index <<= 1;
+ mArray[index] = key;
+ mArray[index+1] = value;
+ }
+
+ /**
+ * The use of the {@link #append} function can result in invalid array maps, in particular
+ * an array map where the same key appears multiple times. This function verifies that
+ * the array map is valid, throwing IllegalArgumentException if a problem is found. The
+ * main use for this method is validating an array map after unpacking from an IPC, to
+ * protect against malicious callers.
+ * @hide
+ */
+ public void validate() {
+ final int N = mSize;
+ if (N <= 1) {
+ // There can't be dups.
+ return;
+ }
+ int basehash = mHashes[0];
+ int basei = 0;
+ for (int i=1; i=basei; j--) {
+ final Object prev = mArray[j<<1];
+ if (cur == prev) {
+ throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
+ }
+ if (cur != null && prev != null && cur.equals(prev)) {
+ throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
+ }
+ }
+ }
+ }
+
+ /**
+ * Perform a {@link #put(Object, Object)} of all key/value pairs in array
+ * @param array The array whose contents are to be retrieved.
+ */
+ public void putAll(ArrayMap extends K, ? extends V> array) {
+ final int N = array.mSize;
+ ensureCapacity(mSize + N);
+ if (mSize == 0) {
+ if (N > 0) {
+ System.arraycopy(array.mHashes, 0, mHashes, 0, N);
+ System.arraycopy(array.mArray, 0, mArray, 0, N<<1);
+ mSize = N;
+ }
+ } else {
+ for (int i=0; i= 0) {
+ return removeAt(index);
+ }
+
+ return null;
+ }
+
+ /**
+ * Remove the key/value mapping at the given index.
+ * @param index The desired index, must be between 0 and {@link #size()}-1.
+ * @return Returns the value that was stored at this index.
+ */
+ public V removeAt(int index) {
+ final Object old = mArray[(index << 1) + 1];
+ if (mSize <= 1) {
+ // Now empty.
+ if (DEBUG) System.out.println("remove: shrink from " + mHashes.length + " to 0");
+ freeArrays(mHashes, mArray, mSize);
+ mHashes = EmptyArray.INT;
+ mArray = EmptyArray.OBJECT;
+ mSize = 0;
+ } else {
+ if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
+ // Shrunk enough to reduce size of arrays. We don't allow it to
+ // shrink smaller than (BASE_SIZE*2) to avoid flapping between
+ // that and BASE_SIZE.
+ final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2);
+
+ if (DEBUG) System.out.println("remove: shrink from " + mHashes.length + " to " + n);
+
+ final int[] ohashes = mHashes;
+ final Object[] oarray = mArray;
+ allocArrays(n);
+
+ mSize--;
+ if (index > 0) {
+ if (DEBUG) System.out.println("remove: copy from 0-" + index + " to 0");
+ System.arraycopy(ohashes, 0, mHashes, 0, index);
+ System.arraycopy(oarray, 0, mArray, 0, index << 1);
+ }
+ if (index < mSize) {
+ if (DEBUG) System.out.println("remove: copy from " + (index+1) + "-" + mSize
+ + " to " + index);
+ System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
+ System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
+ (mSize - index) << 1);
+ }
+ } else {
+ mSize--;
+ if (index < mSize) {
+ if (DEBUG) System.out.println("remove: move " + (index+1) + "-" + mSize
+ + " to " + index);
+ System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
+ System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
+ (mSize - index) << 1);
+ }
+ mArray[mSize << 1] = null;
+ mArray[(mSize << 1) + 1] = null;
+ }
+ }
+ return (V)old;
+ }
+
+ /**
+ * Return the number of items in this array map.
+ */
+ @Override
+ public int size() {
+ return mSize;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * This implementation returns false if the object is not a map, or
+ * if the maps have different sizes. Otherwise, for each key in this map,
+ * values of both maps are compared. If the values for any key are not
+ * equal, the method returns false, otherwise it returns true.
+ */
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (object instanceof Map) {
+ Map, ?> map = (Map, ?>) object;
+ if (size() != map.size()) {
+ return false;
+ }
+
+ try {
+ for (int i=0; iThis implementation composes a string by iterating over its mappings. If
+ * this map contains itself as a key or a value, the string "(this Map)"
+ * will appear in its place.
+ */
+ @Override
+ public String toString() {
+ if (isEmpty()) {
+ return "{}";
+ }
+
+ StringBuilder buffer = new StringBuilder(mSize * 28);
+ buffer.append('{');
+ for (int i=0; i 0) {
+ buffer.append(", ");
+ }
+ Object key = keyAt(i);
+ if (key != this) {
+ buffer.append(key);
+ } else {
+ buffer.append("(this Map)");
+ }
+ buffer.append('=');
+ Object value = valueAt(i);
+ if (value != this) {
+ buffer.append(value);
+ } else {
+ buffer.append("(this Map)");
+ }
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
+
+ // ------------------------------------------------------------------------
+ // Interop with traditional Java containers. Not as efficient as using
+ // specialized collection APIs.
+ // ------------------------------------------------------------------------
+
+ private MapCollections getCollection() {
+ if (mCollections == null) {
+ mCollections = new MapCollections() {
+ @Override
+ protected int colGetSize() {
+ return mSize;
+ }
+
+ @Override
+ protected Object colGetEntry(int index, int offset) {
+ return mArray[(index<<1) + offset];
+ }
+
+ @Override
+ protected int colIndexOfKey(Object key) {
+ return indexOfKey(key);
+ }
+
+ @Override
+ protected int colIndexOfValue(Object value) {
+ return indexOfValue(value);
+ }
+
+ @Override
+ protected Map colGetMap() {
+ return ArrayMap.this;
+ }
+
+ @Override
+ protected void colPut(K key, V value) {
+ put(key, value);
+ }
+
+ @Override
+ protected V colSetValue(int index, V value) {
+ return setValueAt(index, value);
+ }
+
+ @Override
+ protected void colRemoveAt(int index) {
+ removeAt(index);
+ }
+
+ @Override
+ protected void colClear() {
+ clear();
+ }
+ };
+ }
+ return mCollections;
+ }
+
+ /**
+ * Determine if the array map contains all of the keys in the given collection.
+ * @param collection The collection whose contents are to be checked against.
+ * @return Returns true if this array map contains a key for every entry
+ * in collection, else returns false.
+ */
+ public boolean containsAll(Collection> collection) {
+ return MapCollections.containsAllHelper(this, collection);
+ }
+
+ /**
+ * Perform a {@link #put(Object, Object)} of all key/value pairs in map
+ * @param map The map whose contents are to be retrieved.
+ */
+ @Override
+ public void putAll(Map extends K, ? extends V> map) {
+ ensureCapacity(mSize + map.size());
+ for (Map.Entry extends K, ? extends V> entry : map.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
+ * Remove all keys in the array map that exist in the given collection.
+ * @param collection The collection whose contents are to be used to remove keys.
+ * @return Returns true if any keys were removed from the array map, else false.
+ */
+ public boolean removeAll(Collection> collection) {
+ return MapCollections.removeAllHelper(this, collection);
+ }
+
+ /**
+ * Remove all keys in the array map that do not exist in the given collection.
+ * @param collection The collection whose contents are to be used to determine which
+ * keys to keep.
+ * @return Returns true if any keys were removed from the array map, else false.
+ */
+ public boolean retainAll(Collection> collection) {
+ return MapCollections.retainAllHelper(this, collection);
+ }
+
+ /**
+ * Return a {@link java.util.Set} for iterating over and interacting with all mappings
+ * in the array map.
+ *
+ * Note: this is a very inefficient way to access the array contents, it
+ * requires generating a number of temporary objects and allocates additional state
+ * information associated with the container that will remain for the life of the container.
+ *
+ * Note:
the semantics of this
+ * Set are subtly different than that of a {@link java.util.HashMap}: most important,
+ * the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single
+ * object that exists for the entire iterator, so you can not hold on to it
+ * after calling {@link java.util.Iterator#next() Iterator.next}.
+ */
+ @Override
+ public Set> entrySet() {
+ return getCollection().getEntrySet();
+ }
+
+ /**
+ * Return a {@link java.util.Set} for iterating over and interacting with all keys
+ * in the array map.
+ *
+ * Note: this is a fairly inefficient way to access the array contents, it
+ * requires generating a number of temporary objects and allocates additional state
+ * information associated with the container that will remain for the life of the container.
+ */
+ @Override
+ public Set keySet() {
+ return getCollection().getKeySet();
+ }
+
+ /**
+ * Return a {@link java.util.Collection} for iterating over and interacting with all values
+ * in the array map.
+ *
+ * Note: this is a fairly inefficient way to access the array contents, it
+ * requires generating a number of temporary objects and allocates additional state
+ * information associated with the container that will remain for the life of the container.
+ */
+ @Override
+ public Collection values() {
+ return getCollection().getValues();
+ }
+}
diff --git a/src/main/java/android/util/ContainerHelpers.java b/src/main/java/android/util/ContainerHelpers.java
new file mode 100644
index 00000000..4e5fefb9
--- /dev/null
+++ b/src/main/java/android/util/ContainerHelpers.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 android.util;
+
+class ContainerHelpers {
+
+ // This is Arrays.binarySearch(), but doesn't do any argument validation.
+ static int binarySearch(int[] array, int size, int value) {
+ int lo = 0;
+ int hi = size - 1;
+
+ while (lo <= hi) {
+ final int mid = (lo + hi) >>> 1;
+ final int midVal = array[mid];
+
+ if (midVal < value) {
+ lo = mid + 1;
+ } else if (midVal > value) {
+ hi = mid - 1;
+ } else {
+ return mid; // value found
+ }
+ }
+ return ~lo; // value not present
+ }
+
+ static int binarySearch(long[] array, int size, long value) {
+ int lo = 0;
+ int hi = size - 1;
+
+ while (lo <= hi) {
+ final int mid = (lo + hi) >>> 1;
+ final long midVal = array[mid];
+
+ if (midVal < value) {
+ lo = mid + 1;
+ } else if (midVal > value) {
+ hi = mid - 1;
+ } else {
+ return mid; // value found
+ }
+ }
+ return ~lo; // value not present
+ }
+}
diff --git a/src/main/java/android/util/EmptyArray.java b/src/main/java/android/util/EmptyArray.java
new file mode 100644
index 00000000..a5735fbc
--- /dev/null
+++ b/src/main/java/android/util/EmptyArray.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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 android.util;
+
+public final class EmptyArray {
+ private EmptyArray() {}
+ public static final boolean[] BOOLEAN = new boolean[0];
+ public static final byte[] BYTE = new byte[0];
+ public static final char[] CHAR = new char[0];
+ public static final double[] DOUBLE = new double[0];
+ public static final int[] INT = new int[0];
+ public static final Class>[] CLASS = new Class[0];
+ public static final Object[] OBJECT = new Object[0];
+ public static final String[] STRING = new String[0];
+ public static final Throwable[] THROWABLE = new Throwable[0];
+ public static final StackTraceElement[] STACK_TRACE_ELEMENT = new StackTraceElement[0];
+}
diff --git a/src/main/java/android/util/MapCollections.java b/src/main/java/android/util/MapCollections.java
new file mode 100644
index 00000000..acfc1ada
--- /dev/null
+++ b/src/main/java/android/util/MapCollections.java
@@ -0,0 +1,557 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 android.util;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Helper for writing standard Java collection interfaces to a data
+ * structure like {@link ArrayMap}.
+ * @hide
+ */
+abstract class MapCollections {
+ EntrySet mEntrySet;
+ KeySet mKeySet;
+ ValuesCollection mValues;
+
+ final class ArrayIterator implements Iterator {
+ final int mOffset;
+ int mSize;
+ int mIndex;
+ boolean mCanRemove = false;
+
+ ArrayIterator(int offset) {
+ mOffset = offset;
+ mSize = colGetSize();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return mIndex < mSize;
+ }
+
+ @Override
+ public T next() {
+ Object res = colGetEntry(mIndex, mOffset);
+ mIndex++;
+ mCanRemove = true;
+ return (T)res;
+ }
+
+ @Override
+ public void remove() {
+ if (!mCanRemove) {
+ throw new IllegalStateException();
+ }
+ mIndex--;
+ mSize--;
+ mCanRemove = false;
+ colRemoveAt(mIndex);
+ }
+ }
+
+ final class MapIterator implements Iterator>, Map.Entry {
+ int mEnd;
+ int mIndex;
+ boolean mEntryValid = false;
+
+ MapIterator() {
+ mEnd = colGetSize() - 1;
+ mIndex = -1;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return mIndex < mEnd;
+ }
+
+ @Override
+ public Map.Entry next() {
+ mIndex++;
+ mEntryValid = true;
+ return this;
+ }
+
+ @Override
+ public void remove() {
+ if (!mEntryValid) {
+ throw new IllegalStateException();
+ }
+ colRemoveAt(mIndex);
+ mIndex--;
+ mEnd--;
+ mEntryValid = false;
+ }
+
+ @Override
+ public K getKey() {
+ if (!mEntryValid) {
+ throw new IllegalStateException(
+ "This container does not support retaining Map.Entry objects");
+ }
+ return (K)colGetEntry(mIndex, 0);
+ }
+
+ @Override
+ public V getValue() {
+ if (!mEntryValid) {
+ throw new IllegalStateException(
+ "This container does not support retaining Map.Entry objects");
+ }
+ return (V)colGetEntry(mIndex, 1);
+ }
+
+ @Override
+ public V setValue(V object) {
+ if (!mEntryValid) {
+ throw new IllegalStateException(
+ "This container does not support retaining Map.Entry objects");
+ }
+ return colSetValue(mIndex, object);
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ if (!mEntryValid) {
+ throw new IllegalStateException(
+ "This container does not support retaining Map.Entry objects");
+ }
+ if (!(o instanceof Map.Entry)) {
+ return false;
+ }
+ Map.Entry, ?> e = (Map.Entry, ?>) o;
+ return Objects.equal(e.getKey(), colGetEntry(mIndex, 0))
+ && Objects.equal(e.getValue(), colGetEntry(mIndex, 1));
+ }
+
+ @Override
+ public final int hashCode() {
+ if (!mEntryValid) {
+ throw new IllegalStateException(
+ "This container does not support retaining Map.Entry objects");
+ }
+ final Object key = colGetEntry(mIndex, 0);
+ final Object value = colGetEntry(mIndex, 1);
+ return (key == null ? 0 : key.hashCode()) ^
+ (value == null ? 0 : value.hashCode());
+ }
+
+ @Override
+ public final String toString() {
+ return getKey() + "=" + getValue();
+ }
+ }
+
+ final class EntrySet implements Set> {
+ @Override
+ public boolean add(Map.Entry object) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean addAll(Collection extends Map.Entry> collection) {
+ int oldSize = colGetSize();
+ for (Map.Entry entry : collection) {
+ colPut(entry.getKey(), entry.getValue());
+ }
+ return oldSize != colGetSize();
+ }
+
+ @Override
+ public void clear() {
+ colClear();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ if (!(o instanceof Map.Entry))
+ return false;
+ Map.Entry, ?> e = (Map.Entry, ?>) o;
+ int index = colIndexOfKey(e.getKey());
+ if (index < 0) {
+ return false;
+ }
+ Object foundVal = colGetEntry(index, 1);
+ return Objects.equal(foundVal, e.getValue());
+ }
+
+ @Override
+ public boolean containsAll(Collection> collection) {
+ Iterator> it = collection.iterator();
+ while (it.hasNext()) {
+ if (!contains(it.next())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return colGetSize() == 0;
+ }
+
+ @Override
+ public Iterator> iterator() {
+ return new MapIterator();
+ }
+
+ @Override
+ public boolean remove(Object object) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean removeAll(Collection> collection) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean retainAll(Collection> collection) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int size() {
+ return colGetSize();
+ }
+
+ @Override
+ public Object[] toArray() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public T[] toArray(T[] array) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ return equalsSetHelper(this, object);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ for (int i=colGetSize()-1; i>=0; i--) {
+ final Object key = colGetEntry(i, 0);
+ final Object value = colGetEntry(i, 1);
+ result += ( (key == null ? 0 : key.hashCode()) ^
+ (value == null ? 0 : value.hashCode()) );
+ }
+ return result;
+ }
+ };
+
+ final class KeySet implements Set {
+
+ @Override
+ public boolean add(K object) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean addAll(Collection extends K> collection) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ colClear();
+ }
+
+ @Override
+ public boolean contains(Object object) {
+ return colIndexOfKey(object) >= 0;
+ }
+
+ @Override
+ public boolean containsAll(Collection> collection) {
+ return containsAllHelper(colGetMap(), collection);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return colGetSize() == 0;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new ArrayIterator(0);
+ }
+
+ @Override
+ public boolean remove(Object object) {
+ int index = colIndexOfKey(object);
+ if (index >= 0) {
+ colRemoveAt(index);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(Collection> collection) {
+ return removeAllHelper(colGetMap(), collection);
+ }
+
+ @Override
+ public boolean retainAll(Collection> collection) {
+ return retainAllHelper(colGetMap(), collection);
+ }
+
+ @Override
+ public int size() {
+ return colGetSize();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return toArrayHelper(0);
+ }
+
+ @Override
+ public T[] toArray(T[] array) {
+ return toArrayHelper(array, 0);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ return equalsSetHelper(this, object);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ for (int i=colGetSize()-1; i>=0; i--) {
+ Object obj = colGetEntry(i, 0);
+ result += obj == null ? 0 : obj.hashCode();
+ }
+ return result;
+ }
+ };
+
+ final class ValuesCollection implements Collection {
+
+ @Override
+ public boolean add(V object) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean addAll(Collection extends V> collection) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ colClear();
+ }
+
+ @Override
+ public boolean contains(Object object) {
+ return colIndexOfValue(object) >= 0;
+ }
+
+ @Override
+ public boolean containsAll(Collection> collection) {
+ Iterator> it = collection.iterator();
+ while (it.hasNext()) {
+ if (!contains(it.next())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return colGetSize() == 0;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new ArrayIterator(1);
+ }
+
+ @Override
+ public boolean remove(Object object) {
+ int index = colIndexOfValue(object);
+ if (index >= 0) {
+ colRemoveAt(index);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(Collection> collection) {
+ int N = colGetSize();
+ boolean changed = false;
+ for (int i=0; i collection) {
+ int N = colGetSize();
+ boolean changed = false;
+ for (int i=0; i T[] toArray(T[] array) {
+ return toArrayHelper(array, 1);
+ }
+ };
+
+ public static boolean containsAllHelper(Map map, Collection> collection) {
+ Iterator> it = collection.iterator();
+ while (it.hasNext()) {
+ if (!map.containsKey(it.next())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean removeAllHelper(Map map, Collection> collection) {
+ int oldSize = map.size();
+ Iterator> it = collection.iterator();
+ while (it.hasNext()) {
+ map.remove(it.next());
+ }
+ return oldSize != map.size();
+ }
+
+ public static boolean retainAllHelper(Map map, Collection> collection) {
+ int oldSize = map.size();
+ Iterator it = map.keySet().iterator();
+ while (it.hasNext()) {
+ if (!collection.contains(it.next())) {
+ it.remove();
+ }
+ }
+ return oldSize != map.size();
+ }
+
+ public Object[] toArrayHelper(int offset) {
+ final int N = colGetSize();
+ Object[] result = new Object[N];
+ for (int i=0; i T[] toArrayHelper(T[] array, int offset) {
+ final int N = colGetSize();
+ if (array.length < N) {
+ @SuppressWarnings("unchecked") T[] newArray
+ = (T[]) Array.newInstance(array.getClass().getComponentType(), N);
+ array = newArray;
+ }
+ for (int i=0; i N) {
+ array[N] = null;
+ }
+ return array;
+ }
+
+ public static boolean equalsSetHelper(Set set, Object object) {
+ if (set == object) {
+ return true;
+ }
+ if (object instanceof Set) {
+ Set> s = (Set>) object;
+
+ try {
+ return set.size() == s.size() && set.containsAll(s);
+ } catch (NullPointerException ignored) {
+ return false;
+ } catch (ClassCastException ignored) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ public Set> getEntrySet() {
+ if (mEntrySet == null) {
+ mEntrySet = new EntrySet();
+ }
+ return mEntrySet;
+ }
+
+ public Set getKeySet() {
+ if (mKeySet == null) {
+ mKeySet = new KeySet();
+ }
+ return mKeySet;
+ }
+
+ public Collection getValues() {
+ if (mValues == null) {
+ mValues = new ValuesCollection();
+ }
+ return mValues;
+ }
+
+ protected abstract int colGetSize();
+ protected abstract Object colGetEntry(int index, int offset);
+ protected abstract int colIndexOfKey(Object key);
+ protected abstract int colIndexOfValue(Object key);
+ protected abstract Map colGetMap();
+ protected abstract void colPut(K key, V value);
+ protected abstract V colSetValue(int index, V value);
+ protected abstract void colRemoveAt(int index);
+ protected abstract void colClear();
+}
diff --git a/src/main/java/android/util/Objects.java b/src/main/java/android/util/Objects.java
new file mode 100644
index 00000000..64693903
--- /dev/null
+++ b/src/main/java/android/util/Objects.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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 android.util;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+public final class Objects {
+ private Objects() {}
+ /**
+ * Returns true if two possibly-null objects are equal.
+ */
+ public static boolean equal(Object a, Object b) {
+ return a == b || (a != null && a.equals(b));
+ }
+ public static int hashCode(Object o) {
+ return (o == null) ? 0 : o.hashCode();
+ }
+ /**
+ * Returns a string reporting the value of each declared field, via reflection.
+ * Static and transient fields are automatically skipped. Produces output like
+ * "SimpleClassName[integer=1234,string="hello",character='c',intArray=[1,2,3]]".
+ */
+ public static String toString(Object o) {
+ Class> c = o.getClass();
+ StringBuilder sb = new StringBuilder();
+ sb.append(c.getSimpleName()).append('[');
+ int i = 0;
+ for (Field f : c.getDeclaredFields()) {
+ if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) {
+ continue;
+ }
+ f.setAccessible(true);
+ try {
+ Object value = f.get(o);
+ if (i++ > 0) {
+ sb.append(',');
+ }
+ sb.append(f.getName());
+ sb.append('=');
+ if (value.getClass().isArray()) {
+ if (value.getClass() == boolean[].class) {
+ sb.append(Arrays.toString((boolean[]) value));
+ } else if (value.getClass() == byte[].class) {
+ sb.append(Arrays.toString((byte[]) value));
+ } else if (value.getClass() == char[].class) {
+ sb.append(Arrays.toString((char[]) value));
+ } else if (value.getClass() == double[].class) {
+ sb.append(Arrays.toString((double[]) value));
+ } else if (value.getClass() == float[].class) {
+ sb.append(Arrays.toString((float[]) value));
+ } else if (value.getClass() == int[].class) {
+ sb.append(Arrays.toString((int[]) value));
+ } else if (value.getClass() == long[].class) {
+ sb.append(Arrays.toString((long[]) value));
+ } else if (value.getClass() == short[].class) {
+ sb.append(Arrays.toString((short[]) value));
+ } else {
+ sb.append(Arrays.toString((Object[]) value));
+ }
+ } else if (value.getClass() == Character.class) {
+ sb.append('\'').append(value).append('\'');
+ } else if (value.getClass() == String.class) {
+ sb.append('"').append(value).append('"');
+ } else {
+ sb.append(value);
+ }
+ } catch (IllegalAccessException unexpected) {
+ throw new AssertionError(unexpected);
+ }
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/net/kdt/pojavlaunch/PLaunchApp.java b/src/main/java/net/kdt/pojavlaunch/PLaunchApp.java
index 21ac45c5..7e593f82 100644
--- a/src/main/java/net/kdt/pojavlaunch/PLaunchApp.java
+++ b/src/main/java/net/kdt/pojavlaunch/PLaunchApp.java
@@ -8,9 +8,7 @@ import org.lwjgl.glfw.CallbackBridge;
import org.robovm.apple.foundation.Foundation;
import org.robovm.apple.foundation.NSAutoreleasePool;
-import org.robovm.apple.uikit.UIApplication;
-import org.robovm.apple.uikit.UIApplicationDelegateAdapter;
-import org.robovm.apple.uikit.UIApplicationLaunchOptions;
+import org.robovm.apple.uikit.*;
import org.robovm.pods.dialog.*;
diff --git a/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java b/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java
index 1508f10c..a3db8865 100644
--- a/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java
+++ b/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java
@@ -1,6 +1,6 @@
package net.kdt.pojavlaunch.prefs;
-import android.content.*;
+import java.io.*;
import java.util.*;
import net.kdt.pojavlaunch.*;
diff --git a/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java b/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java
index e748cffb..b3833db6 100644
--- a/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java
+++ b/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java
@@ -3,6 +3,9 @@ package net.kdt.pojavlaunch.utils;
import java.io.*;
import java.net.*;
import java.nio.charset.*;
+
+import net.kdt.pojavlaunch;
+
import org.apache.commons.io.*;
public class DownloadUtils {
diff --git a/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java b/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java
index 12261df7..0985df4f 100644
--- a/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java
+++ b/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java
@@ -172,9 +172,7 @@ public class JREUtils
Libcore.os.setenv(env.getKey(), env.getValue(), true);
}
- if (shell == null) {
- setLdLibraryPath(LD_LIBRARY_PATH);
- }
+ setLdLibraryPath(LD_LIBRARY_PATH);
// return ldLibraryPath;
}
@@ -197,7 +195,7 @@ public class JREUtils
}
ctx.appendlnToLog("Executing JVM: \"" + sbJavaArgs.toString() + "\"");
*/
- setJavaEnvironment(null);
+ setJavaEnvironment();
initJavaRuntime();
chdir(Tools.DIR_GAME_NEW);