Browse Source

Finish `Collection`s refactoring

pull/8/head
Anton Tarasenko 2 years ago
parent
commit
66d02e3c8c
  1. 25
      sources/Aliases/AliasSource.uc
  2. 5
      sources/Avarice/AvariceLink.uc
  3. 10
      sources/Avarice/AvariceMessage.uc
  4. 10
      sources/Avarice/AvariceTcpStream.uc
  5. 7
      sources/BaseRealm/Iter.uc
  6. 6
      sources/Commands/BuiltInCommands/ACommandTest.uc
  7. 50
      sources/Data/Collections/ArrayList.uc
  8. 2
      sources/Data/Collections/ArrayListIterator.uc
  9. 1015
      sources/Data/Collections/AssociativeArray.uc
  10. 83
      sources/Data/Collections/AssociativeArrayIterator.uc
  11. 397
      sources/Data/Collections/Collection.uc
  12. 74
      sources/Data/Collections/CollectionsAPI.uc
  13. 817
      sources/Data/Collections/DynamicArray.uc
  14. 80
      sources/Data/Collections/DynamicArrayIterator.uc
  15. 50
      sources/Data/Collections/HashTable.uc
  16. 2
      sources/Data/Collections/HashTableIterator.uc
  17. 488
      sources/Data/Collections/Tests/TEST_AssociativeArray.uc
  18. 322
      sources/Data/Collections/Tests/TEST_DynamicArray.uc
  19. 143
      sources/Data/Collections/Tests/TEST_IteratorOld.uc
  20. 30
      sources/Data/Database/DBAPI.uc
  21. 9
      sources/Data/Database/Tests/TEST_LocalDatabase.uc
  22. 27
      sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc
  23. 2
      sources/Gameplay/KF1Frontend/World/KF1_EntityIterator.uc
  24. 2
      sources/Gameplay/KF1Frontend/World/KF1_TracingIterator.uc
  25. 4
      sources/Logger/Logger.uc
  26. 510
      sources/Text/JSON/JSONAPI.uc
  27. 18
      sources/Text/Tests/TEST_JSON.uc

25
sources/Aliases/AliasSource.uc

@ -23,7 +23,7 @@
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class AliasSource extends Singleton
dependson(AssociativeArray)
dependson(HashTable)
config(AcediaAliases);
// (Sub-)class of `Aliases` objects that this `AliasSource` uses to store
@ -50,7 +50,7 @@ var private config array<AliasValuePair> record;
// It contains same records as `record` array + aliases from
// `loadedAliasObjects` objects when there are no duplicate aliases.
// Otherwise only stores first loaded alias.
var private AssociativeArray aliasHash;
var private HashTable aliasHash;
var private LoggerAPI.Definition errIncorrectAliasPair, warnDuplicateAlias;
@ -63,7 +63,7 @@ protected function OnCreated()
}
// Load and hash
loadedAliasObjects = aliasesClass.static.LoadAllObjects();
aliasHash = _.collections.EmptyAssociativeArray();
aliasHash = _.collections.EmptyHashTable();
HashValidAliasesFromRecord();
HashValidAliasesFromPerObjectConfig();
}
@ -125,8 +125,8 @@ private final function HashValidAliasesFromPerObjectConfig()
// in a case-insensitive way.
private final function InsertAlias(BaseText alias, BaseText value)
{
local Text aliasLowerCaseCopy;
local AssociativeArray.Entry hashEntry;
local Text aliasLowerCaseCopy;
local HashTable.Entry hashEntry;
if (alias == none) return;
if (value == none) return;
aliasLowerCaseCopy = alias.LowerCopy();
@ -136,7 +136,8 @@ private final function InsertAlias(BaseText alias, BaseText value)
}
_.memory.Free(hashEntry.key);
_.memory.Free(hashEntry.value);
aliasHash.SetItem(aliasLowerCaseCopy, value.Copy(), true);
aliasHash.SetItem(aliasLowerCaseCopy, value);
aliasLowerCaseCopy.FreeSelf();
}
/**
@ -191,7 +192,7 @@ public function Text Resolve(BaseText alias, optional bool copyOnFailure)
result = Text(aliasHash.GetItem(lowerCaseAlias));
lowerCaseAlias.FreeSelf();
if (result != none) {
return result.Copy();
return result;
}
if (copyOnFailure) {
return alias.Copy();
@ -254,6 +255,8 @@ public final function bool AddAlias(
record[record.length] = newPair;
}
aliasHash.SetItem(lowerCaseAlias, aliasValue);
_.memory.Free(lowerCaseAlias);
_.memory.Free(aliasValue);
AliasService(class'AliasService'.static.Require()).PendingSaveSource(self);
return true;
}
@ -275,10 +278,10 @@ public final function bool AddAlias(
*/
public final function RemoveAlias(BaseText aliasToRemove)
{
local int i;
local bool isMatchingRecord;
local bool removedAliasFromRecord;
local AssociativeArray.Entry hashEntry;
local int i;
local bool isMatchingRecord;
local bool removedAliasFromRecord;
local HashTable.Entry hashEntry;
if (aliasToRemove == none) {
return;
}

5
sources/Avarice/AvariceLink.uc

@ -68,7 +68,7 @@ var private SimpleSignal onDeathSignal;
// We want to have a separate signal for each message "service", since most
// users of `AvariceLink` would only care about one particular service.
// To achieve that we use this array as a "service name" <-> "signal" map.
var private AssociativeArray serviceSignalMap;
var private HashTable serviceSignalMap;
var private const int TSERVICE_PREFIX, TTYPE_PREFIX;
var private const int TPARAMS_PREFIX, TMESSAGE_SUFFIX;
@ -80,7 +80,7 @@ protected function Constructor()
onConnectedSignal = SimpleSignal(_.memory.Allocate(class'SimpleSignal'));
onDisconnectedSignal = SimpleSignal(_.memory.Allocate(class'SimpleSignal'));
onDeathSignal = SimpleSignal(_.memory.Allocate(class'SimpleSignal'));
serviceSignalMap = _.collections.EmptyAssociativeArray();
serviceSignalMap = _.collections.EmptyHashTable();
}
protected function Finalizer()
@ -219,6 +219,7 @@ private final function Avarice_OnMessage_Signal GetServiceSignal(
result = Avarice_OnMessage_Signal(
_.memory.Allocate(class'Avarice_OnMessage_Signal'));
serviceSignalMap.SetItem(service, result);
_.memory.Free(result);
}
else {
service.FreeSelf();

10
sources/Avarice/AvariceMessage.uc

@ -35,7 +35,7 @@ var public Text type;
// Value of the "p" field
var public AcediaObject parameters;
var private AssociativeArray messageTemplate;
var private HashTable messageTemplate;
var private const int TS, TT, TP;
@ -44,7 +44,7 @@ public static function StaticConstructor()
if (StaticConstructorGuard()) return;
super.StaticConstructor();
default.messageTemplate = __().collections.EmptyAssociativeArray();
default.messageTemplate = __().collections.EmptyHashTable();
ResetTemplate(default.messageTemplate);
}
@ -58,7 +58,7 @@ protected function Finalizer()
parameters = none;
}
private static final function ResetTemplate(AssociativeArray template)
private static final function ResetTemplate(HashTable template)
{
if (template == none) {
return;
@ -70,8 +70,8 @@ private static final function ResetTemplate(AssociativeArray template)
public final function MutableText ToText()
{
local MutableText result;
local AssociativeArray template;
local MutableText result;
local HashTable template;
if (type == none) return none;
if (service == none) return none;

10
sources/Avarice/AvariceTcpStream.uc

@ -335,19 +335,19 @@ private final function AvariceMessage MessageFromText(BaseText message)
{
local Parser parser;
local AvariceMessage result;
local AssociativeArray parsedMessage;
local HashTable parsedMessage;
local AcediaObject item;
if (message == none) {
return none;
}
parser = _.text.Parse(message);
parsedMessage = _.json.ParseObjectWith(parser);
parsedMessage = _.json.ParseHashTableWith(parser);
parser.FreeSelf();
if (parsedMessage == none) {
return none;
}
result = AvariceMessage(_.memory.Allocate(class'AvariceMessage'));
item = parsedMessage.TakeItem(keyS, true);
item = parsedMessage.TakeItem(keyS);
if (item == none || item.class != class'Text')
{
_.memory.Free(item);
@ -356,7 +356,7 @@ private final function AvariceMessage MessageFromText(BaseText message)
return none;
}
result.service = Text(item);
item = parsedMessage.TakeItem(keyT, true);
item = parsedMessage.TakeItem(keyT);
if (item == none || item.class != class'Text')
{
_.memory.Free(item);
@ -365,7 +365,7 @@ private final function AvariceMessage MessageFromText(BaseText message)
return none;
}
result.type = Text(item);
result.parameters = parsedMessage.TakeItem(keyP, true);
result.parameters = parsedMessage.TakeItem(keyP);
_.memory.Free(parsedMessage);
return result;
}

7
sources/BaseRealm/Iter.uc

@ -25,14 +25,9 @@ class Iter extends AcediaObject
* Makes iterator pick next item.
* Use `HasFinished()` to check whether you have iterated all of them.
*
* @param skipNone @deprecated
* Set this to `true` if you want to skip all stored
* values that are equal to `none`. By default does not skip them.
* Since order of iterating through items is not guaranteed, at each
* `Next()` call an arbitrary set of items can be skipped.
* @return Reference to caller `Iterator` to allow for method chaining.
*/
public function Iter Next(optional bool skipNone);
public function Iter Next();
/**
* Returns current value pointed to by an iterator.

6
sources/Commands/BuiltInCommands/ACommandTest.uc

@ -28,8 +28,8 @@ protected function BuildData(CommandDataBuilder builder)
protected function Executed(Command.CallData result, EPlayer callerPlayer)
{
local Parser parser;
local AssociativeArray root;
local Parser parser;
local HashTable root;
/*local int i;
local WeaponLocker lol;
local array<WeaponLocker> aaa;
@ -63,7 +63,7 @@ protected function Executed(Command.CallData result, EPlayer callerPlayer)
}
}*/
parser = _.text.ParseString("{\"innerObject\":{\"my_bool\":true,\"array\":[\"Engine.Actor\",false,null,{\"something \\\"here\\\"\":\"yes\",\"maybe\":0.003},56.6],\"one more\":{\"nope\":324532,\"whatever\":false,\"o rly?\":\"ya rly\"},\"my_int\":-9823452},\"some_var\":-7.32,\"another_var\":\"aye!\"}");
root = _.json.ParseObjectWith(parser);
root = _.json.ParseHashTableWith(parser);
callerPlayer.BorrowConsole().WriteLine(_.json.PrettyPrint(root));
}

50
sources/Data/Collections/ArrayList.uc

@ -95,7 +95,7 @@ protected final function ArrayList FreeItem(int index)
return self;
}
public function Empty(optional bool deprecated)
public function Empty()
{
SetLength(0);
}
@ -1103,54 +1103,6 @@ public final function MutableText GetMutableText(int index)
return result;
}
/**
* Returns `AssociativeArray` item at `index`. If index is invalid or
* stores a non-`AssociativeArray` value, returns `none`.
*
* Referred value must be stored as `AssociativeArray`
* (or one of it's sub-classes) for this method to work.
*
* @param index Index of an `AssociativeArray` item that `ArrayList`
* has to return.
* @return `AssociativeArray` value at `index` in the caller `ArrayList`.
* `none` if passed `index` is invalid or non-`AssociativeArray` value
* is stored there.
*/
public final function AssociativeArray GetAssociativeArray(int index)
{
local AssociativeArray result;
result = AssociativeArray(BorrowItem(index));
if (result != none) {
result.NewRef();
}
return result;
}
/**
* Returns `DynamicArray` item at `index`. If index is invalid or
* stores a non-`DynamicArray` value, returns `none`.
*
* Referred value must be stored as `Text` (or one of it's sub-classes,
* such as `MutableText`) for this method to work.
*
* @param index Index of a `DynamicArray` item that caller `ArrayList`
* has to return.
* @return `DynamicArray` value at `index` in the caller `ArrayList`.
* `none` if passed `index` is invalid or non-`ArrayList` value
* is stored there.
*/
public final function DynamicArray GetDynamicArray(int index)
{
local DynamicArray result;
result = DynamicArray(BorrowItem(index));
if (result != none) {
result.NewRef();
}
return result;
}
/**
* Returns `ArrayList` item at `index`. If index is invalid or
* stores a non-`ArrayList` value, returns `none`.

2
sources/Data/Collections/ArrayListIterator.uc

@ -47,7 +47,7 @@ public function Iter LeaveOnlyNotNone()
return self;
}
public function Iter Next(optional bool deprecated)
public function Iter Next()
{
local int collectionLength;

1015
sources/Data/Collections/AssociativeArray.uc

File diff suppressed because it is too large Load Diff

83
sources/Data/Collections/AssociativeArrayIterator.uc

@ -1,83 +0,0 @@
/**
* Iterator for iterating over `AssociativeArray`'s items.
* 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 <https://www.gnu.org/licenses/>.
*/
class AssociativeArrayIterator extends CollectionIterator
dependson(AssociativeArray);
var private bool hasNotFinished;
var private AssociativeArray relevantCollection;
var private AssociativeArray.Index currentIndex;
protected function Finalizer()
{
relevantCollection = none;
}
public function bool Initialize(Collection relevantArray)
{
local AssociativeArray.Index emptyIndex;
currentIndex = emptyIndex;
relevantCollection = AssociativeArray(relevantArray);
if (relevantCollection == none) {
return false;
}
hasNotFinished = (relevantCollection.GetLength() > 0);
if (GetKey() == none) {
relevantCollection.IncrementIndex(currentIndex);
}
return true;
}
public function Iter Next(optional bool skipNone)
{
local int collectionLength;
if (!skipNone)
{
hasNotFinished = relevantCollection.IncrementIndex(currentIndex);
return self;
}
collectionLength = relevantCollection.GetLength();
while (hasNotFinished)
{
hasNotFinished = relevantCollection.IncrementIndex(currentIndex);
if (relevantCollection.GetEntryByIndex(currentIndex).value != none) {
return self;
}
}
return self;
}
public function AcediaObject Get()
{
return relevantCollection.GetEntryByIndex(currentIndex).value;
}
public function AcediaObject GetKey()
{
return relevantCollection.GetEntryByIndex(currentIndex).key;
}
public function bool HasFinished()
{
return !hasNotFinished;
}
defaultproperties
{
}

397
sources/Data/Collections/Collection.uc

