flex dictionary bug? - actionscript-3

I was testing a HashMap implementation in AS3 .
I have tried the following code:
var map:IMap = new HashMap();
map.put("a", "value A");
map.put("b", "value B");
map.put("c", "value C");
map.put("x", "value X");
map.put("y", "value Y");
map.put("z", "value Z");
Then I called the clear() method:
map.clear();
The size of the hashmap didn't become 0, but it was 1. The problem is that when the key is "y", it doesn't get removed.
The corresponding code is as follows:
protected var map:Dictionary = null;
public function HashMap(useWeakReferences:Boolean = true)
{
map = new Dictionary( useWeakReferences );
}
public function put(key:*, value:*) : void
{
map[key] = value;
}
public function remove(key:*) : void
{
map[ key ] = undefined;
delete map[ key ];
}
public function clear() : void
{
for ( var key:* in map )
{
remove( key );
}
}
If I call the clear() function again, the remaining key will be removed:
if (size() != 0)
{
clear();
}
Does anyone know what's the reason the y key doesn't get deleted?

I haven't had the time to look at the Dictionary implementation in tamarin (the VM for flash), but it seems that the Dictionary is beeing rehashed when a value is reaffected to the map by the line map[ key ] = undefined; in the remove function.
I.E. you begin an iteration with a set of key but then a rehashed occured and the keys are not valid anymore and the VM can't find the previous key and so in this case miss the y key.
What you can do is remove the map[key] = undefined; from the remove function and it should work. What it's strange is that the delete didn't produce any similar bug...
To show that the rehash has occurred see here a live example :
http://wonderfl.net/c/2PZT
You will see that a key is iterate twice when you assign a value to the dictionary.

Related

Mapping to a Variable and retrieving once they match

I have a list of check boxes that when selected the getEventName method should reurn the key that matches the label. For example if the label is "NEW", the key should be returned when map[key] = "new". These have been defined in the LABEL_EVENTTYPE function below. It is always returning an empty string and can't seem to figure out why.
public static const LABEL_EVENTTYPE_MAP:Object = {
"CANCEL":["cancelled","expired", "doneForDay"],
"NEW":["new"],
"TRADE":["trade"],
"AMEND":["replaced"],
}
private function getEventName(label:String):String{
var map:Object = ReplayConstants.LABEL_EVENTTYPE_MAP;
for each(var key:String in map){
if (map[key] == label){
return key;
}
}
return "";
}
Iterating through object properties requires for..in loop instead of for each.. in
for (var key:String in map){
if (map[key] == label){
return key;
}
}
Also take into account, that objects in your map are arrays, that's why your comparison map[key] == label will always return false.

Initialize Dictionary inline in AS3

I'm trying to find a way to initialize a Dictionary inline in ActionScript 3, like:
private var sampleDic:Dictionary = new Dictionary (
{ "zero", "Hello" },
{ "one", "World" }
);
I tried many ways, but none works. Anyone knows if it's possible, and how?
Thanks
No, you can't do it. You have to construct a dictionary and then add the values in a loop or individually.
If it's static you can do this with a block
private static var myDict:Dictionary = new Dictionary();
{
myDict["zero"] = "Hello";
myDict["one"] = "World";
}
If you REALLY want something like that, you can use a Dictionary factory:
public class DictFactory
{
public var dict:Dictionary;
public function create(obj:Object):Dictionary
{
dict = new Dictionary();
for (var key:String in obj) {
dict[key] = obj[key];
}
return dict;
}
}
Usage:
private var sampleDic:Dictionary = new DictFactory().create({ "zero":"Hello", "one": "World" });
The DictFactory.create expects a Object with key-values, that will be applied to the returned Dictionary, if you pass any other object (in AS3, any class is Object), results may be undesireable. :)
You can extend dictionary class and override default constructor with one that accepts initial key-value pears.
EDIT:
You can also use this dirty JS like solution :)
import flash.utils.Dictionary;
var dict : Dictionary = function ( d : Dictionary, p : Object ) : Dictionary { for ( var i : String in p ) { d[i] = p[i] }; return d; }(new Dictionary(), {
"zero": "Hello",
"one": "World"
})
trace( dict["zero"] );
If there is no specific reason to use a Dictionary you can do it with an object.
private var sample:Object = {
"zero": "Hello",
"one": "World"
};

Large Numbers as keys in Dictionaries (ActionScript 3)

