/** * This class implements JSON array storage capabilities. * Array stores ordered JSON values that can be referred by their index. * It can contain any mix of JSON value types and cannot have any gaps, * i.e. in array of length N, there must be a valid value for all indices * from 0 to N-1. * Copyright 2020 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * * Acedia is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, or * (at your option) any later version. * * Acedia is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Acedia. If not, see . */ class JArray extends JSON; // Data will simply be stored as an array of JSON values var private array data; // Return type of value stored at a given index. // Returns `JSON_Undefined` if and only if given index is out of bounds. public final function JType GetTypeOf(int index) { if (index < 0) return JSON_Undefined; if (index >= data.length) return JSON_Undefined; return data[index].type; } // Returns current length of this array. public final function int GetLength() { return data.length; } // Changes length of this array. // In case of the increase - fills new indices with `null` values. public final function SetLength(int newLength) { local int i; local int oldLength; oldLength = data.length; data.length = newLength; if (oldLength >= newLength) { return; } i = oldLength; while (i < newLength) { SetNull(i); i += 1; } } // Following functions are getters for various types of variables. // Getter for null value simply checks if it's null // and returns true/false as a result. // Getters for simple types (number, string, boolean) can have optional // default value specified, that will be returned if requested variable // doesn't exist or has a different type. // Getters for object and array types don't take default values and // will simply return `none`. public final function float GetNumber(int index, optional float defaultValue) { if (index < 0) return defaultValue; if (index >= data.length) return defaultValue; if (data[index].type != JSON_Number) return defaultValue; return data[index].numberValue; } public final function float GetInteger(int index, optional float defaultValue) { if (index < 0) return defaultValue; if (index >= data.length) return defaultValue; if (data[index].type != JSON_Number) return defaultValue; return data[index].numberValueAsInt; } public final function string GetString(int index, optional string defaultValue) { if (index < 0) return defaultValue; if (index >= data.length) return defaultValue; if (data[index].type != JSON_String) return defaultValue; return data[index].stringValue; } public final function class GetClass( int index, optional class defaultValue) { if (index < 0) return defaultValue; if (index >= data.length) return defaultValue; if (data[index].type != JSON_String) return defaultValue; TryLoadingStringAsClass(data[index]); if (data[index].stringValueAsClass != none) { return data[index].stringValueAsClass; } return defaultValue; } public final function bool GetBoolean(int index, optional bool defaultValue) { if (index < 0) return defaultValue; if (index >= data.length) return defaultValue; if (data[index].type != JSON_Boolean) return defaultValue; return data[index].booleanValue; } public final function bool IsNull(int index) { if (index < 0) return false; if (index >= data.length) return false; return (data[index].type == JSON_Null); } public final function JArray GetArray(int index) { if (index < 0) return none; if (index >= data.length) return none; if (data[index].type != JSON_Array) return none; return JArray(data[index].complexValue); } public final function JObject GetObject(int index) { if (index < 0) return none; if (index >= data.length) return none; if (data[index].type != JSON_Object) return none; return JObject(data[index].complexValue); } // Following functions provide simple setters for boolean, string, number // and null values. // If passed index is negative - does nothing. // If index lies beyond array length (`>= GetLength()`), - // these functions will expand array in the same way as `GetLength()` function. // This can be prevented by setting optional parameter `preventExpansion` to // `false` (nothing will be done in this case). // They return object itself, allowing user to chain calls like this: // `array.SetNumber("num1", 1).SetNumber("num2", 2);`. public final function JArray SetNumber( int index, float value, optional bool preventExpansion ) { local JStorageAtom newStorageValue; if (index < 0) return self; if (index >= data.length) { if (preventExpansion) { return self; } else { SetLength(index + 1); } } newStorageValue.type = JSON_Number; newStorageValue.numberValue = value; newStorageValue.numberValueAsInt = int(value); newStorageValue.preferIntegerValue = false; data[index] = newStorageValue; return self; } public final function JArray SetInteger( int index, int value, optional bool preventExpansion ) { local JStorageAtom newStorageValue; if (index < 0) return self; if (index >= data.length) { if (preventExpansion) { return self; } else { SetLength(index + 1); } } newStorageValue.type = JSON_Number; newStorageValue.numberValue = float(value); newStorageValue.numberValueAsInt = value; newStorageValue.preferIntegerValue = true; data[index] = newStorageValue; return self; } public final function JArray SetString ( int index, string value, optional bool preventExpansion ) { local JStorageAtom newStorageValue; if (index < 0) return self; if (index >= data.length) { if (preventExpansion) { return self; } else { SetLength(index + 1); } } newStorageValue.type = JSON_String; newStorageValue.stringValue = value; data[index] = newStorageValue; return self; } public final function JArray SetClass( int index, class value, optional bool preventExpansion ) { local JStorageAtom newStorageValue; if (index < 0) return self; if (index >= data.length) { if (preventExpansion) { return self; } else { SetLength(index + 1); } } newStorageValue.type = JSON_String; newStorageValue.stringValue = string(value); newStorageValue.stringValueAsClass = value; data[index] = newStorageValue; return self; } public final function JArray SetBoolean ( int index, bool value, optional bool preventExpansion ) { local JStorageAtom newStorageValue; if (index < 0) return self; if (index >= data.length) { if (preventExpansion) { return self; } else { SetLength(index + 1); } } newStorageValue.type = JSON_Boolean; newStorageValue.booleanValue = value; data[index] = newStorageValue; return self; } public final function JArray SetNull ( int index, optional bool preventExpansion ) { local JStorageAtom newStorageValue; if (index < 0) return self; if (index >= data.length) { if (preventExpansion) { return self; } else { SetLength(index + 1); } } newStorageValue.type = JSON_Null; data[index] = newStorageValue; return self; } public final function JArray SetArray( int index, JArray template, optional bool preventExpansion ) { local JStorageAtom newStorageValue; if (index < 0) return self; if (template == none) return self; if (index >= data.length) { if (preventExpansion) { return self; } else { SetLength(index + 1); } } newStorageValue.type = JSON_Array; newStorageValue.complexValue = template.Clone(); data[index] = newStorageValue; return self; } public final function JArray SetObject( int index, JObject template, optional bool preventExpansion ) { local JStorageAtom newStorageValue; if (index < 0) return self; if (template == none) return self; if (index >= data.length) { if (preventExpansion) { return self; } else { SetLength(index + 1); } } newStorageValue.type = JSON_Object; newStorageValue.complexValue = template.Clone(); data[index] = newStorageValue; return self; } // JSON array and object types don't have setters, but instead have // functions to create a new, empty array/object under a certain name. // If passed index is negative - does nothing. // If index lies beyond array length (`>= GetLength()`), - // these functions will expand array in the same way as `GetLength()` function. // This can be prevented by setting optional parameter `preventExpansion` to // `false` (nothing will be done in this case). // They return object itself, allowing user to chain calls like this: // `array.CreateObject("sub object").CreateArray("sub array");`. public final function JArray CreateArray ( int index, optional bool preventExpansion ) { local JStorageAtom newStorageValue; if (index < 0) return self; if (index >= data.length) { if (preventExpansion) { return self; } else { SetLength(index + 1); } } newStorageValue.type = JSON_Array; newStorageValue.complexValue = _.json.newArray(); data[index] = newStorageValue; return self; } public final function JArray CreateObject ( int index, optional bool preventExpansion ) { local JStorageAtom newStorageValue; if (index < 0) return self; if (index >= data.length) { if (preventExpansion) { return self; } else { SetLength(index + 1); } } newStorageValue.type = JSON_Object; newStorageValue.complexValue = _.json.newObject(); data[index] = newStorageValue; return self; } // Wrappers for setter functions that don't take index or // `preventExpansion` parameters and add/create value at the end of the array. public final function JArray AddNumber(float value) { return SetNumber(data.length, value); } public final function JArray AddInteger(int value) { return SetInteger(data.length, value); } public final function JArray AddString(string value) { return SetString(data.length, value); } public final function JArray AddClass(class value) { return SetClass(data.length, value); } public final function JArray AddBoolean(bool value) { return SetBoolean(data.length, value); } public final function JArray AddNull() { return SetNull(data.length); } public final function JArray AddArray() { return CreateArray(data.length); } public final function JArray AddObject() { return CreateObject(data.length); } // Removes up to `amount` (minimum of `1`) of values, starting from // a given index. // If `index` falls outside array boundaries - nothing will be done. // Returns `true` if value was actually removed and `false` if it didn't exist. public final function bool RemoveValue(int index, optional int amount) { local int i; if (index < 0) return false; if (index >= data.length) return false; amount = Max(amount, 1); amount = Min(amount, data.length - index); for (i = index; i < index + amount; i += 1) { if (data[index].complexValue != none) { data[index].complexValue.Destroy(); } } data.Remove(index, amount); return true; } public function Clear() { RemoveValue(0, data.length); } public function bool IsSubsetOf(JSON rightValue) { local int i; local JArray rightArray; local array rightAtomArray; rightArray = JArray(rightValue); if (rightArray == none) return false; if (data.length > rightArray.data.length) return false; rightAtomArray = rightArray.data; for (i = 0; i < data.length; i += 1) { if (!AreAtomsEqual(data[i], rightAtomArray[i])) { return false; } } return true; } public function JSON Clone() { local int i; local JArray clonedArray; local array clonedData; clonedArray = _.json.NewArray(); if (clonedArray == none) { _.logger.Failure("Cannot clone `JArray`: cannot spawn a new instance."); return none; } clonedData = data; for (i = 0; i < clonedData.length; i += 1) { if (clonedData[i].complexValue == none) continue; if ( clonedData[i].type != JSON_Array && clonedData[i].type != JSON_Object) { continue; } clonedData[i].complexValue = clonedData[i].complexValue.Clone(); } clonedArray.data = clonedData; return clonedArray; } public function bool ParseIntoSelfWith(Parser parser) { local bool parsingSucceeded; local Parser.ParserState initState, confirmedState; local JStorageAtom nextAtom; local array parsedAtoms; if (parser == none) return false; initState = parser.GetCurrentState(); confirmedState = parser.Skip().Match("[").GetCurrentState(); if (!parser.Ok()) { parser.RestoreState(initState); return false; } while (parser.Ok() && !parser.HasFinished()) { confirmedState = parser.Skip().GetCurrentState(); if (parser.Match("]").Ok()) { parsingSucceeded = true; break; } if ( parsedAtoms.length > 0 && !parser.RestoreState(confirmedState).Match(",").Skip().Ok()) { break; } else if (parser.Ok()) { confirmedState = parser.GetCurrentState(); } nextAtom = ParseAtom(parser.RestoreState(confirmedState)); if (nextAtom.type == JSON_Undefined) { break; } parsedAtoms[parsedAtoms.length] = nextAtom; } HandleParsedAtoms(parsedAtoms, parsingSucceeded); if (!parsingSucceeded) { parser.RestoreState(initState); } return parsingSucceeded; } private function HandleParsedAtoms( array parsedAtoms, bool parsingSucceeded) { local int i; if (parsingSucceeded) { for (i = 0; i < parsedAtoms.length; i += 1) { data[data.length] = parsedAtoms[i]; } return; } for (i = 0; i < parsedAtoms.length; i += 1) { if (parsedAtoms[i].complexValue != none) { parsedAtoms[i].complexValue.Destroy(); } } } public function string DisplayWith(JSONDisplaySettings displaySettings) { local int i; local bool isntFirstElement; local string contents; local string openingBraces, closingBraces; local string elementsSeparator; local JSONDisplaySettings innerSettings; if (displaySettings.stackIndentation) { innerSettings = IndentSettings(displaySettings, true); } else { innerSettings = displaySettings; } // Prepare delimiters using appropriate indentation rules // We only use inner settings for the part right after '[', // as the rest is supposed to be aligned with outer objects openingBraces = displaySettings.beforeArrayOpening $ "[" $ innerSettings.afterArrayOpening; closingBraces = displaySettings.beforeArrayEnding $ "]" $ displaySettings.afterArrayEnding; elementsSeparator = "," $ innerSettings.afterArrayComma; if (innerSettings.colored) { elementsSeparator = "{$json_comma" $ elementsSeparator $ "}"; openingBraces = "{$json_arrayBraces" $ openingBraces $ "}"; closingBraces = "{$json_arrayBraces" $ closingBraces $ "}"; } // Display inner properties for (i = 0; i < data.length; i += 1) { if (isntFirstElement) { contents $= elementsSeparator; } contents $= DisplayAtom(data[i], innerSettings); isntFirstElement = true; } return openingBraces $ contents $ closingBraces; } defaultproperties { }