@ -29,11 +29,11 @@ var protected class<CollectionIterator> iteratorClass;
*
* This method must return an item that `key` refers to with it's
* textual content (not as an object itself).
* For example, `DynamicArray` parses it into unsigned number, while
* `AssociativeArray` uses it as a key directly.
* For example, `ArrayList` parses it into unsigned number, while
* `HashTable` uses it as a key directly.
*
* There is no requirement that all stored values must be reachable by
* this method (i.e. `AssociativeArray` only lets you access values with
* this method (i.e. `HashTable` only lets you access values with
* `Text` keys).
*/
protected function AcediaObject GetByText(BaseText key);
@ -64,28 +64,18 @@ public final function CollectionIterator Iterate()
/**
* Completely clears caller `Collections` of all stored entries,
* deallocating any stored managed values.
*
* @param deallocateKeys Setting this to `true` will force this method to
* also deallocate all keys from the caller `Collection`, if it uses them.
* If this parameter is set to `true`, then `Empty()` call will also be
* made recursively for all stored `Collection`, also causing them to
* deallocate their keys.
* For Acedia keys are only used by `AssociativeArray`.
* Since we do not record whether `Collection` manages keys like it
* does values - all keys will be deallocated, so use this parameter with
* caution.
*/
public function Empty(optional bool deallocateKeys) {}
public function Empty() {}
/**
* Returns stored `AcediaObject` from the caller storage
* (or from it's sub-storages) via given `JSONPointer` path.
*
* Acedia provides two collections:
* 1. `DynamicArray` is treated as a JSON array in the context of
* 1. `ArrayList` is treated as a JSON array in the context of
* JSON pointers and passed variable names are treated as a `Text`
* representation of it's integer indices;
* 2. `AssociativeArray` is treated as a JSON object in the context of
* 2. `HashTable` is treated as a JSON object in the context of
* JSON pointers and passed variable names are treated as it's
* `Text` keys (to refer to an element with an empty key, use "/",
* since "" is treated as a JSON pointer and refers to
@ -95,8 +85,7 @@ public function Empty(optional bool deallocateKeys) {}
* appropriately defining `GetByText()` protected method.
*
* There is no requirement that all stored values must be reachable by
* this method (i.e. `AssociativeArray` only lets you access values with
* `Text` keys).
* this method (i.e. `HashTable` only lets you access values with `Text` keys).
*
* @param jsonPointer Path, given by a JSON pointer.
* @return An item `jsonPointerAsText` is referring to (according to the above
@ -106,16 +95,23 @@ public final function AcediaObject GetItemByJSON(JSONPointer jsonPointer)
{
local int segmentIndex;
local Text nextSegment;
local AcediaObject result;
local Collection nextCollection;
local AcediaObject result, nextObject;
local Collection prevCollection, nextCollection;
if (jsonPointer == none) return none;
if (jsonPointer.GetLength() < 1) return self;
nextCollection = self;
nextCollection.NewRef();
while (segmentIndex < jsonPointer.GetLength() - 1)
{
nextSegment = jsonPointer.GetComponent(segmentIndex);
nextCollection = Collection(nextCollection.GetByText(nextSegment));
prevCollection = nextCollection;
nextObject = nextCollection.GetByText(nextSegment);
nextCollection = Collection(nextObject);
_.memory.Free(prevCollection);
if (nextCollection == none) {
_.memory.Free(nextObject);
}
_.memory.Free(nextSegment);
if (nextCollection == none) {
break;
@ -147,10 +143,10 @@ public final function AcediaObject GetItemByJSON(JSONPointer jsonPointer)
* collection itself).
*
* Acedia provides two collections:
* 1. `DynamicArray` is treated as a JSON array in the context of
* 1. `ArrayList` is treated as a JSON array in the context of
* JSON pointers and passed variable names are treated as a `Text`
* representation of it's integer indices;
* 2. `AssociativeArray` is treated as a JSON object in the context of
* 2. `HashTable` is treated as a JSON object in the context of
* JSON pointers and passed variable names are treated as it's
* `Text` keys (to refer to an element with an empty key, use "/",
* since "" is treated as a JSON pointer and refers to
@ -160,7 +156,7 @@ public final function AcediaObject GetItemByJSON(JSONPointer jsonPointer)
* appropriately defining `GetByText()` protected method.
*
* There is no requirement that all stored values must be reachable by
* this method (i.e. `AssociativeArray` only lets you access values with
* this method (i.e. `HashTable` only lets you access values with
* `Text` keys).
*
* @param jsonPointer Path, given by a JSON pointer.
@ -200,22 +196,26 @@ public final function bool GetBoolBy(
BaseText jsonPointerAsText,
optional bool defaultValue)
{
local AcediaObject result;
local bool result;
local AcediaObject resultObject;
local BoolBox asBox;
local BoolRef asRef;
result = GetItemBy(jsonPointerAsText);
if (result == none) {
resultObject = GetItemBy(jsonPointerAsText);
if (resultObject == none) {
return defaultValue;
}
asBox = BoolBox(result);
result = defaultValue;
asBox = BoolBox(resultObject);
if (asBox != none) {
return asBox.Get();
result = asBox.Get();
}
asRef = BoolRef(result);
asRef = BoolRef(resultObject);
if (asRef != none) {
return asRef.Get();
result = asRef.Get();
}
return defaultValue;
_.memory.Free(resultObject);
return result;
}
/**
@ -238,22 +238,26 @@ public final function byte GetByteBy(
BaseText jsonPointerAsText,
optional byte defaultValue)
{
local AcediaObject result;
local byte result;
local AcediaObject resultObject;
local ByteBox asBox;
local ByteRef asRef;
result = GetItemBy(jsonPointerAsText);
if (result == none) {
resultObject = GetItemBy(jsonPointerAsText);
if (resultObject == none) {
return defaultValue;
}
asBox = ByteBox(result);
result = defaultValue;
asBox = ByteBox(resultObject);
if (asBox != none) {
return asBox.Get();
result = asBox.Get();
}
asRef = ByteRef(result);
asRef = ByteRef(resultObject);
if (asRef != none) {
return asRef.Get();
result = asRef.Get();
}
return defaultValue;
_.memory.Free(resultObject);
return result;
}
/**
@ -276,22 +280,26 @@ public final function int GetIntBy(
BaseText jsonPointerAsText,
optional int defaultValue)
{
local AcediaObject result;
local int result;
local AcediaObject resultObject;
local IntBox asBox;
local IntRef asRef;
result = GetItemBy(jsonPointerAsText);
if (result == none) {
resultObject = GetItemBy(jsonPointerAsText);
if (resultObject == none) {
return defaultValue;
}
asBox = IntBox(result);
result = defaultValue;
asBox = IntBox(resultObject);
if (asBox != none) {
return asBox.Get();
result = asBox.Get();
}
asRef = IntRef(result);
asRef = IntRef(resultObject);
if (asRef != none) {
return asRef.Get();
result = asRef.Get();
}
return defaultValue;
_.memory.Free(resultObject);
return result;
}
/**
@ -314,22 +322,26 @@ public final function float GetFloatBy(
BaseText jsonPointerAsText,
optional float defaultValue)
{
local AcediaObject result;
local float result;
local AcediaObject resultObject;
local FloatBox asBox;
local FloatRef asRef;
result = GetItemBy(jsonPointerAsText);
if (result == none) {
resultObject = GetItemBy(jsonPointerAsText);
if (resultObject == none) {
return defaultValue;
}
asBox = FloatBox(result);
result = defaultValue;
asBox = FloatBox(resultObject);
if (asBox != none) {
return asBox.Get();
result = asBox.Get();
}
asRef = FloatRef(result);
asRef = FloatRef(resultObject);
if (asRef != none) {
return asRef.Get();
result = asRef.Get();
}
return defaultValue;
_.memory.Free(resultObject);
return result;
}
/**
@ -352,22 +364,26 @@ public final function Vector GetVectorBy(
BaseText jsonPointerAsText,
optional Vector defaultValue)
{
local AcediaObject result;
local Vector result;
local AcediaObject resultObject;
local VectorBox asBox;
local VectorRef asRef;
result = GetItemBy(jsonPointerAsText);
if (result == none) {
resultObject = GetItemBy(jsonPointerAsText);
if (resultObject == none) {
return defaultValue;
}
asBox = VectorBox(result);
result = defaultValue;
asBox = VectorBox(resultObject);
if (asBox != none) {
return asBox.Get();
result = asBox.Get();
}
asRef = VectorRef(result);
asRef = VectorRef(resultObject);
if (asRef != none) {
return asRef.Get();
result = asRef.Get();
}
return defaultValue;
_.memory.Free(resultObject);
return result;
}
/**
@ -392,14 +408,16 @@ public final function string GetStringBy(
{
local AcediaObject result;
local Basetext asText;
result = GetItemBy(jsonPointerAsText);
if (result == none) {
return defaultValue;
}
asText = BaseText(result);
if (asText != none) {
return asText.ToString();
return _.text.ToString(asText);
}
_.memory.Free(result);
return defaultValue;
}
@ -425,14 +443,16 @@ public final function string GetFormattedStringBy(
{
local AcediaObject result;
local Basetext asText;
result = GetItemBy(jsonPointerAsText);
if (result == none) {
return defaultValue;
}
asText = BaseText(result);
if (asText != none) {
return asText.ToFormattedString();
return _.text.ToFormattedString(asText);
}
_.memory.Free(result);
return defaultValue;
}
@ -451,46 +471,16 @@ public final function string GetFormattedStringBy(
*/
public final function Text GetTextBy(BaseText jsonPointerAsText)
{
return Text(GetItemBy(jsonPointerAsText));
}
/**
* Returns an `AssociativeArray` value stored (in the caller `Collection` or
* one of it's sub-collections) pointed by
* [JSON pointer](https://tools.ietf.org/html/rfc6901).
* See `GetItemBy()` for more information.
*
* Referred value must be stored as `AssociativeArray`
* (or one of it's sub-classes) for this method to work.
*
* @param jsonPointerAsText Description of a path to the
* `AssociativeArray` value.
* @return `AssociativeArray` value, stored at `jsonPointerAsText` or
* `none` if it is missing or has a different type.
*/
public final function AssociativeArray GetAssociativeArrayBy(
BaseText jsonPointerAsText)
{
return AssociativeArray(GetItemBy(jsonPointerAsText));
}
local Text asText;
local AcediaObject result;
/**
* Returns an `DynamicArray` value stored (in the caller `Collection` or
* one of it's sub-collections) pointed by
* [JSON pointer](https://tools.ietf.org/html/rfc6901).
* See `GetItemBy()` for more information.
*
* Referred value must be stored as `DynamicArray`
* (or one of it's sub-classes) for this method to work.
*
* @param jsonPointerAsText Description of a path to the
* `DynamicArray` value.
* @return `DynamicArray` value, stored at `jsonPointerAsText` or
* `none` if it is missing or has a different type.
*/
public final function DynamicArray GetDynamicArrayBy(BaseText jsonPointerAsText)
{
return DynamicArray(GetItemBy(jsonPointerAsText));
result = GetItemBy(jsonPointerAsText);
asText = Text(result);
if (asText != none) {
return asText;
}
_.memory.Free(result);
return none;
}
/**
@ -509,7 +499,16 @@ public final function DynamicArray GetDynamicArrayBy(BaseText jsonPointerAsText)
public final function HashTable GetHashTableBy(
BaseText jsonPointerAsText)
{
return HashTable(GetItemBy(jsonPointerAsText));
local HashTable asHashTable;
local AcediaObject result;
result = GetItemBy(jsonPointerAsText);
asHashTable = HashTable(result);
if (asHashTable != none) {
return asHashTable;
}
_.memory.Free(result);
return none;
}
/**
@ -527,7 +526,16 @@ public final function HashTable GetHashTableBy(
*/
public final function ArrayList GetArrayListBy(BaseText jsonPointerAsText)
{
return ArrayList(GetItemBy(jsonPointerAsText));
local ArrayList asArrayList;
local AcediaObject result;
result = GetItemBy(jsonPointerAsText);
asArrayList = ArrayList(result);
if (asArrayList != none) {
return asArrayList;
}
_.memory.Free(result);
return none;
}
/**
@ -549,22 +557,26 @@ public final function bool GetBoolByJSON(
JSONPointer jsonPointer,
optional bool defaultValue)
{
local AcediaObject result;
local bool result;
local AcediaObject resultObject;
local BoolBox asBox;
local BoolRef asRef;
result = GetItemByJSON(jsonPointer);
if (result == none) {
resultObject = GetItemByJSON(jsonPointer);
if (resultObject == none) {
return defaultValue;
}
asBox = BoolBox(result);
result = defaultValue;
asBox = BoolBox(resultObject);
if (asBox != none) {
return asBox.Get();
result = asBox.Get();
}
asRef = BoolRef(result);
asRef = BoolRef(resultObject);
if (asRef != none) {
return asRef.Get();
result = asRef.Get();
}
return defaultValue;
_.memory.Free(resultObject);
return result;
}
/**
@ -586,22 +598,26 @@ public final function byte GetByteByJSON(
JSONPointer jsonPointer,
optional byte defaultValue)
{
local AcediaObject result;
local byte result;
local AcediaObject resultObject;
local ByteBox asBox;
local ByteRef asRef;
result = GetItemByJSON(jsonPointer);
if (result == none) {
resultObject = GetItemByJSON(jsonPointer);
if (resultObject == none) {
return defaultValue;
}
asBox = ByteBox(result);
result = defaultValue;
asBox = ByteBox(resultObject);
if (asBox != none) {
return asBox.Get();
result = asBox.Get();
}
asRef = ByteRef(result);
asRef = ByteRef(resultObject);
if (asRef != none) {
return asRef.Get();
result = asRef.Get();
}
return defaultValue;
_.memory.Free(resultObject);
return result;
}
/**
@ -623,22 +639,26 @@ public final function int GetIntByJSON(
JSONPointer jsonPointer,
optional int defaultValue)
{
local AcediaObject result;
local int result;
local AcediaObject resultObject;
local IntBox asBox;
local IntRef asRef;
result = GetItemByJSON(jsonPointer);
if (result == none) {
resultObject = GetItemByJSON(jsonPointer);
if (resultObject == none) {
return defaultValue;
}
asBox = IntBox(result);
result = defaultValue;
asBox = IntBox(resultObject);
if (asBox != none) {
return asBox.Get();
result = asBox.Get();
}
asRef = IntRef(result);
asRef = IntRef(resultObject);
if (asRef != none) {
return asRef.Get();
result = asRef.Get();
}
return defaultValue;
_.memory.Free(resultObject);
return result;
}
/**
@ -660,22 +680,26 @@ public final function float GetFloatByJSON(
JSONPointer jsonPointer,
optional float defaultValue)
{
local AcediaObject result;
local float result;
local AcediaObject resultObject;
local FloatBox asBox;
local FloatRef asRef;
result = GetItemByJSON(jsonPointer);
if (result == none) {
resultObject = GetItemByJSON(jsonPointer);
if (resultObject == none) {
return defaultValue;
}
asBox = FloatBox(result);
result = defaultValue;
asBox = FloatBox(resultObject);
if (asBox != none) {
return asBox.Get();
result = asBox.Get();
}
asRef = FloatRef(result);
asRef = FloatRef(resultObject);
if (asRef != none) {
return asRef.Get();
result = asRef.Get();
}
return defaultValue;
_.memory.Free(resultObject);
return result;
}
/**
@ -697,22 +721,26 @@ public final function Vector GetVectorByJSON(
JSONPointer jsonPointer,
optional Vector defaultValue)
{
local AcediaObject result;
local Vector result;
local AcediaObject resultObject;
local VectorBox asBox;
local VectorRef asRef;
result = GetItemByJSON(jsonPointer);
if (result == none) {
resultObject = GetItemByJSON(jsonPointer);
if (resultObject == none) {
return defaultValue;
}
asBox = VectorBox(result);
result = defaultValue;
asBox = VectorBox(resultObject);
if (asBox != none) {
return asBox.Get();
result = asBox.Get();
}
asRef = VectorRef(result);
asRef = VectorRef(resultObject);
if (asRef != none) {
return asRef.Get();
result = asRef.Get();
}
return defaultValue;
_.memory.Free(resultObject);
return result;
}
/**
@ -736,14 +764,16 @@ public final function string GetStringByJSON(
{
local AcediaObject result;
local BaseText asText;
result = GetItemByJSON(jsonPointer);
if (result == none) {
return defaultValue;
}
asText = BaseText(result);
if (asText != none) {
return asText.ToString();
return _.text.ToString(asText);
}
_.memory.Free(result);
return defaultValue;
}
@ -768,14 +798,16 @@ public final function string GetFormattedStringByJSON(
{
local AcediaObject result;
local BaseText asText;
result = GetItemByJSON(jsonPointer);
if (result == none) {
return defaultValue;
}
asText = BaseText(result);
if (asText != none) {
return asText.ToFormattedString();
return _.text.ToFormattedString(asText);
}
_.memory.Free(result);
return defaultValue;
}
@ -793,43 +825,16 @@ public final function string GetFormattedStringByJSON(
*/
public final function Text GetTextByJSON(JSONPointer jsonPointer)
{
return Text(GetItemByJSON(jsonPointer));
}
/**
* Returns an `AssociativeArray` value stored (in the caller `Collection` or
* one of it's sub-collections) pointed by JSON pointer.
* See `GetItemByJSON()` for more information.
*
* Referred value must be stored as `AssociativeArray`
* (or one of it's sub-classes) for this method to work.
*
* @param jsonPointer JSON path to the `AssociativeArray` value.
* @return `AssociativeArray` value, stored at `jsonPointerAsText` or
* `none` if it is missing or has a different type.
*/
public final function AssociativeArray GetAssociativeArrayByJSON(
JSONPointer jsonPointer)
{
return AssociativeArray(GetItemByJSON(jsonPointer));
}
local AcediaObject result;
local Text asText;
/**
* Returns an `DynamicArray` value stored (in the caller `Collection` or
* one of it's sub-collections) pointed by JSON pointer.
* See `GetItemByJSON()` for more information.
*
* Referred value must be stored as `DynamicArray`
* (or one of it's sub-classes) for this method to work.
*
* @param jsonPointer JSON path to the `DynamicArray` value.
* @return `DynamicArray` value, stored at `jsonPointerAsText` or
* `none` if it is missing or has a different type.
*/
public final function DynamicArray GetDynamicArrayByJSON(
JSONPointer jsonPointer)
{
return DynamicArray(GetItemByJSON(jsonPointer));
result = GetItemByJSON(jsonPointer);
asText = Text(result);
if (asText != none) {
return asText;
}
_.memory.Free(result);
return none;
}
/**
@ -847,7 +852,16 @@ public final function DynamicArray GetDynamicArrayByJSON(
public final function HashTable GetHashTableByJSON(
JSONPointer jsonPointer)
{
return HashTable(GetItemByJSON(jsonPointer));
local AcediaObject result;
local HashTable asHashTable;
result = GetItemByJSON(jsonPointer);
asHashTable = HashTable(result);
if (asHashTable != none) {
return asHashTable;
}
_.memory.Free(result);
return none;
}
/**
@ -865,7 +879,16 @@ public final function HashTable GetHashTableByJSON(
public final function ArrayList GetArrayListByJSON(
JSONPointer jsonPointer)
{
return ArrayList(GetItemByJSON(jsonPointer));
local AcediaObject result;
local ArrayList asArrayList;
result = GetItemByJSON(jsonPointer);
asArrayList = ArrayList(result);
if (asArrayList != none) {
return asArrayList;
}
_.memory.Free(result);
return none;
}
defaultproperties

74
sources/Data/Collections/CollectionsAPI.uc

@ -91,80 +91,6 @@ public final function HashTable EmptyHashTable()
return HashTable(_.memory.Allocate(class'HashTable'));
}
/**
* Creates a new `DynamicArray`, optionally filling it with objects from
* a given native array.
*
* @param objectArray Objects to place inside created `DynamicArray`;
* if empty (by default) - new, empty `DynamicArray` will be returned.
* Objects will be added in the same order as in `objectArray`.
* @param managed Flag that indicates whether objects from
* `objectArray` argument should be added as managed.
* By default `false` - they would not be managed.
* @return New `DynamicArray`, optionally filled with contents of
* `objectArray`. Guaranteed to be not `none` and to not contain any items
* outside of `objectArray`.
*/
public final function DynamicArray NewDynamicArray(
array<AcediaObject> objectArray,
optional bool managed)
{
local int i;
local DynamicArray result;
result = DynamicArray(_.memory.Allocate(class'DynamicArray'));
for (i = 0; i < objectArray.length; i += 1) {
result.AddItem(objectArray[i], managed);
}
return result;
}
/**
* Creates a new empty `DynamicArray`.
*
* @return New empty instance of `DynamicArray`.
*/
public final function DynamicArray EmptyDynamicArray()
{
return DynamicArray(_.memory.Allocate(class'DynamicArray'));
}
/**
* Creates a new `AssociativeArray`, optionally filling it with entries
* (key/value pairs) from a given native array.
*
* @param entriesArray Entries (key/value pairs) to place inside created
* `AssociativeArray`; if empty (by default) - new,
* empty `AssociativeArray` will be returned.
* @param managed Flag that indicates whether values from
* `entriesArray` argument should be added as managed.
* By default `false` - they would not be managed.
* @return New `AssociativeArray`, optionally filled with contents of
* `entriesArray`. Guaranteed to be not `none` and to not contain any items
* outside of `entriesArray`.
*/
public final function AssociativeArray NewAssociativeArray(
array<AssociativeArray.Entry> entriesArray,
optional bool managed)
{
local int i;
local AssociativeArray result;
result = AssociativeArray(_.memory.Allocate(class'AssociativeArray'));
for (i = 0; i < entriesArray.length; i += 1) {
result.SetItem(entriesArray[i].key, entriesArray[i].value, managed);
}
return result;
}
/**
* Creates a new empty `AssociativeArray`.
*
* @return New empty instance of `AssociativeArray`.
*/
public final function AssociativeArray EmptyAssociativeArray()
{
return AssociativeArray(_.memory.Allocate(class'AssociativeArray'));
}
defaultproperties
{
}

817
sources/Data/Collections/DynamicArray.uc

@ -1,817 +0,0 @@
/**
* Dynamic array object for storing arbitrary types of data. Generic
* storage is achieved by using `AcediaObject` as the stored type. Native
* variable types such as `int`, `bool`, etc. can be stored by boxing them into
* `AcediaObject`s.
* Appropriate classes and APIs for their construction are provided for
* main primitive types and can be extended to any custom `struct`.
* Copyright 2020 - 2021 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 <https://www.gnu.org/licenses/>.
*/
class DynamicArray extends Collection;
// Actual storage of all our data.
var private array<AcediaObject> storedObjects;
// `managedFlags[i] > 0` iff `contents[i]` is a managed object.
// Invariant `managedFlags.length == contents.length` should be enforced by
// all methods.
var private array<byte> managedFlags;
// Recorded `lifeVersions` of all stored objects.
// Invariant `lifeVersions.length == contents.length` should be enforced by
// all methods.
var private array<int> lifeVersions;
// Free array data
protected function Finalizer()
{
Empty();
}
// Method, used to compare array values at different indices.
// Does not check boundary conditions, so make sure passed indices are valid.
private function bool AreEqual(AcediaObject object1, AcediaObject object2)
{
if (object1 == none && object2 == none) return true;
if (object1 == none || object2 == none) return false;
return object1.IsEqual(object2);
}
/**
* Returns current length of dynamic `DynamicArray`.
* Cannot fail.
*
* @return Returns length of the caller `DynamicArray`.
* Guaranteed to be non-negative.
*/
public final function int GetLength()
{
return storedObjects.length;
}
/**
* Changes length of the caller `DynamicArray`.
* If `DynamicArray` size is increased as a result - added items will be
* filled with `none`s.
* If `DynamicArray` size is decreased - erased managed items will be
* automatically deallocated.
*
* @param newLength New length of an `DynamicArray`.
* If negative value is passes - method will do nothing.
* @return Reference to the caller `DynamicArray` to allow for method chaining.
*/
public final function DynamicArray SetLength(int newLength)
{
local int i;
if (newLength < 0) {
return self;
}
for (i = newLength; i < storedObjects.length; i += 1) {
FreeManagedItem(i);
}
storedObjects.length = newLength;
managedFlags.length = newLength;
lifeVersions.length = newLength;
return self;
}
/**
* Deallocates an item at a given index `index`, if it's managed.
* Does not check `DynamicArray` bounds for `index`, so you must ensure that
* `index` is valid.
*
* @param index Index of the managed item to deallocate.
* @return Reference to the caller `DynamicArray` to allow for method chaining.
*/
protected final function DynamicArray FreeManagedItem(int index)
{
if (storedObjects[index] == none) return self;
if (!storedObjects[index].IsAllocated()) return self;
if (managedFlags[index] <= 0) return self;
if (lifeVersions[index] != storedObjects[index].GetLifeVersion()) {
return self;
}
if ( storedObjects[index] != none && managedFlags[index] > 0
&& lifeVersions[index] == storedObjects[index].GetLifeVersion())
{
storedObjects[index].FreeSelf();
storedObjects[index] = none;
}
return self;
}
public function Empty(optional bool deallocateKeys)
{
local int i;
local Collection subCollection;
if (deallocateKeys)
{
for (i = 0; i < storedObjects.length; i += 1)
{
subCollection = Collection(GetItem(i));
if (subCollection != none) {
subCollection.Empty(true);
}
}
}
SetLength(0);
}
/**
* Adds `amountOfNewItems` empty (`none`) items at the end of
* the `DynamicArray`.
* To insert items at an arbitrary array index, use `Insert()`.
*
* @param amountOfNewItems Amount of items to add at the end.
* If non-positive value is passed, - method does nothing.
* @return Reference to the caller `DynamicArray` to allow for method chaining.
*/
public final function DynamicArray Add(int amountOfNewItems)
{
if (amountOfNewItems > 0) {
SetLength(storedObjects.length + amountOfNewItems);
}
return self;
}
/**
* Inserts `count` empty (`none`) items into the `DynamicArray`
* at specified position.
* The indices of the following items are increased by `count` in order
* to make room for the new items.
*
* To add items at the end of an `DynamicArray`, consider using `Add()`,
* which is equivalent to `array.Insert(array.GetLength(), ...)`.
*
* @param index Index, where first inserted item will be located.
* Must belong to `[0; self.GetLength()]` inclusive interval,
* otherwise method does nothing.
* @param count Amount of new items to insert.
* Must be positive, otherwise method does nothing.
* @return Reference to the caller `DynamicArray` to allow for method chaining.
*/
public final function DynamicArray Insert(int index, int count)
{
local int i;
local int swapIndex;
local int amountToShift;
if (count <= 0) return self;
if (index < 0 || index > storedObjects.length) return self;
amountToShift = storedObjects.length - index;
Add(count);
if (amountToShift == 0) {
return self;
}
for (i = 0; i < amountToShift; i += 1)
{
swapIndex = storedObjects.length - i - 1;
Swap(swapIndex, swapIndex - count);
}
return self;
}
/**
* Swaps two `DynamicArray` items, along with information about their
* managed status.
*
* @param index1 Index of item to swap.
* @param index2 Index of item to swap.
*/
protected final function Swap(int index1, int index2)
{
local AcediaObject temporaryItem;
local int temporaryNumber;
// Swap object
temporaryItem = storedObjects[index1];
storedObjects[index1] = storedObjects[index2];
storedObjects[index2] = temporaryItem;
// Swap life versions
temporaryNumber = lifeVersions[index1];
lifeVersions[index1] = lifeVersions[index2];
lifeVersions[index2] = temporaryNumber;
// Swap managed flags
temporaryNumber = managedFlags[index1];
managedFlags[index1] = managedFlags[index2];
managedFlags[index2] = temporaryNumber;
}
/**
* Removes number items from the `DynamicArray`, starting at `index`.
* All items before position and from `index + count` on are not changed,
* but the item indices change, - they shift to close the gap,
* created by removed items.
*
* @param index Remove items starting from this index.
* Must belong to `[0; self.GetLength() - 1]` inclusive interval,
* otherwise method does nothing.
* @param count Removes at most this much items.
* Must be positive, otherwise method does nothing.
* Specifying more items than can be removed simply removes
* all items, starting from `index`.
* @return Reference to the caller `DynamicArray` to allow for method chaining.
*/
public final function DynamicArray Remove(int index, int count)
{
local int i;
if (count <= 0) return self;
if (index < 0 || index > storedObjects.length) return self;
count = Min(count, storedObjects.length - index);
for (i = 0; i < count; i += 1) {
FreeManagedItem(index + i);
}
storedObjects.Remove(index, count);
managedFlags.Remove(index, count);
lifeVersions.Remove(index, count);
return self;
}
/**
* Removes item at a given index, shifting all the items that come after
* one place backwards.
*
* @param index Remove items starting from this index.
* Must belong to `[0; self.GetLength() - 1]` inclusive interval,
* otherwise method does nothing.
* @return Reference to the caller `DynamicArray` to allow for method chaining.
*/
public final function DynamicArray RemoveIndex(int index)
{
Remove(index, 1);
return self;
}
/**
* Checks if caller `DynamicArray`'s value at index `index` is managed.
*
* Managed values will be automatically deallocated once they are removed
* (or overwritten) from the caller `DynamicArray`.
*
* @return `true` if value, recorded in caller `DynamicArray` at index `index`
* is managed and `false` otherwise.
* If `index` is invalid (outside of `DynamicArray` bounds)
* also returns `false`.
*/
public final function bool IsManaged(int index)
{
if (index < 0) return false;
if (index >= storedObjects.length) return false;
if (storedObjects[index] == none) return false;
if (!storedObjects[index].IsAllocated()) return false;
if (storedObjects[index].GetLifeVersion() != lifeVersions[index]) {
return false;
}
return (managedFlags[index] > 0);
}
/**
* Returns item at `index` and replaces it with `none` inside `DynamicArray`.
* If index is invalid, returns `none`.
*
* If returned value was managed, it won't be deallocated
* and will stop being managed.
*
* @param index Index of an item that `DynamicArray` has to return.
* @return Either value at `index` in the caller `DynamicArray` or `none` if
* passed `index` is invalid.
*/
public final function AcediaObject TakeItem(int index)
{
local AcediaObject result;
if (index < 0) return none;
if (index >= storedObjects.length) return none;
if (storedObjects[index] == none) return none;
if (!storedObjects[index].IsAllocated()) return none;
if (storedObjects[index].GetLifeVersion() != lifeVersions[index]) {
return none;
}
result = storedObjects[index];
storedObjects[index] = none;
managedFlags[index] = 0;
lifeVersions[index] = 0;
return result;
}
/**
* Returns item at `index`. If index is invalid, returns `none`.
*
* @param index Index of an item that `DynamicArray` has to return.
* @return Either value at `index` in the caller `DynamicArray` or `none` if
* passed `index` is invalid.
*/
public final function AcediaObject GetItem(int index)
{
if (index < 0) return none;
if (index >= storedObjects.length) return none;
if (storedObjects[index] == none) return none;
if (!storedObjects[index].IsAllocated()) return none;
if (storedObjects[index].GetLifeVersion() != lifeVersions[index]) {
return none;
}
return storedObjects[index];
}
/**
* Changes `DynamicArray`'s value at `index` to `item`.
*
* @param index Index, at which to change the value. If `DynamicArray` is
* not long enough to hold it, it will be automatically expanded.
* If passed index is negative - method will do nothing.
* @param item Value to be set at a given index.
* @param managed Whether `item` should be managed by `DynamicArray`.
* By default (`false`) all items are not managed.
* @return Reference to the caller `DynamicArray` to allow for method chaining.
*/
public final function DynamicArray SetItem(
int index,
AcediaObject item,
optional bool managed)
{
if (index < 0) {
return self;
}
if (index >= storedObjects.length) {
SetLength(index + 1);
}
else if (item != storedObjects[index]) {
FreeManagedItem(index);
}
storedObjects[index] = item;
managedFlags[index] = 0;
if (managed) {
managedFlags[index] = 1;
}
if (item != none) {
lifeVersions[index] = item.GetLifeVersion();
}
return self;
}
/**
* Creates a new instance of class `valueClass` and records it's value at index
* `index` in the caller `DynamicArray`. Value is recorded as managed.
*
* @param index Index, at which to change the value. If `DynamicArray`
* is not long enough to hold it, it will be automatically expanded.
* If passed index is negative - method will do nothing.
* @param valueClass Class of object to create. Will only be created if
* passed `index` is valid.
* @return Caller `DynamicArray` to allow for method chaining.
*/
public final function DynamicArray CreateItem(
int index,
class<AcediaObject> valueClass)
{
if (index < 0) return self;
if (valueClass == none) return self;
return SetItem(index, AcediaObject(_.memory.Allocate(valueClass)), true);
}
/**
* Adds given `item` at the end of the `DynamicArray`, expanding it by
* one item.
* Cannot fail.
*
* @param item Item to be added at the end of the `DynamicArray`.
* @param managed Whether `item` should be managed by `DynamicArray`.
* By default (`false`) all items are not managed.
* @return Reference to the caller `DynamicArray` to allow for method chaining.
*/
public final function DynamicArray AddItem(
AcediaObject item,
optional bool managed)
{
return SetItem(storedObjects.length, item, managed);
}
/**
* Inserts given `item` at index `index` of the `DynamicArray`,
* shifting all the items starting from `index` one position to the right.
* Cannot fail.
*
* @param index Index at which to insert new item. Must belong to
* inclusive range `[0; self.GetLength()]`, otherwise method does nothing.
* @param item Item to insert.
* @param managed Whether `item` should be managed by `DynamicArray`.
* By default (`false`) all items are not managed.
* @return Reference to the caller `DynamicArray` to allow for method chaining.
*/
public final function DynamicArray InsertItem(
int index,
AcediaObject item,
optional bool managed)
{
if (index < 0) return self;
if (index > storedObjects.length) return self;
Insert(index, 1);
SetItem(index, item, managed);
return self;
}
/**
* Returns all occurrences of `item` in the caller `DynamicArray`
* (optionally only first one).
*
* @param item Item that needs to be removed from a `DynamicArray`.
* @param onlyFirstItem Set to `true` to only remove first occurrence.
* By default `false`, which means all occurrences will be removed.
* @return Reference to the caller `DynamicArray` to allow for method chaining.
*/
public final function DynamicArray RemoveItem(
AcediaObject item,
optional bool onlyFirstItem)
{
local int i;
while (i < storedObjects.length)
{
if (AreEqual(storedObjects[i], item))
{
Remove(i, 1);
if (onlyFirstItem) {
return self;
}
}
else {
i += 1;
}
}
return self;
}
/**
* Finds first occurrence of `item` in caller `DynamicArray` and returns
* it's index.
*
* @param item Item to find in `DynamicArray`.
* @return Index of first occurrence of `item` in caller `DynamicArray`.
* `-1` if `item` is not found.
*/
public final function int Find(AcediaObject item)
{
local int i;
for (i = 0; i < storedObjects.length; i += 1)
{
if (AreEqual(storedObjects[i], item)) {
return i;
}
}
return -1;
}
protected function AcediaObject GetByText(BaseText key)
{
local int index, consumed;
local Parser parser;
parser = _.text.Parse(key);
parser.MUnsignedInteger(index,,, consumed);
if (!parser.Ok())
{
parser.FreeSelf();
return none;
}
parser.FreeSelf();
return GetItem(index);
}
/**
* Returns `bool` item at `index`. If index is invalid or
* stores a non-`bool` value, returns `defaultValue`.
*
* Referred value must be stored as `BoolBox` or `BoolRef`
* (or one of their sub-classes) for this method to work.
*
* @param index Index of a `bool` item that `DynamicArray`
* has to return.
* @param defaultValue Value to return if there is either no item recorded
* at `index` or it has a wrong type.
* @return `bool` value at `index` in the caller `DynamicArray`.
* `defaultValue` if passed `index` is invalid or non-`bool` value
* is stored there.
*/
public final function bool GetBool(int index, optional bool defaultValue)
{
local AcediaObject result;
local BoolBox asBox;
local BoolRef asRef;
result = GetItem(index);
if (result == none) {
return defaultValue;
}
asBox = BoolBox(result);
if (asBox != none) {
return asBox.Get();
}
asRef = BoolRef(result);
if (asRef != none) {
return asRef.Get();
}
return defaultValue;
}
/**
* Changes `DynamicArray`'s value at `index` to `value` that will be recorded
* as either `BoolBox` or `BoolRef`, depending of `asRef` optional parameter.
*
* Inserted value will always be recorded as a managed value, i.e. it will be
* automatically deallocated when overwritten, removed or caller `DynamicArray`
* is deallocated.
*
* @param index Index, at which to change the value. If `DynamicArray` is
* not long enough to hold it, it will be automatically expanded.
* If passed index is negative - method will do nothing.
* @param value Value to be set at a given index.
* @param asRef Given `bool` value will be recorded as immutable `BoolBox`
* by default (`asRef == false`). Setting this parameter to `true` will
* make this method record it as a mutable `BoolRef`.
* @return Reference to the caller `DynamicArray` to allow for method chaining.
*/
public final function DynamicArray SetBool(
int index,
bool value,
optional bool asRef)
{
if (asRef) {
SetItem(index, _.ref.bool(value), true);
}
else {
SetItem(index, _.box.bool(value), true);
}
return self;
}
/**
* Returns `byte` item at `index`. If index is invalid or
* stores a non-`byte` value, returns `defaultValue`.
*
* Referred value must be stored as `ByteBox` or `ByteRef`
* (or one of their sub-classes) for this method to work.
*
* @param index Index of a `byte` item that `DynamicArray`
* has to return.
* @param defaultValue Value to return if there is either no item recorded
* at `index` or it has a wrong type.
* @return `byte` value at `index` in the caller `DynamicArray`.
* `defaultValue` if passed `index` is invalid or non-`byte` value
* is stored there.
*/
public final function byte GetByte(int index, optional byte defaultValue)
{
local AcediaObject result;
local ByteBox asBox;
local ByteRef asRef;
result = GetItem(index);
if (result == none) {
return defaultValue;
}
asBox = ByteBox(result);
if (asBox != none) {
return asBox.Get();
}
asRef = ByteRef(result);
if (asRef != none) {
return asRef.Get();
}
return defaultValue;
}
/**
* Changes `DynamicArray`'s value at `index` to `value` that will be recorded
* as either `ByteBox` or `ByteRef`, depending of `asRef` optional parameter.
*
* Inserted value will always be recorded as a managed value, i.e. it will be
* automatically deallocated when overwritten, removed or caller `DynamicArray`
* is deallocated.
*
* @param index Index, at which to change the value. If `DynamicArray` is
* not long enough to hold it, it will be automatically expanded.
* If passed index is negative - method will do nothing.
* @param value Value to be set at a given index.
* @param asRef Given `byte` value will be recorded as immutable `ByteBox`
* by default (`asRef == false`). Setting this parameter to `true` will
* make this method record it as a mutable `ByteRef`.
* @return Reference to the caller `DynamicArray` to allow for method chaining.
*/
public final function DynamicArray SetByte(
int index,
byte value,
optional bool asRef)
{
if (asRef) {
SetItem(index, _.ref.byte(value), true);
}
else {
SetItem(index, _.box.byte(value), true);
}
return self;
}
/**
* Returns `int` item at `index`. If index is invalid or
* stores a non-`int` value, returns `defaultValue`.
*
* Referred value must be stored as `IntBox` or `IntRef`
* (or one of their sub-classes) for this method to work.
*
* @param index Index of a `int` item that `DynamicArray`
* has to return.
* @param defaultValue Value to return if there is either no item recorded
* at `index` or it has a wrong type.
* @return `int` value at `index` in the caller `DynamicArray`.
* `defaultValue` if passed `index` is invalid or non-`int` value
* is stored there.
*/
public final function int GetInt(int index, optional int defaultValue)
{
local AcediaObject result;
local IntBox asBox;
local IntRef asRef;
result = GetItem(index);
if (result == none) {
return defaultValue;
}
asBox = IntBox(result);
if (asBox != none) {
return asBox.Get();
}
asRef = IntRef(result);
if (asRef != none) {
return asRef.Get();
}
return defaultValue;
}
/**
* Changes `DynamicArray`'s value at `index` to `value` that will be recorded
* as either `IntBox` or `IntRef`, depending of `asRef` optional parameter.
*
* Inserted value will always be recorded as a managed value, i.e. it will be
* automatically deallocated when overwritten, removed or caller `DynamicArray`
* is deallocated.
*
* @param index Index, at which to change the value. If `DynamicArray` is
* not long enough to hold it, it will be automatically expanded.
* If passed index is negative - method will do nothing.
* @param value Value to be set at a given index.
* @param asRef Given `int` value will be recorded as immutable `IntBox`
* by default (`asRef == false`). Setting this parameter to `true` will
* make this method record it as a mutable `IntRef`.
* @return Reference to the caller `DynamicArray` to allow for method chaining.
*/
public final function DynamicArray SetInt(
int index,
int value,
optional bool asRef)
{
if (asRef) {
SetItem(index, _.ref.int(value), true);
}
else {
SetItem(index, _.box.int(value), true);
}
return self;
}
/**
* Returns `float` item at `index`. If index is invalid or
* stores a non-`int` value, returns `defaultValue`.
*
* Referred value must be stored as `FloatBox` or `FloatRef`
* (or one of their sub-classes) for this method to work.
*
* @param index Index of a `float` item that `DynamicArray`
* has to return.
* @param defaultValue Value to return if there is either no item recorded
* at `index` or it has a wrong type.
* @return `float` value at `index` in the caller `DynamicArray`.
* `defaultValue` if passed `index` is invalid or non-`float` value
* is stored there.
*/
public final function float GetFloat(int index, optional float defaultValue)
{
local AcediaObject result;
local FloatBox asBox;
local FloatRef asRef;
result = GetItem(index);
if (result == none) {
return defaultValue;
}
asBox = FloatBox(result);
if (asBox != none) {
return asBox.Get();
}
asRef = FloatRef(result);
if (asRef != none) {
return asRef.Get();
}
return defaultValue;
}
/**
* Changes `DynamicArray`'s value at `index` to `value` that will be recorded
* as either `FloatBox` or `FloatRef`, depending of `asRef` optional parameter.
*
* Inserted value will always be recorded as a managed value, i.e. it will be
* automatically deallocated when overwritten, removed or caller `DynamicArray`
* is deallocated.
*
* @param index Index, at which to change the value. If `DynamicArray` is
* not long enough to hold it, it will be automatically expanded.
* If passed index is negative - method will do nothing.
* @param value Value to be set at a given index.
* @param asRef Given `float` value will be recorded as immutable `FloatBox`
* by default (`asRef == false`). Setting this parameter to `true` will
* make this method record it as a mutable `FloatRef`.
* @return Reference to the caller `DynamicArray` to allow for method chaining.
*/
public final function DynamicArray SetFloat(
int index,
float value,
optional bool asRef)
{
if (asRef) {
SetItem(index, _.ref.float(value), true);
}
else {
SetItem(index, _.box.float(value), true);
}
return self;
}
/**
* Returns `Text` item at `index`. If index is invalid or
* stores a non-`Text` value, returns `none`.
*
* Referred value must be stored as `Text` (or one of it's sub-classes,
* such as `MutableText`) for this method to work.
*
* @param index Index of a `Text` item that `DynamicArray` has to return.
* @return `Text` value at `index` in the caller `DynamicArray`.
* `none` if passed `index` is invalid or non-`Text` value
* is stored there.
*/
public final function Text GetText(int index)
{
return Text(GetItem(index));
}
/**
* Returns `AssociativeArray` item at `index`. If index is invalid or
* stores a non-`AssociativeArray` value, returns `none`.
*
* Referred value must be stored as `AssociativeArray`
* (or one of it's sub-classes) for this method to work.
*
* @param index Index of an `AssociativeArray` item that `DynamicArray`
* has to return.
* @return `AssociativeArray` value at `index` in the caller `DynamicArray`.
* `none` if passed `index` is invalid or non-`AssociativeArray` value
* is stored there.
*/
public final function AssociativeArray GetAssociativeArray(int index)
{
return AssociativeArray(GetItem(index));
}
/**
* Returns `DynamicArray` item at `index`. If index is invalid or
* stores a non-`DynamicArray` value, returns `none`.
*
* Referred value must be stored as `Text` (or one of it's sub-classes,
* such as `MutableText`) for this method to work.
*
* @param index Index of a `DynamicArray` item that caller `DynamicArray`
* has to return.
* @return `DynamicArray` value at `index` in the caller `DynamicArray`.
* `none` if passed `index` is invalid or non-`DynamicArray` value
* is stored there.
*/
public final function DynamicArray GetDynamicArray(int index)
{
return DynamicArray(GetItem(index));
}
defaultproperties
{
iteratorClass = class'DynamicArrayIterator'
}

80
sources/Data/Collections/DynamicArrayIterator.uc

@ -1,80 +0,0 @@
/**
* Iterator for iterating over `DynamicArray`'s items.
* 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 <https://www.gnu.org/licenses/>.
*/
class DynamicArrayIterator extends CollectionIterator;
var private DynamicArray relevantCollection;
var private int currentIndex;
protected function Finalizer()
{
relevantCollection = none;
}
public function bool Initialize(Collection relevantArray)
{
currentIndex = 0;
relevantCollection = DynamicArray(relevantArray);
if (relevantCollection == none) {
return false;
}
return true;
}
public function Iter Next(optional bool skipNone)
{
local int collectionLength;
if (!skipNone)
{
currentIndex += 1;
return self;
}
collectionLength = relevantCollection.GetLength();
while (currentIndex < collectionLength)
{
currentIndex += 1;
if (relevantCollection.GetItem(currentIndex) != none) {
return self;
}
}
return self;
}
public function AcediaObject Get()
{
return relevantCollection.GetItem(currentIndex);
}
/**
* Note that for `DynamicArrayIterator` this method produces a new `IntBox`
* object each time and requires manual deallocation.
*/
public function AcediaObject GetKey()
{
return _.box.int(currentIndex);
}
public function bool HasFinished()
{
return currentIndex >= relevantCollection.GetLength();
}
defaultproperties
{
}

50
sources/Data/Collections/HashTable.uc

@ -453,7 +453,7 @@ public final function HashTable RemoveItem(AcediaObject key)
return self;
}
public function Empty(optional bool deprecated)
public function Empty()
{
local int i, j;
local array<Entry> nextEntries;
@ -1155,54 +1155,6 @@ public final function Text GetText(AcediaObject key)
return result;
}
/**
* Returns `AssociativeArray` item stored at key `key`. If key is invalid or
* stores a non-`AssociativeArray` value, returns `none`.
*
* Referred value must be stored as `AssociativeArray`
* (or one of it's sub-classes) for this method to work.
*
* @param key Key of an `AssociativeArray` item that caller `HashTable`
* has to return.
* @return `AssociativeArray` value recorded with `key` in the caller
* `HashTable`. `none` if passed `key` is invalid or
* non-`AssociativeArray` value is stored with it.
*/
public final function AssociativeArray GetAssociativeArray(AcediaObject key)
{
local AssociativeArray result;
result = AssociativeArray(BorrowItem(key));
if (result != none) {
result.NewRef();
}
return result;
}
/**
* Returns `DynamicArray` item stored at key `key`. If key is invalid or
* stores a non-`DynamicArray` value, returns `none`.
*
* Referred value must be stored as `DynamicArray`
* (or one of it's sub-classes) for this method to work.
*
* @param key Key of a `DynamicArray` item that caller `HashTable`
* has to return.
* @return `DynamicArray` value recorded with `key` in the caller
* `HashTable`. `none` if passed `key` is invalid or
* non-`DynamicArray` value is stored with it.
*/
public final function DynamicArray GetDynamicArray(AcediaObject key)
{
local DynamicArray result;
result = DynamicArray(BorrowItem(key));
if (result != none) {
result.NewRef();
}
return result;
}
/**
* Returns `HashTable` item stored at key `key`. If key is invalid or
* stores a non-`HashTable` value, returns `none`.

2
sources/Data/Collections/HashTableIterator.uc

@ -57,7 +57,7 @@ public function Iter LeaveOnlyNotNone()
return self;
}
public function Iter Next(optional bool deprecated)
public function Iter Next()
{
local int collectionLength;

488
sources/Data/Collections/Tests/TEST_AssociativeArray.uc

@ -1,488 +0,0 @@
/**
* Set of tests for `AssociativeArray` class.
* 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 <https://www.gnu.org/licenses/>.
*/
class TEST_AssociativeArray extends TestCase
abstract;
protected static function TESTS()
{
Test_GetSet();
Test_HasKey();
Test_GetKeys();
Test_CopyTextKeys();
Test_Remove();
Test_CreateItem();
Test_Empty();
Test_Length();
Test_Managed();
Test_DeallocationHandling();
Test_Take();
Test_LargeArray();
}
protected static function Test_GetSet()
{
local Text textObject;
local AssociativeArray array;
array = AssociativeArray(__().memory.Allocate(class'AssociativeArray'));
Context("Testing getters and setters for items of `AssociativeArray`.");
Issue("`SetItem()` does not correctly set new items.");
textObject = __().text.FromString("value");
array.SetItem(__().text.FromString("key"), textObject);
array.SetItem(__().box.int(13), __().text.FromString("value #2"));
array.SetItem(__().box.float(345.2), __().box.bool(true));
TEST_ExpectTrue( Text(array.GetItem(__().box.int(13))).ToString()
== "value #2");
TEST_ExpectTrue(array.GetItem(__().text.FromString("key")) == textObject);
TEST_ExpectTrue(BoolBox(array.GetItem(__().box.float(345.2))).Get());
Issue("`SetItem()` does not correctly overwrite new items.");
array.SetItem(__().text.FromString("key"), __().text.FromString("value"));
array.SetItem(__().box.int(13), __().box.int(11));
TEST_ExpectFalse(array.GetItem(__().text.FromString("key")) == textObject);
TEST_ExpectTrue( Text(array.GetItem(__().text.FromString("key")))
.ToString() == "value");
TEST_ExpectTrue( IntBox(array.GetItem(__().box.int(13))).Get()
== 11);
Issue("`GetItem()` does not return `none` for non-existing keys.");
TEST_ExpectNone(array.GetItem(__().box.int(12)));
TEST_ExpectNone(array.GetItem(__().box.byte(67)));
TEST_ExpectNone(array.GetItem(__().box.float(43.1234)));
TEST_ExpectNone(array.GetItem(__().text.FromString("Some random stuff")));
}
protected static function Test_HasKey()
{
local AssociativeArray array;
array = AssociativeArray(__().memory.Allocate(class'AssociativeArray'));
Context("Testing `HasKey()` method for `AssociativeArray`.");
array.SetItem(__().text.FromString("key"), __().text.FromString("value"));
array.SetItem(__().box.int(13), __().text.FromString("value #2"));
array.SetItem(__().box.float(345.2), __().box.bool(true));
Issue("`HasKey()` reports that added keys do not exist in"
@ "`AssociativeArray`.");
TEST_ExpectTrue(array.HasKey(__().text.FromString("key")));
TEST_ExpectTrue(array.HasKey(__().box.int(13)));
TEST_ExpectTrue(array.HasKey(__().box.float(345.2)));
Issue("`HasKey()` reports that `AssociativeArray` contains keys that"
@ "were never added.");
TEST_ExpectFalse(array.HasKey(none));
TEST_ExpectFalse(array.HasKey(__().box.float(13)));
TEST_ExpectFalse(array.HasKey(__().box.byte(139)));
}
protected static function Test_GetKeys()
{
local int i;
local AcediaObject key1, key2, key3;
local array<AcediaObject> keys;
local AssociativeArray array;
array = AssociativeArray(__().memory.Allocate(class'AssociativeArray'));
Context("Testing `GetKeys()` method for `AssociativeArray`.");
key1 = __().text.FromString("key");
key2 = __().box.int(13);
key3 = __().box.float(345.2);
array.SetItem(key1, __().text.FromString("value"));
array.SetItem(key2, __().text.FromString("value #2"));
array.SetItem(key3, __().box.bool(true));
keys = array.GetKeys();
Issue("`GetKeys()` returns array with wrong amount of elements.");
TEST_ExpectTrue(keys.length == 3);
Issue("`GetKeys()` returns array with duplicate keys.");
TEST_ExpectTrue(keys[0] != keys[1]);
TEST_ExpectTrue(keys[0] != keys[2]);
TEST_ExpectTrue(keys[1] != keys[2]);
Issue("`GetKeys()` returns array with incorrect keys.");
for (i = 0; i < 3; i += 1) {
TEST_ExpectTrue(keys[i] == key1 || keys[i] == key2 || keys[i] == key3);
}
keys = array.RemoveItem(key1).GetKeys();
Issue("`GetKeys()` returns array with incorrect keys after removing"
@ "an element.");
TEST_ExpectTrue(keys.length == 2);
TEST_ExpectTrue(keys[0] != keys[1]);
for (i = 0; i < 2; i += 1) {
TEST_ExpectTrue(keys[i] == key2 || keys[i] == key3);
}
}
protected static function Test_CopyTextKeys()
{
local array<AcediaObject> allKeys;
local array<Text> keys;
local AssociativeArray array;
array = AssociativeArray(__().memory.Allocate(class'AssociativeArray'));
Context("Testing `CopyTextKeys()` method for `AssociativeArray`.");
array.SetItem(__().text.FromString("key"), __().text.FromString("value"));
array.SetItem(__().box.int(13), __().text.FromString("value #2"));
array.SetItem(__().box.float(-925.274), __().text.FromString("value #2"));
array.SetItem(__().text.FromString("second key"), __().box.bool(true));
Issue("`CopyTextKeys()` does not return correct set of keys.");
keys = array.CopyTextKeys();
TEST_ExpectTrue(keys.length == 2);
TEST_ExpectTrue(
(keys[0].ToString() == "key"
&& keys[1].ToString() == "second key")
|| (keys[0].ToString() == "second key"
&& keys[1].ToString() == "key"));
Issue("Deallocating keys returned by `CopyTextKeys()` affects their"
@ "source collection.");
allKeys = array.GetKeys();
TEST_ExpectTrue(allKeys.length == 4);
TEST_ExpectNotNone(array.GetItem(__().text.FromString("key")));
TEST_ExpectNotNone(array.GetItem(__().text.FromString("second key")));
TEST_ExpectTrue(
array.GetText(__().text.FromString("key")).ToString() == "value");
}
protected static function Test_Remove()
{
Context("Testing removing elements from `AssociativeArray`.");
SubTest_RemoveWithoutKeys();
SubTest_RemoveWithKeys();
}
protected static function SubTest_RemoveWithoutKeys()
{
local AcediaObject key1, key2, key3;
local array<AcediaObject> keys;
local AssociativeArray array;
array = AssociativeArray(__().memory.Allocate(class'AssociativeArray'));
key1 = __().text.FromString("some key");
key2 = __().box.int(25);
key3 = __().box.float(0.07);
array.SetItem(key1, __().text.FromString("value"));
array.SetItem(key2, __().text.FromString("value #2"));
array.SetItem(key3, __().box.bool(true));
Issue("Elements are not properly removed from `AssociativeArray`.");
array.RemoveItem(key1)
.RemoveItem(__().box.int(25))
.RemoveItem(__().box.float(0.06));
keys = array.GetKeys();
TEST_ExpectTrue(array.GetLength() == 1);
TEST_ExpectTrue(keys.length == 1);
TEST_ExpectTrue(keys[0] == key3);
Issue("Keys are deallocated upon removing elements from `AssociativeArray`"
@ "when they should not.");
TEST_ExpectTrue(key1.IsAllocated());
TEST_ExpectTrue(key2.IsAllocated());
}
protected static function SubTest_RemoveWithKeys()
{
local AcediaObject key1, key2, key3;
local array<AcediaObject> keys;
local AssociativeArray array;
array = AssociativeArray(__().memory.Allocate(class'AssociativeArray'));
key1 = __().text.FromString("some key");
key2 = __().box.int(25);
key3 = __().box.float(0.07);
array.SetItem(key1, __().text.FromString("value"));
array.SetItem(key2, __().text.FromString("value #2"));
array.SetItem(key3, __().box.bool(true));
Issue("Elements are not properly removed from `AssociativeArray`.");
array.RemoveItem(key1, true)
.RemoveItem(__().box.int(25), true)
.RemoveItem(__().box.float(0.06), true);
keys = array.GetKeys();
TEST_ExpectTrue(array.GetLength() == 1);
TEST_ExpectTrue(keys.length == 1);
TEST_ExpectTrue(keys[0] == key3);
Issue("Keys are not deallocated upon removing elements from"
@ "`AssociativeArray` when they should.");
TEST_ExpectFalse(key1.IsAllocated());
TEST_ExpectFalse(key2.IsAllocated());
}
protected static function Test_CreateItem()
{
local AssociativeArray array;
array = AssociativeArray(__().memory.Allocate(class'AssociativeArray'));
Context("Testing creating brand new items for `AssociativeArray`.");
Issue("`CreateItem()` incorrectly adds new values to the"
@ "`AssociativeArray`.");
array.CreateItem(__().text.FromString("key"), class'Text');
array.CreateItem(__().box.float(17.895), class'IntRef');
array.CreateItem(__().text.FromString("key #2"), class'BoolBox');
TEST_ExpectTrue(Text(array.GetItem(__().text.FromString("key")))
.ToString() == "");
TEST_ExpectTrue( IntRef(array.GetItem(__().box.float(17.895))).Get()
== 0);
TEST_ExpectFalse(BoolBox(array.GetItem(__().text.FromString("key #2")))
.Get());
Issue("`CreateItem()` incorrectly overrides existing values in the"
@ "`AssociativeArray`.");
array.SetItem(__().box.int(13), __().ref.int(7));
array.CreateItem(__().box.int(13), class'StringRef');
TEST_ExpectTrue( StringRef(array.GetItem(__().box.int(13))).Get()
== "");
class'MockItem'.default.objectCount = 0;
Issue("`CreateItem()` creates new object even if it cannot be recorded with"
@ "a given key.");
array.CreateItem(none, class'MockItem');
TEST_ExpectTrue(class'MockItem'.default.objectCount == 0);
}
protected static function Test_Empty()
{
local AcediaObject key1, key2, key3;
local AssociativeArray array;
key1 = __().text.FromString("key");
key2 = __().box.int(13);
key3 = __().box.float(345.2);
array = AssociativeArray(__().memory.Allocate(class'AssociativeArray'));
array.SetItem(key1, __().text.FromString("value"));
array.SetItem(key2, __().text.FromString("value #2"), true);
array.SetItem(key3, __().box.bool(true));
Context("Testing `Empty()` method for `AssociativeArray`.");
Issue("`AssociativeArray` still contains elements after being emptied.");
array.Empty();
TEST_ExpectTrue(array.GetKeys().length == 0);
TEST_ExpectTrue(array.GetLength() == 0);
Issue("`AssociativeArray` deallocated keys when not being told to do so.");
TEST_ExpectTrue(key1.IsAllocated());
TEST_ExpectTrue(key2.IsAllocated());
TEST_ExpectTrue(key3.IsAllocated());
Issue("`AssociativeArray` still contains elements after being emptied.");
array.SetItem(key1, __().text.FromString("value"), true);
array.SetItem(key2, __().text.FromString("value #2"));
array.SetItem(key3, __().box.bool(true), true);
array.Empty(true);
TEST_ExpectTrue(array.GetKeys().length == 0);
TEST_ExpectTrue(array.GetLength() == 0);
Issue("`AssociativeArray` does not deallocate keys when told to do so.");
TEST_ExpectFalse(key1.IsAllocated());
TEST_ExpectFalse(key2.IsAllocated());
TEST_ExpectFalse(key3.IsAllocated());
}
protected static function Test_Length()
{
local AssociativeArray array;
array = AssociativeArray(__().memory.Allocate(class'AssociativeArray'));
Context("Testing computing length of `AssociativeArray`.");
Issue("Length is not zero for newly created `AssociativeArray`.");
TEST_ExpectTrue(array.GetLength() == 0);
Issue("Length is incorrectly computed after adding elements to"
@ "`AssociativeArray`.");
array.SetItem(__().text.FromString("key"), __().text.FromString("value"));
array.SetItem(__().box.int(4563), __().text.FromString("value #2"));
array.SetItem(__().box.float(3425.243), __().box.byte(23));
TEST_ExpectTrue(array.GetLength() == 3);
Issue("Length is incorrectly computed after removing elements from"
@ "`AssociativeArray`.");
array.RemoveItem(__().box.int(4563));
TEST_ExpectTrue(array.GetLength() == 2);
}
protected static function MockItem NewMockItem()
{
return MockItem(__().memory.Allocate(class'MockItem'));
}
protected static function Test_Managed()
{
local AssociativeArray array;
class'MockItem'.default.objectCount = 0;
array = AssociativeArray(__().memory.Allocate(class'AssociativeArray'));
array.SetItem(__().box.int(0), NewMockItem());
array.SetItem(__().box.int(1), NewMockItem());
array.SetItem(__().box.int(2), NewMockItem());
array.SetItem(__().box.int(3), NewMockItem(), true);
array.SetItem(__().box.int(4), NewMockItem(), true);
array.SetItem(__().box.int(5), NewMockItem(), true);
array.CreateItem(__().box.int(6), class'MockItem');
array.CreateItem(__().box.int(7), class'MockItem');
array.CreateItem(__().box.int(8), class'MockItem');
Context("Testing how `AssociativeArray` deallocates managed objects.");
Issue("`RemoveItem()` incorrectly deallocates managed items.");
array.RemoveItem(__().box.int(0));
array.RemoveItem(__().box.int(3));
array.RemoveItem(__().box.int(6));
TEST_ExpectTrue(class'MockItem'.default.objectCount == 7);
Issue("Rewriting values with `SetItem()` incorrectly handles"
@ "managed items.");
array.SetItem(__().box.int(1), __().ref.int(28347));
array.SetItem(__().box.int(4), __().ref.float(13.4));
array.SetItem(__().box.int(7), __().ref.byte(94));
TEST_ExpectTrue(class'MockItem'.default.objectCount == 5);
Issue("Rewriting values with `CreateItem()` incorrectly handles"
@ "managed items.");
array.CreateItem(__().box.int(2), class'IntRef');
array.CreateItem(__().box.int(5), class'StringRef');
array.CreateItem(__().box.int(8), class'IntArrayBox');
TEST_ExpectTrue(class'MockItem'.default.objectCount == 3);
}
protected static function Test_DeallocationHandling()
{
Context("Testing how `AssociativeArray` deals with external deallocation of"
@ "keys and managed objects.");
SubTest_DeallocationHandlingKeys();
SubTest_DeallocationHandlingManagedObjects();
}
protected static function SubTest_DeallocationHandlingKeys()
{
local IntBox key1;
local BoolBox key2;
local Text key3;
local AssociativeArray array;
local array<AcediaObject> keys;
key1 = __().box.int(3881);
key2 = __().box.bool(true);
key3 = __().text.FromString("Text key, bay bee");
array = AssociativeArray(__().memory.Allocate(class'AssociativeArray'));
class'MockItem'.default.objectCount = 0;
array.SetItem(key1, NewMockItem());
array.SetItem(key2, __().box.float(32.31), true);
array.SetItem(key3, NewMockItem(), true);
Issue("Deallocating keys does not remove them from `AssociativeArray`.");
key1.FreeSelf();
key3.FreeSelf();
keys = array.GetKeys();
TEST_ExpectTrue(keys.length == 1);
TEST_ExpectTrue(keys[0] == key2);
Issue("`AssociativeArray` does not deallocate managed objects, even though"
@ "their keys were deallocated");
TEST_ExpectTrue(class'MockItem'.default.objectCount < 2);
TEST_ExpectNone(array.GetItem(__().box.int(3881)));
Issue("`AssociativeArray` deallocates unmanaged objects, when"
@ "their keys were deallocated");
TEST_ExpectTrue(class'MockItem'.default.objectCount > 0);
}
protected static function SubTest_DeallocationHandlingManagedObjects()
{
local MockItem exampleItem;
local AssociativeArray array;
class'MockItem'.default.objectCount = 0;
exampleItem = NewMockItem();
array = AssociativeArray(__().memory.Allocate(class'AssociativeArray'));
array.SetItem(__().box.int(-34), exampleItem, true);
array.SetItem(__().box.int(-7), exampleItem, true);
array.SetItem(__().box.int(23), NewMockItem());
array.SetItem(__().box.int(242), NewMockItem(), true);
array.CreateItem(__().box.int(24532), class'MockItem');
Issue("`AssociativeArray` does not return `none` even though stored object"
@ "was already deallocated.");
array.GetItem(__().box.int(23)).FreeSelf();
TEST_ExpectTrue(class'MockItem'.default.objectCount == 3);
TEST_ExpectTrue(array.GetItem(__().box.int(23)) == none);
Issue("Managed items are not deallocated when they are duplicated inside"
@ "`AssociativeArray`, but they should.");
array.RemoveItem(__().box.int(-7));
// At this point we got rid of all the managed objects that were generated
// in `array` + deallocated `exampleObject`.
TEST_ExpectTrue(class'MockItem'.default.objectCount == 2);
TEST_ExpectTrue(array.GetItem(__().box.int(-34)) == none);
}
protected static function Test_Take()
{
local AssociativeArray.Entry entry;
local AssociativeArray array;
class'MockItem'.default.objectCount = 0;
array = AssociativeArray(__().memory.Allocate(class'AssociativeArray'));
array.SetItem(__().box.int(0), NewMockItem());
array.SetItem(__().box.int(1), NewMockItem());
array.SetItem(__().box.int(2), NewMockItem());
array.SetItem(__().box.int(3), NewMockItem(), true);
array.SetItem(__().box.int(4), NewMockItem(), true);
array.SetItem(__().box.int(5), NewMockItem(), true);
array.CreateItem(__().box.int(6), class'MockItem');
array.CreateItem(__().box.int(7), class'MockItem');
array.CreateItem(__().box.int(8), class'MockItem');
Context("Testing `TakeItem()` method of `AssociativeArray`.");
Issue("`TakeItem()` returns incorrect value.");
TEST_ExpectTrue(array.TakeItem(__().box.int(0)).class == class'MockItem');
TEST_ExpectTrue(array.TakeItem(__().box.int(3)).class == class'MockItem');
TEST_ExpectTrue(array.TakeItem(__().box.int(6)).class == class'MockItem');
Issue("`TakeEntry()` returns incorrect value.");
entry = array.TakeEntry(__().box.int(4));
TEST_ExpectTrue(entry.key.class == class'IntBox');
TEST_ExpectTrue(entry.value.class == class'MockItem');
entry = array.TakeEntry(__().box.int(7));
TEST_ExpectTrue(entry.key.class == class'IntBox');
TEST_ExpectTrue(entry.value.class == class'MockItem');
Issue("Objects returned by `Take()` and `takeEntry()` are still managed by"
@ "`AssociativeArray`.");
array.Empty();
TEST_ExpectTrue(class'MockItem'.default.objectCount == 7);
}
protected static function Test_LargeArray()
{
local int i;
local AcediaObject nextKey;
local AssociativeArray array;
Context("Testing storing large amount of elements in `AssociativeArray`.");
Issue("`AssociativeArray` cannot handle large amount of elements.");
array = AssociativeArray(__().memory.Allocate(class'AssociativeArray'));
for (i = 0; i < 2500; i += 1) {
if (i % 2 == 0) {
nextKey = __().text.FromString("var" @ i);
}
else {
nextKey = __().box.int(i * 56 - 435632);
}
array.SetItem(nextKey, __().ref.int(i));
}
for (i = 0; i < 2500; i += 1) {
if (i % 2 == 0) {
nextKey = __().text.FromString("var" @ i);
}
else {
nextKey = __().box.int(i * 56 - 435632);
}
TEST_ExpectTrue(IntRef(array.GetItem(nextKey)).Get() == i);
}
}
defaultproperties
{
caseGroup = "Collections"
caseName = "AssociativeArray"
}