I am using a Dictionary where the keys are Number objects, but I run into unexpected problems if the keys are large integers.
Here is some example code that inserts two key-value pairs in a Dictionary, one with a small key and one with a large one:
var dictionary:Dictionary = new Dictionary();
var smallNumberKey:Number = 1;
dictionary[smallNumberKey] = "some value";
var largeNumberKey:Number = 0x10000000;
dictionary[largeNumberKey] = "some value";
for (var key:Object in dictionary) {
trace("Key: " + key);
trace("Key type: " + flash.utils.getQualifiedClassName(key));
trace("Key as Number: " + (key as Number));
}
This gives the following output:
Key: 1
Key type: int
Key as Number: 1
Key: 268435456
Key type: String
Key as Number: null
None of the keys seems to be stored as a Number. Why is that? The first one is stored as an int, which can be converted to a Number. The second one however seems to be stored as a String, which makes no sense to me. What is going on here?
You can't use a Number as a key, because binary floating-point numbers do not lend themselves well to exact comparison, which is exactly what the Dictionary class does. (is "uses strict equality (===) for key comparison"). Read up on floating point numbers to understand why.
So when you assign a Number as a key to a Dictionary, the Flash engine must convert it to some other thing which can be represented exactly. String and int values can both be represented exactly by their contents, so Flash picks one to convert the supplied Number to. Doing some experimenting, it seems flash always converts an whole number value (even if supplied as a string) to an int if less than or equal to 0xFFFFFFF, and always converts to a String any whole number greater than that, probably due to some internal optimization in the fastest way to compare keys:
import flash.utils.Dictionary;
import flash.utils.getQualifiedClassName;
var dictionary:Dictionary = new Dictionary();
var stringKey:String = "123456789";
var intKey:int = 0xFFFFFFF;
var intKey2:int = intKey + 1;
dictionary[stringKey] = "Value 1";
dictionary[intKey] = "Value 2";
dictionary[intKey2] = "Value 3";
for (var key:Object in dictionary) {
trace( key + " [" + getQualifiedClassName(key) + "] = " + dictionary[key]);
}
Prints:
123456789 [int] = Value 1
268435455 [int] = Value 2
268435456 [String] = Value 3
From the Adobe doc:
The Dictionary class lets you create a dynamic collection of properties, which uses strict equality (===) for key comparison. When an object is used as a key, the object's identity is used to look up the object, and not the value returned from calling toString() on it.
"Using the object's identity" means using a reference to the object, a pointer to the place in memory where the actual object is stored. The problem when using primitive values as dictionary keys is that they are never passed by reference, and thus the values are not stored in the actual dictionary (i.e. they are not references to an object), but rather treated as "regular" dynamic properties, just like the ones you would expect from an Object. When you set
object[key] = "some value";
the key values are automatically converted either to int(just like the index of an Array) or String.
You can get around this, and use the actual performance benefits of Dictionary, by using a simple NumberKey wrapper class:
package
{
import flash.display.Sprite;
import flash.utils.Dictionary;
public class SimpleTest extends Sprite
{
var dictionary:Dictionary = new Dictionary();
public function SimpleTest() {
var smallNumberKey:NumberKey = new NumberKey(1);
dictionary[smallNumberKey] = "some value";
var largeNumberKey:NumberKey = new NumberKey(0x10000000);
dictionary[largeNumberKey] = "some value";
for(var key:Object in dictionary) {
trace( "Key: "+key );
trace( "Key type: "+flash.utils.getQualifiedClassName( key ) );
trace( "Key as Number: "+(key.numberValue) );
}
}
}
}
package {
public class NumberKey {
public var numberValue : Number;
public function NumberKey( n:Number ) {
numberValue = n;
}
}
}

Best way to write unit tests for dictionary

This is kinda related to my other question: flex dictionary bug?
The HashMap has a method getValues() which returns an array of values:
protected var map:Dictionary = null;
public function HashMap(useWeakReferences:Boolean = true)
{
map = new Dictionary( useWeakReferences );
}
public function getValues() : Array
{
var values:Array = [];
for (var key:* in map)
{
values.push( map[key] );
}
return values;
}
In my unit test class, I have:
private var map:IMap;
[Before]
public function setUp():void
{
map = new HashMap();
map.put("a", "value A");
map.put("b", "value B");
map.put("c", "value C");
map.put("x", "value X");
map.put("y", "value Y");
map.put("z", "value Z");
}
[Test]
public function testGetValues():void
{
assertEquals(map.getValues(), /*what should I put here*/);
}
the loop for (var key:* in map) iterates the keys of the dictionary map, but it seems its implementation does it in some random way. What is the best way to write a test since I don't know what the array returned by getValues method will contain?
I thought I could do it by calling the sort method, and compare the values, is there a better way to do it?
assertEquals(map.getValues().sort(), "value A,value B,value C,value X,value Y,value Z");
Yes. If I understand your problem properly, you are trying to see if the inserted values are there in the mapValues array you are getting back. You can use the indexOf function on the array and check something like
//start a loop to go through the arrays.
if( mapValues.indexOf("value A") >= 0 )
{
continue;
}
Hope this helps!

Trying to understand the AsyncToken in Flex/Actionscript

