v1.2.0: reverted base save system back to org.json, plus improvements

This commit is contained in:
Evan Debenham 2022-03-03 16:39:20 -05:00
parent c7976b9972
commit ddb9210803
2 changed files with 201 additions and 133 deletions

View File

@ -9,4 +9,7 @@ dependencies {
api "com.badlogicgames.gdx:gdx:$gdxVersion" api "com.badlogicgames.gdx:gdx:$gdxVersion"
api "com.badlogicgames.gdx-controllers:gdx-controllers-core:$gdxControllersVersion" api "com.badlogicgames.gdx-controllers:gdx-controllers-core:$gdxControllersVersion"
implementation "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" implementation "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
//noinspection GradleDependency , later JSON versions cause crashes on old versions of android
implementation "org.json:json:20170516"
} }

View File

@ -26,6 +26,11 @@ import com.badlogic.gdx.utils.JsonValue;
import com.badlogic.gdx.utils.JsonWriter; import com.badlogic.gdx.utils.JsonWriter;
import com.watabou.noosa.Game; import com.watabou.noosa.Game;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
@ -37,6 +42,7 @@ import java.io.OutputStreamWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
@ -45,83 +51,99 @@ public class Bundle {
private static final String CLASS_NAME = "__className"; private static final String CLASS_NAME = "__className";
public static final String DEFAULT_KEY = "key"; public static final String DEFAULT_KEY = "key";
private static HashMap<String,String> aliases = new HashMap<>(); private static HashMap<String,String> aliases = new HashMap<>();
private JsonValue data; /*
WARNING: NOT ALL METHODS IN ORG.JSON ARE PRESENT ON ANDROID/IOS!
Many methods which work on desktop will cause the game to crash on Android and iOS
This is because the Android runtime includes its own version of org.json which does not
implement all methods. MobiVM uses the Android runtime and so this applies to iOS as well.
org.json is very fast (~2x faster than libgdx JSON), which is why the game uses it despite
this dependency conflict.
See https://developer.android.com/reference/org/json/package-summary for details on
what methods exist in all versions of org.json. This class is also commented in places
Where Android/iOS force the use of unusual methods.
*/
private JSONObject data;
public Bundle() { public Bundle() {
this( new JsonValue(JsonValue.ValueType.object) ); this( new JSONObject() );
} }
public String toString() { public String toString() {
return data.toString(); return data.toString();
} }
private Bundle( JsonValue data ) { private Bundle( JSONObject data ) {
this.data = data; this.data = data;
} }
public boolean isNull() { public boolean isNull() {
return data == null; return data == null;
} }
public boolean contains( String key ) { public boolean contains( String key ) {
return data.has(key) && !data.get(key).isNull(); return !data.isNull( key );
} }
//JSONObject.keyset() doesn't exist on Android/iOS
public ArrayList<String> getKeys(){ public ArrayList<String> getKeys(){
ArrayList<String> keys = new ArrayList<>(); Iterator<String> keys = data.keys();
for (JsonValue child : data){ ArrayList<String> result = new ArrayList<>();
keys.add(child.name()); while (keys.hasNext()){
result.add(keys.next());
} }
return keys; return result;
} }
public boolean getBoolean( String key ) { public boolean getBoolean( String key ) {
return data.getBoolean( key, false ); return data.optBoolean( key );
} }
public int getInt( String key ) { public int getInt( String key ) {
return data.getInt( key, 0 ); return data.optInt( key );
} }
public long getLong( String key ) { public long getLong( String key ) {
return data.getLong( key, 0 ); return data.optLong( key );
} }
public float getFloat( String key ) { public float getFloat( String key ) {
return data.getFloat( key, 0f ); return (float)data.optDouble( key, 0.0 );
} }
public String getString( String key ) { public String getString( String key ) {
return data.getString( key, "" ); return data.optString( key );
} }
public Class getClass( String key ) { public Class getClass( String key ) {
String clName = getString(key).replace("class ", ""); String clName = getString(key).replace("class ", "");
if (!clName.equals("")){ if (!clName.equals("")){
if (aliases.containsKey( clName )) { if (aliases.containsKey( clName )) {
clName = aliases.get( clName ); clName = aliases.get( clName );
} }
return Reflection.forName( clName ); return Reflection.forName( clName );
} }
return null; return null;
} }
public Bundle getBundle( String key ) { public Bundle getBundle( String key ) {
return new Bundle( data.get(key) ); return new Bundle( data.optJSONObject( key ) );
} }
private Bundlable get() { private Bundlable get() {
if (data == null) return null; if (data == null) return null;
String clName = getString( CLASS_NAME ); String clName = getString( CLASS_NAME );
if (aliases.containsKey( clName )) { if (aliases.containsKey( clName )) {
clName = aliases.get( clName ); clName = aliases.get( clName );
} }
Class<?> cl = Reflection.forName( clName ); Class<?> cl = Reflection.forName( clName );
//Skip none-static inner classes as they can't be instantiated through bundle restoring //Skip none-static inner classes as they can't be instantiated through bundle restoring
//Classes which make use of none-static inner classes must manage instantiation manually //Classes which make use of none-static inner classes must manage instantiation manually
@ -132,54 +154,81 @@ public class Bundle {
return object; return object;
} }
} }
return null; return null;
} }
public Bundlable get( String key ) { public Bundlable get( String key ) {
return getBundle( key ).get(); return getBundle( key ).get();
} }
public <E extends Enum<E>> E getEnum( String key, Class<E> enumClass ) { public <E extends Enum<E>> E getEnum( String key, Class<E> enumClass ) {
try { try {
return Enum.valueOf( enumClass, getString( key ) ); return Enum.valueOf( enumClass, data.getString( key ) );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e);
return enumClass.getEnumConstants()[0];
} catch (IllegalArgumentException e) {
Game.reportException(e); Game.reportException(e);
return enumClass.getEnumConstants()[0]; return enumClass.getEnumConstants()[0];
} }
} }
public int[] getIntArray( String key ) { public int[] getIntArray( String key ) {
try { try {
return data.get( key ).asIntArray(); JSONArray array = data.getJSONArray( key );
} catch (Exception e) { int length = array.length();
int[] result = new int[length];
for (int i=0; i < length; i++) {
result[i] = array.getInt( i );
}
return result;
} catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
return null; return null;
} }
} }
public float[] getFloatArray( String key ) { public float[] getFloatArray( String key ) {
try { try {
return data.get( key ).asFloatArray(); JSONArray array = data.getJSONArray( key );
} catch (Exception e) { int length = array.length();
float[] result = new float[length];
for (int i=0; i < length; i++) {
result[i] = (float)array.optDouble( i, 0.0 );
}
return result;
} catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
return null; return null;
} }
} }
public boolean[] getBooleanArray( String key ) { public boolean[] getBooleanArray( String key ) {
try { try {
return data.get( key ).asBooleanArray(); JSONArray array = data.getJSONArray( key );
} catch (Exception e) { int length = array.length();
boolean[] result = new boolean[length];
for (int i=0; i < length; i++) {
result[i] = array.getBoolean( i );
}
return result;
} catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
return null; return null;
} }
} }
public String[] getStringArray( String key ) { public String[] getStringArray( String key ) {
try { try {
return data.get( key ).asStringArray(); JSONArray array = data.getJSONArray( key );
} catch (Exception e) { int length = array.length();
String[] result = new String[length];
for (int i=0; i < length; i++) {
result[i] = array.getString( i );
}
return result;
} catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
return null; return null;
} }
@ -187,10 +236,11 @@ public class Bundle {
public Class[] getClassArray( String key ) { public Class[] getClassArray( String key ) {
try { try {
String[] clNames = data.get( key ).asStringArray(); JSONArray array = data.getJSONArray( key );
Class[] result = new Class[clNames.length]; int length = array.length();
for (int i=0; i < clNames.length; i++) { Class[] result = new Class[length];
String clName = clNames[i].replace("class ", ""); for (int i=0; i < length; i++) {
String clName = array.getString( i ).replace("class ", "");
if (aliases.containsKey( clName )) { if (aliases.containsKey( clName )) {
clName = aliases.get( clName ); clName = aliases.get( clName );
} }
@ -198,7 +248,7 @@ public class Bundle {
result[i] = cl; result[i] = cl;
} }
return result; return result;
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
return null; return null;
} }
@ -210,177 +260,177 @@ public class Bundle {
public Bundle[] getBundleArray( String key ){ public Bundle[] getBundleArray( String key ){
try { try {
JsonValue array = data.get( key ); JSONArray array = data.getJSONArray( key );
int length = array.size; int length = array.length();
Bundle[] result = new Bundle[length]; Bundle[] result = new Bundle[length];
for (int i=0; i < length; i++) { for (int i=0; i < length; i++) {
result[i] = new Bundle( array.get( i ) ); result[i] = new Bundle( array.getJSONObject( i ) );
} }
return result; return result;
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
return null; return null;
} }
} }
public Collection<Bundlable> getCollection( String key ) { public Collection<Bundlable> getCollection( String key ) {
ArrayList<Bundlable> list = new ArrayList<>(); ArrayList<Bundlable> list = new ArrayList<>();
try { try {
JsonValue array = data.get( key ); JSONArray array = data.getJSONArray( key );
for (JsonValue element : array) { for (int i=0; i < array.length(); i++) {
Bundlable O = new Bundle( element ).get(); Bundlable O = new Bundle( array.getJSONObject( i ) ).get();
if (O != null) list.add( O ); if (O != null) list.add( O );
} }
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
return list; return list;
} }
public void put( String key, boolean value ) { public void put( String key, boolean value ) {
try { try {
data.addChild( key, new JsonValue(value) ); data.put( key, value );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
} }
public void put( String key, int value ) { public void put( String key, int value ) {
try { try {
data.addChild( key, new JsonValue(value) ); data.put( key, value );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
} }
public void put( String key, long value ) { public void put( String key, long value ) {
try { try {
data.addChild( key, new JsonValue(value) ); data.put( key, value );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
} }
public void put( String key, float value ) { public void put( String key, float value ) {
try { try {
data.addChild( key, new JsonValue(value) ); data.put( key, value );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
} }
public void put( String key, String value ) { public void put( String key, String value ) {
try { try {
data.addChild( key, new JsonValue(value) ); data.put( key, value );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
} }
public void put( String key, Class value ){ public void put( String key, Class value ){
try { try {
data.addChild( key, new JsonValue(value.toString()) ); data.put( key, value );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
} }
public void put( String key, Bundle bundle ) { public void put( String key, Bundle bundle ) {
try { try {
data.addChild( key, bundle.data); data.put( key, bundle.data );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
} }
public void put( String key, Bundlable object ) { public void put( String key, Bundlable object ) {
if (object != null) { if (object != null) {
try { try {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.put( CLASS_NAME, object.getClass().getName() ); bundle.put( CLASS_NAME, object.getClass().getName() );
object.storeInBundle( bundle ); object.storeInBundle( bundle );
data.addChild( key, bundle.data); data.put( key, bundle.data );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
} }
} }
public void put( String key, Enum<?> value ) { public void put( String key, Enum<?> value ) {
if (value != null) { if (value != null) {
try { try {
data.addChild( key, new JsonValue(value.name()) ); data.put( key, value.name() );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
} }
} }
public void put( String key, int[] array ) { public void put( String key, int[] array ) {
try { try {
JsonValue JSON = new JsonValue(JsonValue.ValueType.array); JSONArray jsonArray = new JSONArray();
for (int val : array) { for (int i=0; i < array.length; i++) {
JSON.addChild(new JsonValue(val)); jsonArray.put( i, array[i] );
} }
data.addChild( key, JSON ); data.put( key, jsonArray );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
} }
public void put( String key, float[] array ) { public void put( String key, float[] array ) {
try { try {
JsonValue JSON = new JsonValue(JsonValue.ValueType.array); JSONArray jsonArray = new JSONArray();
for (float val : array) { for (int i=0; i < array.length; i++) {
JSON.addChild(new JsonValue(val)); jsonArray.put( i, array[i] );
} }
data.addChild( key, JSON ); data.put( key, jsonArray );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
} }
public void put( String key, boolean[] array ) { public void put( String key, boolean[] array ) {
try { try {
JsonValue JSON = new JsonValue(JsonValue.ValueType.array); JSONArray jsonArray = new JSONArray();
for (boolean val : array) { for (int i=0; i < array.length; i++) {
JSON.addChild(new JsonValue(val)); jsonArray.put( i, array[i] );
} }
data.addChild( key, JSON ); data.put( key, jsonArray );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
} }
public void put( String key, String[] array ) { public void put( String key, String[] array ) {
try { try {
JsonValue JSON = new JsonValue(JsonValue.ValueType.array); JSONArray jsonArray = new JSONArray();
for (String val : array) { for (int i=0; i < array.length; i++) {
JSON.addChild(new JsonValue(val)); jsonArray.put( i, array[i] );
} }
data.addChild( key, JSON ); data.put( key, jsonArray );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
} }
public void put( String key, Class[] array ){ public void put( String key, Class[] array ){
try { try {
JsonValue JSON = new JsonValue(JsonValue.ValueType.array); JSONArray jsonArray = new JSONArray();
for (Class val : array) { for (int i=0; i < array.length; i++) {
JSON.addChild(new JsonValue(val.getName())); jsonArray.put( i, array[i].getName() );
} }
data.addChild( key, JSON ); data.put( key, jsonArray );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
} }
public void put( String key, Collection<? extends Bundlable> collection ) { public void put( String key, Collection<? extends Bundlable> collection ) {
JsonValue JSON = new JsonValue(JsonValue.ValueType.array); JSONArray array = new JSONArray();
for (Bundlable object : collection) { for (Bundlable object : collection) {
//Skip none-static inner classes as they can't be instantiated through bundle restoring //Skip none-static inner classes as they can't be instantiated through bundle restoring
//Classes which make use of none-static inner classes must manage instantiation manually //Classes which make use of none-static inner classes must manage instantiation manually
@ -390,13 +440,13 @@ public class Bundle {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.put(CLASS_NAME, cl.getName()); bundle.put(CLASS_NAME, cl.getName());
object.storeInBundle(bundle); object.storeInBundle(bundle);
JSON.addChild(bundle.data); array.put(bundle.data);
} }
} }
} }
try { try {
data.addChild( key, JSON ); data.put( key, array );
} catch (Exception e) { } catch (JSONException e) {
Game.reportException(e); Game.reportException(e);
} }
} }
@ -405,7 +455,7 @@ public class Bundle {
private static final boolean compressByDefault = true; private static final boolean compressByDefault = true;
private static final int GZIP_BUFFER = 1024*4; //4 kb private static final int GZIP_BUFFER = 1024*4; //4 kb
public static Bundle read( InputStream stream ) throws IOException { public static Bundle read( InputStream stream ) throws IOException {
try { try {
@ -418,25 +468,39 @@ public class Bundle {
byte[] header = new byte[2]; byte[] header = new byte[2];
stream.read( header ); stream.read( header );
stream.reset(); stream.reset();
//GZIP header is 0x1f8b //GZIP header is 0x1f8b
if( header[ 0 ] == (byte) 0x1f && header[ 1 ] == (byte) 0x8b ) { if( header[ 0 ] == (byte) 0x1f && header[ 1 ] == (byte) 0x8b ) {
stream = new GZIPInputStream( stream, GZIP_BUFFER ); stream = new GZIPInputStream( stream, GZIP_BUFFER );
} }
//cannot just tokenize the stream directly as that constructor doesn't exist on Android //JSONTokenizer only has a string-based constructor on Android/iOS
BufferedReader reader = new BufferedReader( new InputStreamReader( stream )); BufferedReader reader = new BufferedReader( new InputStreamReader( stream ));
JsonValue json = new JsonReader().parse(reader); StringBuilder jsonBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
jsonBuilder.append(line);
}
String jsonString = jsonBuilder.toString();
Object json;
try {
json = new JSONTokener(jsonString).nextValue();
} catch (Exception e){
//if the string can't be tokenized, it may be written by v1.1.0 or v1.1.1,
// which used a 'minified' format. use libGDX JSON to read it.
JsonValue minJson = new JsonReader().parse(jsonString);
json = new JSONTokener(minJson.prettyPrint(JsonWriter.OutputType.json, 0)).nextValue();
}
reader.close(); reader.close();
//if the data is an array, put it in a fresh object with the default key //if the data is an array, put it in a fresh object with the default key
if (json.isArray()){ if (json instanceof JSONArray){
JsonValue result = new JsonValue( JsonValue.ValueType.object ); json = new JSONObject().put( DEFAULT_KEY, json );
result.addChild( DEFAULT_KEY, json );
return new Bundle(result);
} else {
return new Bundle(json);
} }
return new Bundle( (JSONObject) json );
} catch (Exception e) { } catch (Exception e) {
Game.reportException(e); Game.reportException(e);
throw new IOException(); throw new IOException();
@ -453,7 +517,8 @@ public class Bundle {
if (compressed) writer = new BufferedWriter( new OutputStreamWriter( new GZIPOutputStream(stream, GZIP_BUFFER ) ) ); if (compressed) writer = new BufferedWriter( new OutputStreamWriter( new GZIPOutputStream(stream, GZIP_BUFFER ) ) );
else writer = new BufferedWriter( new OutputStreamWriter( stream ) ); else writer = new BufferedWriter( new OutputStreamWriter( stream ) );
bundle.data.prettyPrint(JsonWriter.OutputType.json, writer); //JSONObject.write does not exist on Android/iOS
writer.write(bundle.data.toString());
writer.close(); writer.close();
return true; return true;