322
sources/Data/Collections/Tests/TEST_DynamicArray.uc

@ -1,322 +0,0 @@
/**
* Set of tests for `DynamicArray` class.
* 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 <https://www.gnu.org/licenses/>.
*/
class TEST_DynamicArray extends TestCase
abstract;
protected static function TESTS()
{
Test_GetSet();
Test_CreateItem();
Test_Length();
Test_Empty();
Test_AddInsert();
Test_Remove();
Test_Find();
Test_Managed();
Test_DeallocationHandling();
Test_Take();
}
protected static function Test_GetSet()
{
local DynamicArray array;
array = DynamicArray(__().memory.Allocate(class'DynamicArray'));
Context("Testing getters and setters for items of `DynamicArray`.");
Issue("Setters do not correctly expand `DynamicArray`.");
array.SetItem(0, __().box.int(-9)).SetItem(2, __().text.FromString("text"));
TEST_ExpectTrue(array.GetLength() == 3);
TEST_ExpectTrue(IntBox(array.GetItem(0)).Get() == -9);
TEST_ExpectNone(array.GetItem(1));
TEST_ExpectTrue(Text(array.GetItem(2)).ToString() == "text");
Issue("Setters do not correctly overwrite items of `DynamicArray`.");
array.SetItem(1, __().box.float(34.76));
array.SetItem(2, none);
TEST_ExpectTrue(array.GetLength() == 3);
TEST_ExpectTrue(IntBox(array.GetItem(0)).Get() == -9);
TEST_ExpectTrue(FloatBox(array.GetItem(1)).Get() == 34.76);
TEST_ExpectNone(array.GetItem(2));
}
protected static function Test_CreateItem()
{
local DynamicArray array;
array = DynamicArray(__().memory.Allocate(class'DynamicArray'));
Context("Testing creating brand new items for `DynamicArray`.");
Issue("`CreateItem()` incorrectly adds new values to the"
@ "`DynamicArray`.");
array.CreateItem(1, class'Text');
array.CreateItem(3, class'IntRef');
array.CreateItem(4, class'BoolBox');
TEST_ExpectNone(array.GetItem(0));
TEST_ExpectNone(array.GetItem(2));
TEST_ExpectTrue(Text(array.GetItem(1)).ToString() == "");
TEST_ExpectTrue(IntRef(array.GetItem(3)).Get() == 0);
TEST_ExpectFalse(BoolBox(array.GetItem(4)).Get());
Issue("`CreateItem()` incorrectly overrides existing values in the"
@ "`DynamicArray`.");
array.SetItem(5, __().ref.int(7));
array.CreateItem(5, class'StringRef');
TEST_ExpectTrue(StringRef(array.GetItem(5)).Get() == "");
class'MockItem'.default.objectCount = 0;
Issue("`CreateItem()` creates new object even if it cannot be recorded at"
@ "a given index.");
array.CreateItem(-1, class'MockItem');
TEST_ExpectTrue(class'MockItem'.default.objectCount == 0);
}
protected static function Test_Length()
{
local DynamicArray array;
array = DynamicArray(__().memory.Allocate(class'DynamicArray'));
Context("Testing length getter and setter for `DynamicArray`.");
Issue("Length of just created `DynamicArray` is not zero.");
TEST_ExpectTrue(array.GetLength() == 0);
Issue("`SetLength()` incorrectly changes length of the `DynamicArray`.");
array.SetLength(200).SetItem(198, __().box.int(25));
TEST_ExpectTrue(array.GetLength() == 200);
TEST_ExpectTrue(IntBox(array.GetItem(198)).Get() == 25);
array.SetLength(0);
TEST_ExpectTrue(array.GetLength() == 0);
Issue("Shrinking size of `DynamicArray` does not remove recorded items.");
array.SetLength(1000);
TEST_ExpectNone(array.GetItem(198));
}
protected static function Test_Empty()
{
local DynamicArray array;
array = DynamicArray(__().memory.Allocate(class'DynamicArray'));
Context("Testing emptying `DynamicArray`.");
array.AddItem(__().box.int(1)).AddItem(__().box.int(3))
.AddItem(__().box.int(1)).AddItem(__().box.int(3));
Issue("`Empty()` does not produce an empty array.");
array.Empty();
TEST_ExpectTrue(array.GetLength() == 0);
}
protected static function Test_AddInsert()
{
local DynamicArray array;
array = DynamicArray(__().memory.Allocate(class'DynamicArray'));
Context("Testing adding new items to `DynamicArray`.");
Issue("`Add()`/`AddItem()` incorrectly add new items to"
@ "the `DynamicArray`.");
array.AddItem(__().box.int(3)).Add(3).AddItem(__().box.byte(7)).Add(1);
TEST_ExpectTrue(array.GetLength() == 6);
TEST_ExpectNotNone(IntBox(array.GetItem(0)));
TEST_ExpectTrue(IntBox(array.GetItem(0)).Get() == 3);
TEST_ExpectNone(array.GetItem(1));
TEST_ExpectNone(array.GetItem(2));
TEST_ExpectNone(array.GetItem(3));
TEST_ExpectNotNone(ByteBox(array.GetItem(4)));
TEST_ExpectTrue(ByteBox(array.GetItem(4)).Get() == 7);
TEST_ExpectNone(array.GetItem(5));
Issue("`Insert()`/`InsertItem()` incorrectly add new items to"
@ "the `DynamicArray`.");
array.Insert(2, 2).InsertItem(0, __().ref.bool(true));
TEST_ExpectTrue(array.GetLength() == 9);
TEST_ExpectNotNone(BoolRef(array.GetItem(0)));
TEST_ExpectTrue(BoolRef(array.GetItem(0)).Get());
TEST_ExpectNotNone(IntBox(array.GetItem(1)));
TEST_ExpectTrue(IntBox(array.GetItem(1)).Get() == 3);
TEST_ExpectNone(array.GetItem(2));
TEST_ExpectNone(array.GetItem(6));
TEST_ExpectNotNone(ByteBox(array.GetItem(7)));
TEST_ExpectTrue(ByteBox(array.GetItem(7)).Get() == 7);
TEST_ExpectNone(array.GetItem(8));
}
protected static function Test_Remove()
{
local DynamicArray array;
array = DynamicArray(__().memory.Allocate(class'DynamicArray'));
Context("Testing removing items from `DynamicArray`.");
array.AddItem(__().box.int(1)).AddItem(__().box.int(3))
.AddItem(__().box.int(1)).AddItem(__().box.int(3))
.AddItem(__().box.int(5)).AddItem(__().box.int(2))
.AddItem(__().box.int(4)).AddItem(__().box.int(7))
.AddItem(__().box.int(5)).AddItem(__().box.int(1))
.AddItem(__().box.int(5)).AddItem(__().box.int(0));
Issue("`Remove()` incorrectly removes items from array.");
array.Remove(3, 2).Remove(0, 2).Remove(7, 9);
TEST_ExpectTrue(array.GetLength() == 7);
TEST_ExpectTrue(IntBox(array.GetItem(0)).Get() == 1);
TEST_ExpectTrue(IntBox(array.GetItem(1)).Get() == 2);
TEST_ExpectTrue(IntBox(array.GetItem(2)).Get() == 4);
TEST_ExpectTrue(IntBox(array.GetItem(3)).Get() == 7);
TEST_ExpectTrue(IntBox(array.GetItem(4)).Get() == 5);
TEST_ExpectTrue(IntBox(array.GetItem(5)).Get() == 1);
TEST_ExpectTrue(IntBox(array.GetItem(6)).Get() == 5);
Issue("`RemoveItem()` incorrectly removes items from array.");
array.RemoveItem(__().box.int(1)).RemoveItem(__().box.int(5), true);
TEST_ExpectTrue(array.GetLength() == 4);
TEST_ExpectTrue(IntBox(array.GetItem(0)).Get() == 2);
TEST_ExpectTrue(IntBox(array.GetItem(1)).Get() == 4);
TEST_ExpectTrue(IntBox(array.GetItem(2)).Get() == 7);
TEST_ExpectTrue(IntBox(array.GetItem(3)).Get() == 5);
Issue("`RemoveIndex()` incorrectly removes items from array.");
array.RemoveIndex(0).RemoveIndex(1).RemoveIndex(1);
TEST_ExpectTrue(array.GetLength() == 1);
TEST_ExpectTrue(IntBox(array.GetItem(0)).Get() == 4);
}
protected static function Test_Find()
{
local DynamicArray array;
array = DynamicArray(__().memory.Allocate(class'DynamicArray'));
Context("Testing searching for items in `DynamicArray`.");
array.AddItem(__().box.int(1)).AddItem(__().box.int(3))
.AddItem(__().box.int(1)).AddItem(__().box.int(3))
.AddItem(__().box.int(5)).AddItem(__().box.bool(true))
.AddItem(none).AddItem(__().box.float(72.54))
.AddItem(__().box.int(5)).AddItem(__().box.int(1))
.AddItem(__().box.int(5)).AddItem(__().box.int(0));
Issue("`Find()` does not properly find indices of existing items.");
TEST_ExpectTrue(array.Find(__().box.int(5)) == 4);
TEST_ExpectTrue(array.Find(__().box.int(1)) == 0);
TEST_ExpectTrue(array.Find(__().box.int(0)) == 11);
TEST_ExpectTrue(array.Find(__().box.float(72.54)) == 7);
TEST_ExpectTrue(array.Find(__().box.bool(true)) == 5);
TEST_ExpectTrue(array.Find(none) == 6);
Issue("`Find()` does not return `-1` on missing values.");
TEST_ExpectTrue(array.Find(__().box.int(42)) == -1);
TEST_ExpectTrue(array.Find(__().box.float(72.543)) == -1);
TEST_ExpectTrue(array.Find(__().box.bool(false)) == -1);
TEST_ExpectTrue(array.Find(__().box.byte(128)) == -1);
}
protected static function MockItem NewMockItem()
{
return MockItem(__().memory.Allocate(class'MockItem'));
}
// Creates array with mock objects, also zeroing their count.
// Managed items' count: 6, but 12 items total.
protected static function DynamicArray NewMockArray()
{
local DynamicArray array;
class'MockItem'.default.objectCount = 0;
array = DynamicArray(__().memory.Allocate(class'DynamicArray'));
array.AddItem(NewMockItem(), true).AddItem(NewMockItem(), false)
.InsertItem(2, NewMockItem(), true).AddItem(NewMockItem(), true)
.AddItem(NewMockItem(), false).AddItem(NewMockItem(), true)
.InsertItem(6, NewMockItem(), false).AddItem(NewMockItem(), true)
.InsertItem(3, NewMockItem(), false).AddItem(NewMockItem(), false)
.InsertItem(10, NewMockItem(), true).AddItem(NewMockItem(), false);
return array;
}
protected static function Test_Managed()
{
local MockItem exampleItem;
local DynamicArray array;
exampleItem = NewMockItem();
// Managed items' count: 6, but 12 items total.
array = NewMockArray();
Context("Testing how `DynamicArray` deallocates managed objects.");
Issue("`Remove()` incorrectly deallocates managed items.");
// -2 managed items
array.Remove(3, 2).Remove(0, 2).Remove(7, 9);
TEST_ExpectTrue(class'MockItem'.default.objectCount == 10);
Issue("`RemoveIndex()` incorrectly deallocates managed items.");
// -1 managed items
array.RemoveIndex(3).RemoveIndex(0);
TEST_ExpectTrue(class'MockItem'.default.objectCount == 9);
// -1 managed items
array.RemoveItem(exampleItem, true).RemoveItem(exampleItem, true);
TEST_ExpectTrue(class'MockItem'.default.objectCount == 8);
// -2 managed items
array.RemoveItem(exampleItem);
TEST_ExpectTrue(class'MockItem'.default.objectCount == 6);
array = NewMockArray();
Issue("Shrinking array with `SetLength()` incorrectly handles"
@ "managed items");
// -4 managed items
array.SetLength(3);
TEST_ExpectTrue(class'MockItem'.default.objectCount == 8);
Issue("Rewriting values with `SetItem()` incorrectly handles"
@ "managed items.");
// -2 managed items
array.SetItem(0, exampleItem, true);
array.SetItem(2, exampleItem, true);
TEST_ExpectTrue(class'MockItem'.default.objectCount == 6);
}
protected static function Test_DeallocationHandling()
{
local MockItem exampleItem;
local DynamicArray array;
exampleItem = NewMockItem();
// Managed items' count: 6, but 12 items total.
array = NewMockArray();
Context("Testing how `DynamicArray` deals with external deallocation of"
@ "managed objects.");
Issue("`DynamicArray` does not return `none` even though stored object"
@ "was already deallocated.");
array.GetItem(0).FreeSelf();
TEST_ExpectTrue(class'MockItem'.default.objectCount == 11);
TEST_ExpectTrue(array.GetItem(0) == none);
Issue("Managed items are not deallocated when they are duplicated inside"
@ "`DynamicArray`, but they should.");
array.SetItem(1, exampleItem, true).SetItem(2, exampleItem, true);
TEST_ExpectTrue(class'MockItem'.default.objectCount == 10);
array.SetLength(2);
// At this point we got rid of all the managed objects that were generated
// in `array` + deallocated `exampleObject`.
TEST_ExpectTrue(class'MockItem'.default.objectCount == 5);
TEST_ExpectTrue(array.GetItem(1) == none);
}
protected static function Test_Take()
{
local DynamicArray array;
Context("Testing `TakeItem()` method of `DynamicArray`.");
// Managed items' count: 6, but 12 items total.
array = NewMockArray();
Issue("`TakeItem()` returns incorrect value.");
TEST_ExpectTrue(array.TakeItem(0).class == class'MockItem');
TEST_ExpectTrue(array.TakeItem(3).class == class'MockItem');
TEST_ExpectTrue(array.TakeItem(4).class == class'MockItem');
TEST_ExpectTrue(array.TakeItem(6).class == class'MockItem');
Issue("Objects returned by `TakeItem()` are still managed by"
@ "`DynamicArray`.");
array.Empty();
TEST_ExpectTrue(class'MockItem'.default.objectCount == 9);
}
defaultproperties
{
caseGroup = "Collections"
caseName = "DynamicArray"
}