I am trying to understand the way the AsyncToken works in actionscript. How can I call a remote service and ensure that a specific parameter is available in the result or fault event functions? I think it is the async functionality I want to use.
The following code will hopefully explain what I am trying to do. Feel free to modify the code block as your explanation.
Thanks.
public function testSerivceCall(data:Object, callBackCommand:String):void
{
// Assume callBackCommand == "FOO";
// How can I pass in callBackCommand as a parameter to the result or fault events?
// How do I create an async token here?
var remoteObject:RemoteObject;
remoteObject = new RemoteObject();
remoteObject.destination = "zend";
remoteObject.source = "MyService";
remoteObject.endpoint = "http://example.com/service";
remoteObject.test.addEventListener(ResultEvent.RESULT, _handleTestResult);
remoteObject.test.addEventListener(FaultEvent.FAULT, _handleTestFault);
remoteObject.test(data);
}
private function _handleTestResult( event:ResultEvent ) : void
{
// How do I get the async token value?
// How can I get the value of callBackCommand in this code block?
if (callBackCommand == "FOO")
{
// do something related to "FOO"
}
else
{
// do something else with the result event
}
}
private function _handleTestFault( event:FaultEvent ) : void
{
// How do I get the async token value?
// How can I get the value of callBackCommand in this code block?
}
An edit to make this question more clear:
Assume I make the following method call somewhere in my code:
testSerivceCall(personObject, "LoginCommand");
How do I get access to the actual string "LoginCommand" inside the _handleTestResult function block?
The reason I want to do this is because I want to dynamically call back certain functions and hand off the result data to specific commands that I know ahead of time when I am making the service call.
I am just having a time grokking the AsyncToken syntax and functionality.
I did not even need closures. I added a class as below which I called externally.
The call was like this:
public class MyClass
{
...
var adminServerRO:AdminServerRO = new AdminServerRO();
adminServerRO.testSerivceCall("FOO",cptyId);
}
public class AdminServerRO
{
private function extResult( event:ResultEvent, token:Object ) : void
{
//the token is now accessed from the paremeter
var tmp:String = "in here";
}
private function extFault( event:FaultEvent ) : void
{
var tmp:String = "in here";
}
public function testSerivceCall(callBackCommand:String, cptyId:String):void
{
var remoteObject:RemoteObject = new RemoteObject();
remoteObject.destination = "adminServer";
var token:AsyncToken = remoteObject.getCounterpartyLimitMonitorItemNode(cptyId);
token.addResponder(new AsyncResponder(extResult,extFault,cptyId));
}
}
While the accepted answer will accomplish what the original submitter wants it does not actually answer the question which was asked. An AsyncToken is created as a result of a remote method call and is accessible from the ResultEvent. Since AsyncToken is a dynamic class you can add whatever property to it that you want. The code below should demonstrate this:
public function testSerivceCall(data:Object, callBackCommand:String):void
{
var remoteObject:RemoteObject;
remoteObject = new RemoteObject();
remoteObject.destination = "zend";
remoteObject.source = "MyService";
remoteObject.endpoint = "http://example.com/service";
remoteObject.test.addEventListener(ResultEvent.RESULT, _handleTestResult);
remoteObject.test.addEventListener(FaultEvent.FAULT, _handleTestFault);
var token:AsyncToken = remoteObject.test(data);
token.callBackCommand = callBackCommand;
}
private function _handleTestResult( event:ResultEvent ) : void
{
if (event.token.callBackCommand == "FOO")
{
// do something related to "FOO"
}
else
{
// do something else with the result event
}
}
private function _handleTestFault( event:FaultEvent ) : void
{
//event.token.callBackCommand should be populated here too
}
If you want to access the properties used during the remote call (parameters to the call and/or AsycToken), you can make use of closures. Just define the result event handler inside the calling method as a closure. It can then access any variable in the calling function.
public function testSerivceCall(data:Object, callBackCommand:String):void
{
var _handleTestResult:Function = function( event:ResultEvent ) : void
{
// token is visible here now
if (callBackCommand == "FOO")
{
// do something related to "FOO"
}
else
{
// do something else with the result event
}
}
var remoteObject:RemoteObject;
remoteObject = new RemoteObject();
remoteObject.destination = "zend";
remoteObject.source = "MyService";
remoteObject.endpoint = "http://example.com/service";
remoteObject.test.addEventListener(ResultEvent.RESULT, _handleTestResult);
remoteObject.test.addEventListener(FaultEvent.FAULT, _handleTestFault);
var token = remoteObject.test(data);
}
If I'm reading your question correctly, you're trying to figure out how to access the actual data returned by the ResultEvent ?
If so, assuming you've made the call correctly and you've gotten data back in a format you're expecting:
private function _handleTestResult( event:ResultEvent ) : void
{
// you get the result from the result property on the event object
// edit: assuming the class Person exists with a property called name
// which has the value "John"
var person : Person = event.result as Person;
if (person.name == "John")
{
Alert.show("John: " + person.name);
}
else
{
Alert.show("Not John: " + person.name);
}
}
private function _handleTestFault( event:FaultEvent ) : void
{
// Maybe you know the type of the returned fault
var expectedFault : Object = event.fault as MyPredefinedType
if (expectedFault.myPredefinedTypesPredefinedMethod() == "BAR")
{
// something here
}
}
The ResultEvent has a property called result which will hold an instance of the object returned by the result (it might be the output of an XML file if using a web service, or a serialized object if using AMF, for example). This is what you want to access. Similarly, FaultEvent has a fault property that returns the fault information.
Edit: Changed code in _handleTestResult() in response to Gordon Potter's comment.