143
sources/Data/Collections/Tests/TEST_IteratorOld.uc

@ -1,143 +0,0 @@
/**
* Set of tests for `Iterator` classes.
* 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 <https://www.gnu.org/licenses/>.
*/
class TEST_IteratorOld extends TestCase
abstract;
var const int TESTED_ITEMS_AMOUNT;
var array<AcediaObject> items;
var array<byte> seenFlags;
protected static function CreateItems()
{
local int i;
ResetFlags();
for (i = 0; i < default.TESTED_ITEMS_AMOUNT; i += 1) {
default.items[default.items.length] = __().ref.float(i*2 + 1/i);
}
}
protected static function ResetFlags()
{
default.seenFlags.length = 0;
default.seenFlags.length = default.TESTED_ITEMS_AMOUNT;
}
protected static function DoTestIterator(Iter iter)
{
local int i;
local int seenCount;
local AcediaObject nextObject;
ResetFlags();
while (!iter.HasFinished())
{
nextObject = iter.Get();
for (i = 0; i < default.items.length; i += 1)
{
if (default.items[i] == nextObject)
{
if (default.seenFlags[i] == 0) {
seenCount += 1;
}
default.seenFlags[i] = 1;
continue;
}
}
iter.Next();
}
TEST_ExpectTrue(seenCount == default.TESTED_ITEMS_AMOUNT);
}
protected static function TESTS()
{
// Prepare
CreateItems();
// Test
Test_DynamicArray();
Test_AssociativeArray();
}
protected static function Test_DynamicArray()
{
local int i;
local CollectionIterator iter;
local DynamicArray array;
array = DynamicArray(__().memory.Allocate(class'DynamicArray'));
iter = array.Iterate();
Context("Testing iterator for `DynamicArray`");
Issue("`DynamicArray` returns `none` iterator.");
TEST_ExpectNotNone(iter);
Issue("Iterator for empty `DynamicArray` is not finished by default.");
TEST_ExpectTrue(iter.HasFinished());
Issue("Iterator for empty `DynamicArray` does not return `none` as"
@ "a current item.");
TEST_ExpectNone(iter.Get());
TEST_ExpectNone(iter.Next().Get());
for (i = 0; i < default.items.length; i += 1) {
array.AddItem(default.items[i]);
}
iter = array.Iterate();
Issue("`DynamicArray` returns `none` iterator.");
TEST_ExpectNotNone(iter);
Issue("`DynamicArray`'s iterator iterates over incorrect set of items.");
DoTestIterator(iter);
}
protected static function Test_AssociativeArray()
{
local int i;
local CollectionIterator iter;
local AssociativeArray array;
array = AssociativeArray(__().memory.Allocate(class'AssociativeArray'));
iter = array.Iterate();
Context("Testing iterator for `AssociativeArray`");
Issue("`AssociativeArray` returns `none` iterator.");
TEST_ExpectNotNone(iter);
Issue("Iterator for empty `AssociativeArray` is not finished by default.");
TEST_ExpectTrue(iter.HasFinished());
Issue("Iterator for empty `AssociativeArray` does not return `none` as"
@ "a current item.");
TEST_ExpectNone(iter.Get());
TEST_ExpectNone(iter.Next().Get());
for (i = 0; i < default.items.length; i += 1) {
array.SetItem(__().box.int(i), default.items[i]);
}
iter = array.Iterate();
Issue("`AssociativeArray` returns `none` iterator.");
TEST_ExpectNotNone(iter);
Issue("`AssociativeArray`'s iterator iterates over incorrect set of"
@ "items.");
DoTestIterator(iter);
}
defaultproperties
{
caseGroup = "Collections"
caseName = "IteratorOld"
TESTED_ITEMS_AMOUNT = 100
}

30
sources/Data/Database/DBAPI.uc

@ -25,7 +25,7 @@ var private const class<Database> localDBClass;
// Store all already loaded databases to make sure we do not create two
// different `LocalDatabaseInstance` that are trying to make changes
// separately.
var private AssociativeArray loadedLocalDatabases;
var private HashTable loadedLocalDatabases;
var private LoggerAPI.Definition infoLocalDatabaseCreated;
var private LoggerAPI.Definition infoLocalDatabaseDeleted;
@ -34,7 +34,7 @@ var private LoggerAPI.Definition infoLocalDatabaseLoaded;
private final function CreateLocalDBMapIfMissing()
{
if (loadedLocalDatabases == none) {
loadedLocalDatabases = __().collections.EmptyAssociativeArray();
loadedLocalDatabases = __().collections.EmptyHashTable();
}
}
@ -126,6 +126,7 @@ public final function LocalDatabaseInstance NewLocal(BaseText databaseName)
{
local DBRecord rootRecord;
local Text rootRecordName;
local Text databaseNameCopy;
local LocalDatabase newConfig;
local LocalDatabaseInstance newLocalDBInstance;
@ -138,13 +139,14 @@ public final function LocalDatabaseInstance NewLocal(BaseText databaseName)
if (loadedLocalDatabases.HasKey(databaseName)) return none;
newLocalDBInstance = LocalDatabaseInstance(_.memory.Allocate(localDBClass));
loadedLocalDatabases.SetItem(databaseName.Copy(), newLocalDBInstance);
databaseNameCopy = databaseName.Copy();
loadedLocalDatabases.SetItem(databaseNameCopy, newLocalDBInstance);
rootRecord = class'DBRecord'.static.NewRecord(databaseName);
rootRecordName = _.text.FromString(string(rootRecord.name));
newConfig.SetRootName(rootRecordName);
newConfig.Save();
newLocalDBInstance.Initialize(newConfig, rootRecord);
_.logger.Auto(infoLocalDatabaseCreated).Arg(databaseName.Copy());
_.logger.Auto(infoLocalDatabaseCreated).Arg(databaseNameCopy);
_.memory.Free(rootRecordName);
return newLocalDBInstance;
}
@ -201,6 +203,7 @@ public final function LocalDatabaseInstance LoadLocal(BaseText databaseName)
newLocalDBInstance.Initialize(newConfig, rootRecord);
_.logger.Auto(infoLocalDatabaseLoaded).Arg(databaseName.Copy());
_.memory.Free(rootRecordName);
_.memory.Free(newLocalDBInstance);
return newLocalDBInstance;
}
@ -212,9 +215,16 @@ public final function LocalDatabaseInstance LoadLocal(BaseText databaseName)
*/
public final function bool ExistsLocal(BaseText databaseName)
{
return LoadLocal(databaseName) != none;
local bool result;
local LocalDatabaseInstance instance;
instance = LoadLocal(databaseName);
result = (instance != none);
_.memory.Free(instance);
return result;
}
// TODO: deleted database must be marked as disposed + change tests too
/**
* Deletes local database with name `databaseName`.
*
@ -224,17 +234,19 @@ public final function bool ExistsLocal(BaseText databaseName)
*/
public final function bool DeleteLocal(BaseText databaseName)
{
local LocalDatabase localDatabaseConfig;
local LocalDatabaseInstance localDatabase;
local AssociativeArray.Entry dbEntry;
local LocalDatabase localDatabaseConfig;
local LocalDatabaseInstance localDatabase;
local HashTable.Entry dbEntry;
if (databaseName == none) {
return false;
}
CreateLocalDBMapIfMissing();
// To delete database we first need to load it
localDatabase = LoadLocal(databaseName);
if (localDatabase != none) {
if (localDatabase != none)
{
localDatabaseConfig = localDatabase.GetConfig();
_.memory.Free(localDatabase);
}
dbEntry = loadedLocalDatabases.TakeEntry(databaseName);
// Delete `LocalDatabaseInstance` before erasing the package,

9
sources/Data/Database/Tests/TEST_LocalDatabase.uc

@ -243,6 +243,8 @@ protected static function Test_LoadingPrepared()
SubTest_LoadingPreparedGetSizeNegative(db);
SubTest_LoadingPreparedGetKeysSuccess(db);
SubTest_LoadingPreparedGetKeysFail(db);
__().memory.Free(db);
__().memory.Free(db);
}
protected static function SubTest_LoadingPreparedSuccessRoot(
@ -491,6 +493,8 @@ protected static function Test_Writing()
Issue("`DeleteLocal()` does not return `true` after deleting existing"
@ "local database.");
__().memory.Free(db); // For `NewLocal()` call
__().memory.Free(db); // For `LoadLocal()` call
TEST_ExpectTrue(__().db.DeleteLocal(P("TEST_DB")));
Issue("Newly created database is reported to still exist after deletion.");
@ -516,6 +520,7 @@ protected static function Test_Recreate()
SubTest_WritingArrayIndicies(db);
__().db.DeleteLocal(P("TEST_DB"));
Issue("Newly created database is reported to still exist after deletion.");
__().memory.Free(db);
TEST_ExpectFalse(__().db.ExistsLocal(P("TEST_DB")));
TEST_ExpectFalse(db.IsAllocated());
}
@ -540,14 +545,14 @@ protected static function HashTable GetJSONSubTemplateObject()
{
local Parser parser;
parser = __().text.ParseString("{\"A\":\"simpleValue\",\"B\":11.12}");
return HashTable(__().json.ParseWith(parser,, true));
return HashTable(__().json.ParseWith(parser));
}
protected static function ArrayList GetJSONSubTemplateArray()
{
local Parser parser;
parser = __().text.ParseString("[true, null, \"huh\"]");
return ArrayList(__().json.ParseWith(parser,, true));
return ArrayList(__().json.ParseWith(parser));
}
/*

27
sources/Gameplay/KF1Frontend/BaseImplementation/EKFInventory.uc

@ -579,7 +579,7 @@ public function bool Remove(
local NativeActorRef pawnRef;
local Inventory nativeInstance;
local KFWeapon kfWeapon;
local DynamicArray removalList;
local ArrayList removalList;
if (EAmmo(itemToRemove) != none) return false;
nativeInstance = GetItemNativeInstance(itemToRemove);
if (nativeInstance == none) return false;
@ -600,8 +600,8 @@ public function bool Remove(
// guaranteed.
// Only optimize this if this method will become
// a bottleneck somewhere.
removalList = _.collections.EmptyDynamicArray();
removalList.AddItem(_server.unreal.ActorRef(nativeInstance), true);
removalList = _.collections.EmptyArrayList();
removalList.AddItem(_server.unreal.ActorRef(nativeInstance));
pawnRef = _server.unreal.ActorRef(pawn);
result = RemoveInventoryArray( pawnRef, removalList,
keepItem, forceRemoval, true);
@ -623,7 +623,7 @@ public function bool RemoveTemplate(
local bool result;
local Pawn pawn;
local NativeActorRef pawnRef;
local DynamicArray removalList;
local ArrayList removalList;
local class<Inventory> inventoryClass;
local class<KFWeapon> weaponClass;
if (template == none) return false;
@ -635,7 +635,7 @@ public function bool RemoveTemplate(
if (pawn == none) return false;
pawnRef = _server.unreal.ActorRef(pawn);
removalList = _.collections.EmptyDynamicArray();
removalList = _.collections.EmptyArrayList();
// All removal works the same - form a "kill list", then remove
// all `Inventory` at once with `RemoveInventoryArray`
AddClassForRemoval(removalList, inventoryClass, forceRemoval, removeAll);
@ -654,7 +654,7 @@ public function bool RemoveTemplate(
// Searches `EKFInventory`'s owner's inventory chain for items of class
// `inventoryClass` and adds them to the `removalArray` (for later removal).
private function AddClassForRemoval(
DynamicArray removalArray,
ArrayList removalArray,
class<Inventory> inventoryClass,
optional bool forceRemoval,
optional bool removeAll)
@ -686,7 +686,7 @@ private function AddClassForRemoval(
}
if (canRemoveInventory)
{
removalArray.AddItem(_server.unreal.ActorRef(nextInventory), true);
removalArray.AddItem(_server.unreal.ActorRef(nextInventory));
if (!removeAll) {
break;
}
@ -709,12 +709,12 @@ public function bool RemoveAll(
local NativeActorRef pawnRef;
local KFWeapon kfWeapon;
local Inventory nextInventory;
local DynamicArray inventoryToRemove;
local ArrayList inventoryToRemove;
pawn = GetOwnerPawn();
if (pawn == none) {
return false;
}
inventoryToRemove = _.collections.EmptyDynamicArray();
inventoryToRemove = _.collections.EmptyArrayList();
nextInventory = pawn.inventory;
while (nextInventory != none)
{
@ -723,11 +723,8 @@ public function bool RemoveAll(
&& (forceRemoval || !kfWeapon.bKFNeverThrow);
canRemoveItem = canRemoveItem
|| (includeHidden && Ammunition(nextInventory) == none);
if (canRemoveItem)
{
inventoryToRemove.AddItem(
_server.unreal.ActorRef(nextInventory),
true);
if (canRemoveItem) {
inventoryToRemove.AddItem(_server.unreal.ActorRef(nextInventory));
}
nextInventory = nextInventory.inventory;
}
@ -745,7 +742,7 @@ public function bool RemoveAll(
// dropped/removed.
private function bool RemoveInventoryArray(
NativeActorRef ownerPawnRef,
DynamicArray itemsToRemove,
ArrayList itemsToRemove,
bool keepItems,
bool forceRemoval,
bool completeRemoval)

2
sources/Gameplay/KF1Frontend/World/KF1_EntityIterator.uc

@ -101,7 +101,7 @@ private final function TryIterating()
iterated = true;
}
public function Iter Next(optional bool skipNone)
public function Iter Next()
{
if (!initialized) {
return self;

2
sources/Gameplay/KF1Frontend/World/KF1_TracingIterator.uc

@ -126,7 +126,7 @@ private final function TryTracing()
traced = true;
}
public function Iter Next(optional bool skipNone)
public function Iter Next()
{
if (!initialized) {
return self;

4
sources/Logger/Logger.uc

@ -33,7 +33,7 @@ class Logger extends AcediaObject
abstract;
// Named loggers are stored here to avoid recreating them
var protected AssociativeArray loadedLoggers;
var protected HashTable loadedLoggers;
// Should `Logger` display prefix indicating it's a log message from Acedia?
var protected config bool acediaStamp;
@ -68,7 +68,7 @@ public final static function Logger GetLogger(BaseText loggerName)
return none;
}
if (default.loadedLoggers == none) {
default.loadedLoggers = __().collections.EmptyAssociativeArray();
default.loadedLoggers = __().collections.EmptyHashTable();
}
loggerKey = loggerName.LowerCopy();
loggerInstance = Logger(default.loadedLoggers.GetItem(loggerKey));

510
sources/Text/JSON/JSONAPI.uc

@ -91,11 +91,11 @@ public final function JSONPointer Pointer(optional BaseText pointerAsText)
*
* Compatible objects are `none` and any object that has one of the following
* classes: `BoolBox`, `BoolRef`, `ByteBox`, `ByteRef`, `IntBox`, `IntRef`,
* `FloatBox`, `FloatRef`, `Text`, `MutableText`, `DynamicArray`,
* `AssociativeArray`.
* `FloatBox`, `FloatRef`, `Text`, `MutableText`, `ArrayList`,
* `HashTable`.
*
* This method does not check whether objects stored inside `DynamicArray`,
* `AssociativeArray` are compatible. If they are not, they will normally be
* This method does not check whether objects stored inside `ArrayList`,
* `HashTable` are compatible. If they are not, they will normally be
* defaulted to JSON null upon any conversion.
*/
public function bool IsCompatible(AcediaObject data)
@ -110,9 +110,7 @@ public function bool IsCompatible(AcediaObject data)
|| dataClass == class'IntBox' || dataClass == class'IntRef'
|| dataClass == class'FloatBox' || dataClass == class'FloatRef'
|| dataClass == class'Text' || dataClass == class'MutableText'
|| dataClass == class'ArrayList' || dataClass == class'HashTable'
|| dataClass == class'DynamicArray'
|| dataClass == class'AssociativeArray';
|| dataClass == class'ArrayList' || dataClass == class'HashTable';
}
/**
@ -598,75 +596,6 @@ public final function BaseText ParseString(
return immutableTextValue;
}
/**
* Uses given parser to parse a JSON array.
*
* This method will parse JSON values that are contained in parsed JSON array
* according to description given for `ParseWith()` method.
*
* It does not matter what content follows parsed value in the `parser`,
* method will be successful as long as it manages to parse correct
* JSON array (from the current `parser`'s position).
*
* To check whether parsing have failed, simply check if `parser` is in
* a failed state after the method call.
*
* @param parser Parser that method would use to parse JSON array
* from it's current position. It's confirmed state will not be changed.
* If parsing was successful it will point at the next available character.
* Parser will be in a failed state after this method iff
* parsing has failed.
* @param parseAsMutable `true` if you want this method to parse array's
* items as mutable values and `false` otherwise (as immutable ones).
* @return Parsed JSON array as `DynamicArray` if parsing was successful and
* `none` otherwise. To check for parsing success check the state of
* the `parser`.
*/
public final function DynamicArray ParseArrayWith(
Parser parser,
optional bool parseAsMutable)
{
local bool parsingSucceeded;
local Parser.ParserState confirmedState;
local AcediaObject nextValue;
local array<AcediaObject> parsedValues;
if (parser == none) return none;
confirmedState =
parser.Skip().Match(T(default.TOPEN_BRACKET)).GetCurrentState();
while (parser.Ok() && !parser.HasFinished())
{
confirmedState = parser.Skip().GetCurrentState();
// Check for JSON array ending and ONLY THEN declare parsing
// is successful, not encountering '}' implies bad JSON format.
if (parser.Match(T(default.TCLOSE_BRACKET)).Ok()) {
parsingSucceeded = true;
break;
}
parser.RestoreState(confirmedState);
// Look for comma after each element
if (parsedValues.length > 0)
{
if (!parser.Match(T(default.TCOMMA)).Skip().Ok()) {
break;
}
confirmedState = parser.GetCurrentState();
}
// Parse next value
nextValue = ParseWith(parser, parseAsMutable);
parsedValues[parsedValues.length] = nextValue;
if (!parser.Ok()) {
break;
}
}
if (parsingSucceeded) {
return _.collections.NewDynamicArray(parsedValues, true);
}
_.memory.FreeMany(parsedValues);
parser.Fail();
return none;
}
/**
* Uses given parser to parse a JSON array.
*
@ -725,7 +654,7 @@ public final function ArrayList ParseArrayListWith(
confirmedState = parser.GetCurrentState();
}
// Parse next value
nextValue = ParseWith(parser, parseAsMutable, true);
nextValue = ParseWith(parser, parseAsMutable);
parsedValues[parsedValues.length] = nextValue;
if (!parser.Ok()) {
break;
@ -741,79 +670,6 @@ public final function ArrayList ParseArrayListWith(
return result;
}
/**
* Uses given parser to parse a JSON object.
*
* This method will parse JSON values that are contained in parsed JSON object
* according to description given for `ParseWith()` method.
*
* It does not matter what content follows parsed value in the `parser`,
* method will be successful as long as it manages to parse correct
* JSON object (from the current `parser`'s position).
*
* To check whether parsing have failed, simply check if `parser` is in
* a failed state after the method call.
*
* @param parser Parser that method would use to parse JSON object
* from it's current position. It's confirmed state will not be changed.
* If parsing was successful it will point at the next available character.
* Parser will be in a failed state after this method iff
* parsing has failed.
* @param parseAsMutable `true` if you want this method to parse object's
* items as mutable values and `false` otherwise (as immutable ones).
* @return Parsed JSON object as `AssociativeArray` if parsing was successful
* and `none` otherwise. To check for parsing success check the state of
* the `parser`.
*/
public function AssociativeArray ParseObjectWith(
Parser parser,
optional bool parseAsMutable)
{
local bool parsingSucceeded;
local Parser.ParserState confirmedState;
local array<AssociativeArray.Entry> parsedEntries;
if (parser == none) return none;
// Ensure that parser starts pointing at what looks like a JSON object
confirmedState =
parser.Skip().Match(T(default.TOPEN_BRACE)).GetCurrentState();
if (!parser.Ok()) {
return none;
}
while (parser.Ok() && !parser.HasFinished())
{
confirmedState = parser.Skip().GetCurrentState();
// Check for JSON object ending and ONLY THEN declare parsing
// is successful, not encountering '}' implies bad JSON format.
if (parser.Match(T(default.TCLOSE_BRACE)).Ok())
{
parsingSucceeded = true;
break;
}
parser.RestoreState(confirmedState);
// Look for comma after each key-value pair
if (parsedEntries.length > 0)
{
if (!parser.Match(T(default.TCOMMA)).Skip().Ok()) {
break;
}
confirmedState = parser.GetCurrentState();
}
// Parse property
parsedEntries[parsedEntries.length] =
ParseProperty(parser, parseAsMutable);
if (!parser.Ok()) {
break;
}
}
if (parsingSucceeded) {
return _.collections.NewAssociativeArray(parsedEntries, true);
}
FreeEntries(parsedEntries);
parser.Fail();
return none;
}
/**
* Uses given parser to parse a JSON object.
*
@ -892,20 +748,7 @@ public function HashTable ParseHashTableWith(
return result;
}
// Parses a JSON key-value pair (there must not be any leading spaces).
private function AssociativeArray.Entry ParseProperty(
Parser parser,
bool parseAsMutable)
{
local MutableText nextKey;
local AssociativeArray.Entry entry;
parser.MStringLiteral(nextKey).Skip().Match(T(default.TCOLON)).Skip();
entry.key = nextKey.Copy();
nextKey.FreeSelf();
entry.value = ParseWith(parser, parseAsMutable);
return entry;
}
// TODO: ParseProperty
// Parses a JSON key-value pair (there must not be any leading spaces).
private function HashTable.Entry ParseHashTableProperty(
Parser parser,
@ -915,21 +758,11 @@ private function HashTable.Entry ParseHashTableProperty(
local HashTable.Entry entry;
parser.MStringLiteral(nextKey).Skip().Match(T(default.TCOLON)).Skip();
entry.key = nextKey.IntoText();
entry.value = ParseWith(parser, parseAsMutable, true);
entry.value = ParseWith(parser, parseAsMutable);
return entry;
}
// Auxiliary method for deallocating unneeded objects in entry pairs.
private function FreeEntries(array<AssociativeArray.Entry> entries)
{
local int i;
for (i = 0; i < entries.length; i += 1)
{
_.memory.Free(entries[i].key);
_.memory.Free(entries[i].value);
}
}
// TODO: FreeEntries
// Auxiliary method for deallocating unneeded objects in entry pairs.
private function FreeHashTableEntries(array<HashTable.Entry> entries)
{
@ -955,9 +788,9 @@ private function FreeHashTableEntries(array<HashTable.Entry> entries)
* `parseAsMutable` parameter (boxes are immutable, refs are mutable);
* 3. String values will be parsed as `Text`/`MutableText`, based on
* `parseAsMutable` parameter;
* 4. Array values will be parsed as a `DynamicArray`, it's items parsed
* 4. Array values will be parsed as a `ArrayList`s, their items parsed
* according to these rules (`parseAsMutable` parameter is propagated).
* 5. Object values will be parsed as a `AssociativeArray`, it's items
* 5. Object values will be parsed as a `HashTable`s, their items
* parsed according to these rules (`parseAsMutable` parameter is
* propagated) and recorded under the keys parsed into `Text`.
*
@ -983,8 +816,7 @@ private function FreeHashTableEntries(array<HashTable.Entry> entries)
*/
public final function AcediaObject ParseWith(
Parser parser,
optional bool parseAsMutable,
optional bool parseAsNew)
optional bool parseAsMutable)
{
local AcediaObject result;
local Parser.ParserState initState;
@ -1008,35 +840,17 @@ public final function AcediaObject ParseWith(
if (parser.Ok()) {
return result;
}
if (parseAsNew)
{
result = ParseArrayListWith(
parser.RestoreState(initState),
parseAsMutable);
if (parser.Ok()) {
return result;
}
result = ParseHashTableWith(
parser.RestoreState(initState),
parseAsMutable);
if (parser.Ok()) {
return result;
}
result = ParseArrayListWith(
parser.RestoreState(initState),
parseAsMutable);
if (parser.Ok()) {
return result;
}
else
{
result = ParseArrayWith(
parser.RestoreState(initState),
parseAsMutable);
if (parser.Ok()) {
return result;
}
result = ParseObjectWith(
parser.RestoreState(initState),
parseAsMutable);
if (parser.Ok()) {
return result;
}
result = ParseHashTableWith(
parser.RestoreState(initState),
parseAsMutable);
if (parser.Ok()) {
return result;
}
return none;
}
@ -1054,10 +868,10 @@ public final function AcediaObject ParseWith(
* 3. Integer (`IntBox`/`IntRef`) and float (`FloatBox`/`FloatRef`) types
* are printed into JSON number value;
* 4. `Text` and `MutableText` are printed into JSON string value;
* 5. `DynamicArray` is printed into JSON array with `Print()` method
* 5. `ArrayList` is printed into JSON array with `Print()` method
* applied to each of it's items. If some of them have not printable
* types - "none" will be used as a fallback.
* 6. `AssociativeArray` is printed into JSON object with `Print()` method
* 6. `HashTable` is printed into JSON object with `Print()` method
* applied to each of it's items. Only items with `Text` keys are
* printed, the rest is omitted. If some of them have not printable
* types - "none" will be used as a fallback.
@ -1094,66 +908,15 @@ public final function MutableText Print(AcediaObject toPrint)
|| toPrint.class == class'MutableText') {
return DisplayText(BaseText(toPrint));
}
if (toPrint.class == class'DynamicArray') {
return PrintArray(DynamicArray(toPrint));
}
if (toPrint.class == class'ArrayList') {
return PrintArrayList(ArrayList(toPrint));
}
if (toPrint.class == class'AssociativeArray') {
return PrintObject(AssociativeArray(toPrint));
}
if (toPrint.class == class'HashTable') {
return PrintHashTable(HashTable(toPrint));
}
return none;
}
/**
* "Prints" given `DynamicArray` value, saving it as a JSON array in
* `MutableText`.
*
* "Prints" given `DynamicArray` in a minimal way, for a human-readable output
* use `PrettyPrintArray()` method.
*
* It's items must either be equal to `none` or have one of the following
* classes: `BoolBox`, `BoolRef`, `IntBox`, `IntRef`, `FloatBox`, `FloatRef`,
* `Text`, `MutableText`, `DynamicArray`, `AssociativeArray`.
* Otherwise items will be printed as "null" values.
* Also see `Print()` method.
*
* @param toPrint Array to "print" into `MutableText`.
* @return Text version of given `toPrint`, if it has one of the printable
* classes. Otherwise returns `none`.
* Note that `none` is considered printable and will produce "null".
*/
public final function MutableText PrintArray(DynamicArray toPrint)
{
local int i, length;
local MutableText result, printedItem;
if (toPrint == none) return none;
length = toPrint.GetLength();
result = T(default.TOPEN_BRACKET).MutableCopy();
for (i = 0; i < length; i += 1)
{
if (i > 0) {
result.Append(T(default.TCOMMA));
}
printedItem = Print(toPrint.GetItem(i));
if (printedItem != none)
{
result.Append(printedItem);
printedItem.FreeSelf();
}
else {
result.Append(T(default.TNULL));
}
}
result.Append(T(default.TCLOSE_BRACKET));
return result;
}
/**
* "Prints" given `ArrayList` value, saving it as a JSON array in
* `MutableText`.
@ -1163,7 +926,7 @@ public final function MutableText PrintArray(DynamicArray toPrint)
*
* It's items must either be equal to `none` or have one of the following
* classes: `BoolBox`, `BoolRef`, `IntBox`, `IntRef`, `FloatBox`, `FloatRef`,
* `Text`, `MutableText`, `DynamicArray`, `AssociativeArray`.
* `Text`, `MutableText`, `ArrayList`, `HashTable`.
* Otherwise items will be printed as "null" values.
* Also see `Print()` method.
*
@ -1204,65 +967,6 @@ public final function MutableText PrintArrayList(ArrayList toPrint)
return result;
}
/**
* "Prints" given `AssociativeArray` value, saving it as a JSON object in
* `MutableText`.
*
* "Prints" given `AssociativeArray` in a minimal way, for
* a human-readable output use `PrettyPrintObject()` method.
*
* Only prints items recorded with `Text` key, the rest is omitted.
*
* It's items must either be equal to `none` or have one of the following
* classes: `BoolBox`, `BoolRef`, `IntBox`, `IntRef`, `FloatBox`, `FloatRef`,
* `Text`, `MutableText`, `DynamicArray`, `AssociativeArray`.
* Otherwise items will be printed as "null" values.
* Also see `Print()` method.
*
* @param toPrint Array to "print" into `MutableText`.
* @return Text version of given `toPrint`, if it has one of the printable
* classes. Otherwise returns `none`.
* Note that `none` is considered printable and will produce "null".
*/
public final function MutableText PrintObject(AssociativeArray toPrint)
{
local bool printedKeyValuePair;
local CollectionIterator iter;
local Text nextKey;
local AcediaObject nextValue;
local MutableText result, printedKey, printedValue;
if (toPrint == none) return none;
result = T(default.TOPEN_BRACE).MutableCopy();
iter = toPrint.Iterate();
for (iter = toPrint.Iterate(); !iter.HasFinished(); iter.Next())
{
if (printedKeyValuePair) {
result.Append(T(default.TCOMMA));
}
nextKey = Text(iter.GetKey());
nextValue = iter.Get();
if (nextKey == none) continue;
if (nextKey.class != class'Text') continue;
printedKey = DisplayText(nextKey);
printedValue = Print(nextValue);
result.Append(printedKey).Append(T(default.TCOLON));
printedKey.FreeSelf();
if (printedValue != none)
{
result.Append(printedValue);
printedValue.FreeSelf();
}
else {
result.Append(T(default.TNULL));
}
printedKeyValuePair = true;
}
iter.FreeSelf();
result.Append(T(default.TCLOSE_BRACE));
return result;
}
/**
* "Prints" given `HashTable` value, saving it as a JSON object in
* `MutableText`.
@ -1274,7 +978,7 @@ public final function MutableText PrintObject(AssociativeArray toPrint)
*
* It's items must either be equal to `none` or have one of the following
* classes: `BoolBox`, `BoolRef`, `IntBox`, `IntRef`, `FloatBox`, `FloatRef`,
* `Text`, `MutableText`, `DynamicArray`, `AssociativeArray`.
* `Text`, `MutableText`, `ArrayList`, `HashTable`.
* Otherwise items will be printed as "null" values.
* Also see `Print()` method.
*
@ -1343,10 +1047,10 @@ public final function MutableText PrintHashTable(HashTable toPrint)
* 3. Integer (`IntBox`/`IntRef`) and float (`FloatBox`/`FloatRef`) types
* are printed into JSON number value;
* 4. `Text` and `MutableText` are printed into JSON string value;
* 5. `DynamicArray` is printed into JSON array with `Print()` method
* 5. `ArrayList` is printed into JSON array with `Print()` method
* applied to each of it's items. If some of them have not printable
* types - "none" will be used as a fallback.
* 6. `AssociativeArray` is printed into JSON object with `Print()` method
* 6. `HashTable` is printed into JSON object with `Print()` method
* applied to each of it's items. Only items with `Text` keys are
* printed, the rest is omitted. If some of them have not printable
* types - "none" will be used as a fallback.
@ -1367,35 +1071,6 @@ public final function MutableText PrettyPrint(AcediaObject toPrint)
return result;
}
/**
* "Prints" given `DynamicArray` value, saving it as a JSON array in
* `MutableText`.
*
* "Prints" given `DynamicArray` in human-readable way, for minimal output
* use `PrintArray()` method.
*
* It's items must either be equal to `none` or have one of the following
* classes: `BoolBox`, `BoolRef`, `IntBox`, `IntRef`, `FloatBox`, `FloatRef`,
* `Text`, `MutableText`, `DynamicArray`, `AssociativeArray`.
* Otherwise items will be printed as "null" values.
* Also see `Print()` method.
*
* @param toPrint Array to "print" into `MutableText`.
* @return Text version of given `toPrint`, if it has one of the printable
* classes. Otherwise returns `none`.
* Note that `none` is considered printable and will produce "null".
*/
public final function MutableText PrettyPrintArray(DynamicArray toPrint)
{
local MutableText result;
local MutableText accumulatedIndent;
InitFormatting();
accumulatedIndent = _.text.Empty();
result = PrettyPrintArrayWithIndent(toPrint, accumulatedIndent);
accumulatedIndent.FreeSelf();
return result;
}
/**
* "Prints" given `ArrayList` value, saving it as a JSON array in
* `MutableText`.
@ -1405,7 +1080,7 @@ public final function MutableText PrettyPrintArray(DynamicArray toPrint)
*
* It's items must either be equal to `none` or have one of the following
* classes: `BoolBox`, `BoolRef`, `IntBox`, `IntRef`, `FloatBox`, `FloatRef`,
* `Text`, `MutableText`, `DynamicArray`, `AssociativeArray`.
* `Text`, `MutableText`, `ArrayList`, `HashTable`.
* Otherwise items will be printed as "null" values.
* Also see `Print()` method.
*
@ -1426,37 +1101,6 @@ public final function MutableText PrettyPrintArrayList(ArrayList toPrint)
return result;
}
/**
* "Prints" given `AssociativeArray` value, saving it as a JSON object in
* `MutableText`.
*
* "Prints" given `AssociativeArray` in a human readable way, for
* a minimal output use `PrintObject()` method.
*
* Only prints items recorded with `Text` key, the rest is omitted.
*
* It's items must either be equal to `none` or have one of the following
* classes: `BoolBox`, `BoolRef`, `IntBox`, `IntRef`, `FloatBox`, `FloatRef`,
* `Text`, `MutableText`, `DynamicArray`, `AssociativeArray`.
* Otherwise items will be printed as "null" values.
* Also see `Print()` method.
*
* @param toPrint Array to "print" into `MutableText`.
* @return Text version of given `toPrint`, if it has one of the printable
* classes. Otherwise returns `none`.
* Note that `none` is considered printable and will produce "null".
*/
public final function MutableText PrettyPrintObject(AssociativeArray toPrint)
{
local MutableText result;
local MutableText accumulatedIndent;
InitFormatting();
accumulatedIndent = _.text.Empty();
result = PrettyPrintObjectWithIndent(toPrint, accumulatedIndent);
accumulatedIndent.FreeSelf();
return result;
}
/**
* "Prints" given `HashTable` value, saving it as a JSON object in
* `MutableText`.
@ -1468,7 +1112,7 @@ public final function MutableText PrettyPrintObject(AssociativeArray toPrint)
*
* It's items must either be equal to `none` or have one of the following
* classes: `BoolBox`, `BoolRef`, `IntBox`, `IntRef`, `FloatBox`, `FloatRef`,
* `Text`, `MutableText`, `DynamicArray`, `AssociativeArray`.
* `Text`, `MutableText`, `ArrayList`, `HashTable`.
* Otherwise items will be printed as "null" values.
* Also see `Print()` method.
*
@ -1532,20 +1176,11 @@ private final function MutableText PrettyPrintWithIndent(
{
return DisplayText(BaseText(toPrint)).ChangeFormatting(jString);
}
if (toPrint.class == class'DynamicArray')
{
return PrettyPrintArrayWithIndent( DynamicArray(toPrint),
accumulatedIndent);
}
if (toPrint.class == class'ArrayList')
{
return PrettyPrintArrayListWithIndent( ArrayList(toPrint),
accumulatedIndent);
}
if (toPrint.class == class'AssociativeArray') {
return PrettyPrintObjectWithIndent( AssociativeArray(toPrint),
accumulatedIndent);
}
if (toPrint.class == class'HashTable') {
return PrettyPrintHashTableWithIndent( HashTable(toPrint),
accumulatedIndent);
@ -1553,48 +1188,6 @@ private final function MutableText PrettyPrintWithIndent(
return none;
}
// Does the actual job for `PrettyPrintArray()` method.
// Separated to hide `accumulatedIndent` parameter that is necessary for
// pretty printing.
// Assumes `InitFormatting()` was made and json formatting variables are
// initialized.
private final function MutableText PrettyPrintArrayWithIndent(
DynamicArray toPrint,
MutableText accumulatedIndent)
{
local int i, length;
local MutableText extendedIndent;
local MutableText result, printedItem;
if (toPrint == none) {
return none;
}
length = toPrint.GetLength();
extendedIndent = accumulatedIndent.MutableCopy().Append(T(TJSON_INDENT));
result = T(default.TOPEN_BRACKET).MutableCopy()
.ChangeFormatting(jArrayBraces);
for (i = 0; i < length; i += 1)
{
if (i > 0) {
result.Append(T(default.TCOMMA), jComma);
}
printedItem = PrettyPrintWithIndent(toPrint.GetItem(i), extendedIndent);
if (printedItem != none)
{
result.AppendLineBreak().Append(extendedIndent).Append(printedItem);
printedItem.FreeSelf();
}
else {
result.Append(T(default.TNULL), jNull);
}
}
if (i > 0) {
result.AppendLineBreak().Append(accumulatedIndent);
}
result.Append(T(default.TCLOSE_BRACKET), jArrayBraces);
extendedIndent.FreeSelf();
return result;
}
// Does the actual job for `PrettyPrintArray()` method.
// Separated to hide `accumulatedIndent` parameter that is necessary for
// pretty printing.
@ -1641,49 +1234,6 @@ private final function MutableText PrettyPrintArrayListWithIndent(
return result;
}
// Does the actual job for `PrettyPrintObject()` method.
// Separated to hide `accumulatedIndent` parameter that is necessary for
// pretty printing.
// Assumes `InitFormatting()` was made and json formatting variables are
// initialized.
private final function MutableText PrettyPrintObjectWithIndent(
AssociativeArray toPrint,
MutableText accumulatedIndent)
{
local bool printedKeyValuePair;
local CollectionIterator iter;
local Text nextKey;
local AcediaObject nextValue;
local MutableText extendedIndent;
local MutableText result;
if (toPrint == none) {
return none;
}
extendedIndent = accumulatedIndent.MutableCopy().Append(T(TJSON_INDENT));
result = T(default.TOPEN_BRACE).MutableCopy()
.ChangeFormatting(jObjectBraces);
iter = toPrint.Iterate();
for (iter = toPrint.Iterate(); !iter.HasFinished(); iter.Next())
{
if (printedKeyValuePair) {
result.Append(T(default.TCOMMA), jComma);
}
nextKey = Text(iter.GetKey());
nextValue = iter.Get();
if (nextKey == none) continue;
if (nextKey.class != class'Text') continue;
PrettyPrintKeyValue(result, nextKey, nextValue, extendedIndent);
printedKeyValuePair = true;
}
if (printedKeyValuePair) {
result.AppendLineBreak().Append(accumulatedIndent);
}
iter.FreeSelf();
result.Append(T(default.TCLOSE_BRACE), jObjectBraces);
extendedIndent.FreeSelf();
return result;
}
// Does the actual job for `PrettyPrintHashTable()` method.
// Separated to hide `accumulatedIndent` parameter that is necessary for
// pretty printing.

18
sources/Text/Tests/TEST_JSON.uc

@ -525,17 +525,17 @@ protected static function SubTest_ParseSimpleValueSuccess()
api = __().json;
Issue("`ParseWith()` fails to parse correct JSON values.");
parser = __().text.ParseString("false, 98.2, 42, \"hmmm\", null");
TEST_ExpectFalse(BoolBox(api.ParseWith(parser,, true)).Get());
TEST_ExpectFalse(BoolBox(api.ParseWith(parser)).Get());
parser.MatchS(",").Skip();
TEST_ExpectTrue(FloatBox(api.ParseWith(parser,, true)).Get() == 98.2);
TEST_ExpectTrue(FloatBox(api.ParseWith(parser)).Get() == 98.2);
parser.MatchS(",").Skip();
TEST_ExpectTrue(IntRef(api.ParseWith(parser, true, true)).Get() == 42);
TEST_ExpectTrue(IntRef(api.ParseWith(parser, true)).Get() == 42);
parser.MatchS(",").Skip();
TEST_ExpectTrue(
MutableText(api.ParseWith(parser, true, true)).ToString()
MutableText(api.ParseWith(parser, true)).ToString()
== "hmmm");
parser.MatchS(",").Skip();
TEST_ExpectNone(api.ParseWith(parser,, true));
TEST_ExpectNone(api.ParseWith(parser));
TEST_ExpectTrue(parser.Ok());
}
protected static function SubTest_ParseSimpleValueFailure()
@ -546,13 +546,13 @@ protected static function SubTest_ParseSimpleValueFailure()
Issue("`ParseWith()` does not correctly handle parsing invalid"
@ "JSON values.");
parser = __().text.ParseString("tru");
TEST_ExpectNone(api.ParseWith(parser,, true));
TEST_ExpectNone(api.ParseWith(parser));
TEST_ExpectFalse(parser.Ok());
parser = __().text.ParseString("");
TEST_ExpectNone(api.ParseWith(parser,, true));
TEST_ExpectNone(api.ParseWith(parser));
TEST_ExpectFalse(parser.Ok());
parser = __().text.ParseString("NUL");
TEST_ExpectNone(api.ParseWith(parser,, true));
TEST_ExpectNone(api.ParseWith(parser));
TEST_ExpectFalse(parser.Ok());
}
@ -624,7 +624,7 @@ protected static function SubTest_ParseComplex()
local HashTable root, mainObj, subObj, inner;
Issue("`ParseHashTableWith()` cannot handle complex values.");
parser = __().text.ParseString(default.complexJSONObject);
root = HashTable(__().json.ParseWith(parser,, true));
root = HashTable(__().json.ParseWith(parser));
TEST_ExpectTrue(root.GetLength() == 3);
TEST_ExpectTrue(FloatBox(root.GetItem(P("some_var"))).Get() == -7.32);
TEST_ExpectTrue( Text(root.GetItem(P("another_var"))).ToString()

Loading…
Cancel
Save