I want to make a paramterized junit test using #RunWith(Parameterized.class) and
#Parameterized.Parameters
public static Collection<String[]> testdata() {
return Arrays.asList(new String[][] {
{ "inParam1", "inPAram2", "expectedOut1", "expectedOut2" }
});
}
The actual testdata shall be created by business people via Excel.
Is there an easy / generic way to get an Apache POI XSSFSheet to the prescribed Collection of String arrays?
If yes: can someone provide an example please ?
I found this question: Datadriven Testing in TestNG using Apache POI --- but I'd expect a kind of a 3-liner ;-)
It isn't quite a 3 liner, but assuming I've correctly understood your needs, you can do something like:
Sheet sheet = workbook.getSheetAt(0);
DataFormatter fmt = new DataFormatter();
List<List<String>> cellData = new ArrayList<List<String>>();
for (Row r : sheet) {
List<String> rd = new ArrayList<String>();
for (Cell c : r) {
rd.add(fmt.formatCellValue(c));
}
cellData.add(rd);
}
That will, for all defined rows and cells, of any time, generate you a list of list of strings, one list per row, one string per cell in that. You can switch from lists to arrays fairly easily
If you need more control over what cells/rows are/aren't included, including for empty ones, see the Iterating guide in the documentation
Related
Really looking for some practical advice and general guidance.
Below is the current scenario.
I have an excel document each row would be considered a test with inputs.
There would be hundreds if not thousands of rows.
Lets for example say Row1 would look like
Col1----------|Col2---------------|Col3
TestingUser|TestingSurname|1980/01/01
This needs to me mapped to a JSON object then sent / POST to an API end point.
I then need to assert the data that is coming back to make sure it’s the correct values.
The tools I have looked at is:
ReadyAPI
rest-assured.io
Would you recommend any other tool or framework for this type of testing.
If you have worked with something and you can provide an example that would be great.
I wouldn't be able to provide recommendation as i haven't worked on RestAssured.However below are few advantages of ReadyAPI:
Learning curve is shallow,any Tester will be able to build test
case without dependency on any programming language. ReadyAPI has
inbuild feature to read data from different datasources(DB, XML,
json,csv,excel etc.) and invoke REST endpoint by passing these
fields to Header,query and Json Body of the end point.
The response for each call can be dumped to a file using DataSink option for a
test step for each of the request calls made for the records from the file.
Tool is structured to easily build test Cases with multiple test
Steps. It more like drag and drop to build your test cases.Hierarchy
is Project -> Test Suite -> Test Case -> Test Step.
Easy integration with Jenkins CI/CD pipeline using testRunner with wide
variety of test reporting capabilities. Test reports are available as Allure,
jasper reports, junit Style reporting.
For more technical testers who need more control can use Groovy,javascript
language to build frameworks.
VirtServer and LoadUI are other tools by SmartBear that can be used to mock
services and run performance tests as desired.
I have an important comment to make here, if the file is huge(even 1000 lines) i have seen Ready API struggling as the tool does the heavylifting in the back. Hence would recommend to use groovy script utilizing Java API's for any file operations.
Ok so I have created a class using velocity as a json template engine.
I have created a test and within that test i have a normal java loop.
This will loop through the entire xls, map values and and post to the API.
This is all working as expected.
The problem is the runner displays
Default Suite
Total tests run: 1, Passes: 0,
However the loop does run x amount of times.
How can i update it the when i execute the test its shows total tests run 10 or the same amount that is from the loop.
Hope this makes sense
#Test
public void generatePostData() throws IOException {
Workbook wb = WorkbookFactory.create(new File("data\\sc1.xlsx"));
Sheet sheet = wb.getSheetAt(0);
for (int i = 1; i < 10; i++) {
//Get Excel Data
Cell testNumber = sheet.getRow(i).getCell(1);
System.out.println(testNumber.getNumericCellValue());
//Velocity
VelocityEngine ve = new VelocityEngine();
ve.init();
//get the template
Template t = ve.getTemplate("post.json");
//create context and add data
VelocityContext context = new VelocityContext();
//map data
context.put("tpltestNumber", testNumber);
//render to stringWriter
StringWriter writer = new StringWriter();
t.merge(context, writer);
baseURI = "someURL";
Response response =
given()
.contentType("application/json")
.body(String.valueOf(writer))
.when()
.post()
.then()
.assertThat()
.statusCode(200)
.extract()
.response();
}
}
This is the answer to the question asked in the answers sesction by the reporter of the main question. (How to get the executed excel row count, for total executed test case count)
For that you have to pass the data using a method with DataProvider annotation.
TestNG documentation
DataProvider in TestNG
#DataProvider(name = "dp")
private Object[][] dataProvider() {
Workbook wb;
Sheet sheet = null;
Object[][] excelRowArray = new Object[10][]; //this 10 the row count in the excel file
try {
wb = WorkbookFactory.create(new File("data\\sc1.xlsx"));
sheet = wb.getSheetAt(0);
} catch (IOException e) {
e.printStackTrace();
}
for (int i = 1; i < 10; i++) {// Here 10 is the row count in the excel sheet
//Get Excel Data row by row
Cell testNumber = sheet.getRow(i).getCell(1);
System.out.println(testNumber.getNumericCellValue());
// Create a object array with the values taken from a singe excel row
Object[] excelRow = new Object[]{testNumber};
// Add the created object array to the 'excelRowArray'
excelRowArray[i - 1] = excelRow;
}
return excelRowArray;
}
#Test(dataProvider = "dp")
public void generatePostData(Object[] excelRow) {
// Here single excelRow will be passed each time.
// And this will run till all object[] in excelRowArray are finished.
// Total tests executed will be the number of 'excelRow' object arrays in
// excelRowArray. (Or excel row count in the sheet)
//Velocity
VelocityEngine ve = new VelocityEngine();
ve.init();
//get the template
Template t = ve.getTemplate("post.json");
//create context and add data
VelocityContext context = new VelocityContext();
//map data
context.put("tpltestNumber", excelRow); // Here excelRow is used as the value
//render to stringWriter
StringWriter writer = new StringWriter();
t.merge(context, writer);
String baseURI = "someURL";
Response response =
given()
.contentType("application/json")
.body(String.valueOf(writer))
.when()
.post()
.then()
.assertThat()
.statusCode(200)
.extract()
.response();
}
Is it possible to do something similar to this in Haxe?
private var _bindingsFiltered:Vector<String>;
_bindingsFiltered = new Vector<String>();
_controller_touched_binding.action = "meta_start";
What I would like to be able to do:
_bindingsFiltered[_controller_touched_binding.action] = "BUTTON_13";
trace(_bindingsFiltered["meta_start"]); //result: "BUTTON_13"
I want to be able to override a specific index too (still accessed via a string), with a new value, rather than keep pushing new content to the end of the vector. I have been using 'openfl.utils.Object' to cheat for now but I am looking for a more reliable approach for the long run.
Is there a way to do this in Haxe?
If not, what are my options?
I would also be interested in a solution for this in AS3, if there is one (avoiding the Array class).
My goal is to find a method that I can use in both languages seamlessly (next-to-none, differences).
Vector's cannot be indexed by string in Haxe. A vector is an array with a fixed size. This is the Haxe manual on that subject.
Instead of vectors, you can use a Map.
class Test {
private var vector:Map<String, String> = new Map<String, String>();
public function new() {
var str = 'haxe';
vector[str] = "is great";
trace(vector[str]);
}
static function main() {
new Test();
}
}
https://try.haxe.org/#F74Ba
I think you could do this using flash.utils.Dictionary:
ActionScript
import flash.utils.Dictionary;
...
var _bindingsFiltered:Dictionary = new Dictionary ();
_bindingsFiltered[_controller_touched_binding.action] = "BUTTON_13";
trace(_bindingsFiltered["meta_start"]); //result: "BUTTON_13"
Haxe
import openfl.utils.Dictionary;
...
var _bindingsFiltered = new Dictionary<String, String> ();
_bindingsFiltered[_controller_touched_binding.action] = "BUTTON_13";
trace(_bindingsFiltered["meta_start"]); //result: "BUTTON_13"
First, do you really want an array / vector / list, or do you really want a hashmap of key / value pairs? How are you using the collection? Why do you want String keys? And related, is this mostly about access symantics (you want to type it this way), or are the runtime reasons you'd want to use strings (serialization / etc)?
From what you've described, it sounds like what you really want is an Object like the ones in AS3/JS/ECMAScript, with square-bracket access symantics -- obj[key]
Yes, you can do that in Haxe. The openfl.utils.Object class is a helper to do exactly this, using Dynamic objects and reflection. It should compile to exactly what you want on all Haxe targets.
In any case, if you'd like to feel like you're not bound to OpenFL, no problem. Copy the openfl/utils/Object.hx file and place it anywhere you like in your project's class path (and update the package statement).
There's nothing particularly OpenFL-ish about that code. It's pure Haxe code with no dependencies. It provides array access with String keys, as well as toString, toLocaleString, propertyIsEnumerable, iterator, isPrototypeOf, and hasOwnProperty functions (which ECMA-folk are used to.)
The transition from AS3/JS to Haxe is a little weird, especially when it comes to dynamic objects, and I've been meaning to blog more about it. ;) Good luck!
ETA: In truth, you probably want to get away from Dynamic/Reflection, and embrace a more type-strict approach. AS3/JS devs don't understand this at first, but it is where the benefits of Haxe come from. If you don't then your Haxe experience is likely to be unplesant.
Short answer: yes, you can.
abstract MyVector<T>(Vector<T>) {
public function new(l:Int) this = new Vector<T>(l);
#:op([]) public function set<K:T>(s:String, v:K) {
switch (s) {
case "FIRST": this[0] = v;
case "SECOND": this[1] = v;
default: return;
}
}
#:op([]) public function get(s:String) {
switch (s) {
case "FIRST": return this[0];
case "SECOND": return this[1];
default: return cast 0;
}
}
}
var mv = new MyVector<String>(2);
mv["SECOND"] = "Second";
trace(mv["SECOND"]); // outputs Second
You can inline get and set methods if you want.
I want to create a JSON file for use as part of a simple web prototyping exercise. LinqPAD is perfect for accessing the data from my DB in just the shape I need, however I cannot get it out as JSON very easily.
I don't really care what the schema is, because I can adapt my JavaScript to work with whatever is returned.
Is this possible?
A more fluent solution is to add the following methods to the "My Extensions" File in Linqpad:
public static String DumpJson<T>(this T obj)
{
return
obj
.ToJson()
.Dump();
}
public static String ToJson<T>(this T obj)
{
return
new System.Web.Script.Serialization.JavaScriptSerializer()
.Serialize(obj);
}
Then you can use them like this in any query you like:
Enumerable.Range(1, 10)
.Select(i =>
new
{
Index = i,
IndexTimesTen = i * 10,
})
.DumpJson();
I added "ToJson" separately so it can be used in with "Expessions".
This is not directly supported, and I have opened a feature request here. Vote for it if you would also find this useful.
A workaround for now is to do the following:
Set the language to C# Statement(s)
Add an assembly reference (press F4) to System.Web.Extensions.dll
In the same dialog, add a namespace import to System.Web.Script.Serialization
Use code like the following to dump out your query as JSON
new JavaScriptSerializer().Serialize(query).Dump();
There's a solution with Json.NET since it does indented formatting, and renders Json dates properly. Add Json.NET from NuGet, and refer to Newtonsoft.Json.dll to your “My Extensions” query and as well the following code :
public static object DumpJson(this object value, string description = null)
{
return GetJson(value).Dump(description);
}
private static object GetJson(object value)
{
object dump = value;
var strValue = value as string;
if (strValue != null)
{
var obj = JsonConvert.DeserializeObject(strValue);
dump = JsonConvert.SerializeObject(obj, Newtonsoft.Json.Formatting.Indented);
}
else
{
dump = JsonConvert.SerializeObject(value, Newtonsoft.Json.Formatting.Indented);
}
return dump;
}
Use .DumpJson() as .Dump() to render the result. It's possible to override more .DumpJson() with different signatures if necessary.
As of version 4.47, LINQPad has the ability to export JSON built in. Combined with the new lprun.exe utility, it can also satisfy your needs.
http://www.linqpad.net/lprun.aspx
I am redirecting rows from a flat file source to a flat file destination. The default metadata in the redirected rows are:
The original flat file source row
The ErrorCode
The ErrorColumn
What I get in the output flat file is the source row (nice) and the error code (not nice, ex. -1071628249) and the error column (not nice since it's the internal ID of the column).
How can I transform the rows to output the error message (e.g. "The data was truncated.") and the column name as defined in the flat file source?
In other words, instead of ...,-1071607675,10 I'd like to see:
...,The data was truncated,Firstname
or alternatively (if the previous is not possible);
...,DTS_E_FLATFILESOURCEADAPTERSTATIC_TRUNCATED,Firstname.
Error message list is in the following location:
MSDN, Integration Services Error and Message Reference
https://learn.microsoft.com/en-us/sql/integration-services/integration-services-error-and-message-reference?view=sql-server-ver15
And column Id Number can be found in SSIS's Data Flow Task:
select the task component that generates the error, Advanced Editor, 'Input and Output Properties' tab, External Columns Properties.
Part of the question (adding the error description) can be achieved with a script component. This is described in Enhancing an Error Output with the Script Component.
It seems that the Dougbert blog has a solution to adding the column name, but it's far from simple. I'm amazed this is so difficult to do in SSIS; you'd think it was a basic need to know the name of the source and column.
There is a far simpler answer. Simply redirect the error output to a new destination file (CSV or whatever) and then enable a DataViewer on the error output....
It can be achieved using script component as transformation, Redirect error output to the script component and follow the steps to achieve what you are looking for.
(1) Open script component ,
Input Columns select
ErrorColumn
ErrorCode
Input and Output add Output columns
ErrorDescription (DT_STRING 500)
ErrorColumnDescription (DT_STRING 100)
(2) Edit Script
Paste the following code
using System;
using System.Data;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;
#endregion
/// <summary>
/// This is the class to which to add your code. Do not change the name, attributes, or parent
/// of this class.
/// </summary>
[Microsoft.SqlServer.Dts.Pipeline.SSISScriptComponentEntryPointAttribute]
public class ScriptMain : UserComponent
{
public override void Input0_ProcessInputRow(Input0Buffer Row)
{
var component130 = this.ComponentMetaData as IDTSComponentMetaData130;
if (component130 != null)
{
Row.ErrorDescription = component130.GetErrorDescription(Row.ErrorCode);
Row.ErrorColumnDescription = component130.GetIdentificationStringByID(Row.ErrorColumn);
}
}
Pragmatic Works appears to have an Error Output Description Transform that is a part of the Community Edition (Free) of the Product they call "Task Factory".
The Error Output Description Transform provides the user with a User Interface that can retrieve valuable information such as the ErrorCode, ErrorColumn, ErrorDescription, ComponentName (that generated the error), ColumnName (if known), ColumnType, and ColumnLength.
It also allows you to pass through any input columns to the Error Output. To be honest it is quite handy and has saved me hours of time troubleshooting my SSIS Packages.
//column error description
Row.ErrorDescription = this.ComponentMetaData.GetErrorDescription(Row.ErrorCode);
//we are getting column name with some extra information
String rawColumnName = this.ComponentMetaData.GetIdentificationStringByLineageID(Row.ErrorColumn);
//extracting positions of brackets
int bracketPos = rawColumnName.LastIndexOf('[')+1;
int lastBracketPos = rawColumnName.LastIndexOf(']');
//extracting column name from the raw column name
Row.ErrorColName = rawColumnName.Substring(bracketPos, (lastBracketPos - bracketPos));
Using SS2016 and above, it is easy:
https://www.mssqltips.com/sqlservertip/4066/retrieve-the-column-causing-an-error-in-sql-server-integration-services/
public override void Input0_ProcessInputRow(Input0Buffer Row)
{
Row.ErrorDescription = this.ComponentMetaData.GetErrorDescription(Row.ErrorCode);
IDTSComponentMetaData130 componentMetaData = this.ComponentMetaData as IDTSComponentMetaData130;
Row.ErrorColumnName = componentMetaData.GetIdentificationStringByID(Row.ErrorColumn);
}
For anyone using SQL Server versions before SS2016, here are a couple of reference links for a way to get the Column name:
http://www.andrewleesmith.co.uk/2017/02/24/finding-the-column-name-of-an-ssis-error-output-error-column-id/
which is based on:
http://toddmcdermid.blogspot.com/2016/04/finding-column-name-for-errorcolumn.html
I appreciate we aren't supposed to just post links, but this solution is quite convoluted, and I've tried to summarise by pulling info from both Todd and Andrew's blog posts and recreating them here. (thank you to both if you ever read this!)
From Todd's page:
Go to the "Inputs and Outputs" page, and select the "Output 0" node.
Change the "SynchronousInputID" property to "None". (This changes
the script from synchronous to asynchronous.)
On the same page, open the "Output 0" node and select the "Output
Columns" folder. Press the "Add Column" button. Change the "Name"
property of this new column to "LineageID".
Press the "Add Column" button again, and change the "DataType"
property to "Unicode string [DT_WSTR]", and change the "Name"
property to "ColumnName".
Go to the "Script" page, and press the "Edit Script" button. Copy
and paste this code into the ScriptMain class (you can delete all
other method stubs):
public override void CreateNewOutputRows() {
IDTSInput100 input = this.ComponentMetaData.InputCollection[0];
if (input != null)
{
IDTSVirtualInput100 vInput = input.GetVirtualInput();
if (vInput != null)
{
foreach (IDTSVirtualInputColumn100 vInputColumn in vInput.VirtualInputColumnCollection)
{
Output0Buffer.AddRow();
Output0Buffer.LineageID = vInputColumn.LineageID;
Output0Buffer.ColumnName = vInputColumn.Name;
}
}
} }
Feel free to attach a dummy output to that script, with a data viewer,
and see what you get. From here, it's "standard engineering" for you
ETL gurus. Simply merge join the error output of the failing
component with this metadata, and you'll be able to transform the
ErrorColumn number into a meaningful column name.
But for those of you that do want to understand what the above script
is doing:
It's getting the "first" (and only) input attached to the script
component.
It's getting the virtual input related to the input. The "input" is
what the script can actually "see" on the input - and since we
didn't mark any columns as being "ReadOnly" or "ReadWrite"... that
means the input has NO columns. However, the "virtual input" has
the complete list of every column that exists, whether or not we've
said we're "using" it.
We then loop over all of the "virtual columns" on this virtual
input, and for each one...
Get the LineageID and column name, and push them out as a new row on
our asynchronous script.
The image and text from Andrew's page helps explain it in a bit more detail:
This map is then merge-joined with the ErrorColumn lineage ID(s)
coming down the error path, so that the error information can be
appended with the column name(s) from the map. I included a second
script component that looks up the error description from the error
code, so the error table rows that we see above contain both column
names and error descriptions.
The remaining component that needs explaining is the conditional split
– this exists just to provide metadata to the script component that
creates the map. I created an expression (1 == 0) that always
evaluates to false for the “No Rows – Metadata Only” path, so no rows
ever travel down it.
Whilst this solution does require the insertion of some additional
plumbing within the data flow, we get extremely valuable information
logged when errors do occur. So especially when the data flow is
running unattended in Production – when we don’t have the tools &
techniques available at design time to figure out what’s going wrong –
the logging that results gives us much more precise information about
what went wrong and why, compared to simply giving us the failed data
and leaving us to figure out why it was rejected.
Here is a solution that
Works at package runtime (not pre-populating)
Is automated through a Script Task and Component
Doesn't involve installing new assemblies or custom components
Is nicely BIML compatible
Check out the full solution here.
Here is the short version.
Create 2 Object variables, execsObj and lineageIds
Create Script Task in Control flow, give it ReadWrite access to both variables
Insert the following code into your Script Task
Dictionary<int, string> lineageIds = null;
public void Main()
{
// Grab the executables so we have to something to iterate over, and initialize our lineageIDs list
// Why the executables? Well, SSIS won't let us store a reference to the Package itself...
Dts.Variables["User::execsObj"].Value = ((Package)Dts.Variables["User::execsObj"].Parent).Executables;
Dts.Variables["User::lineageIds"].Value = new Dictionary<int, string>();
lineageIds = (Dictionary<int, string>)Dts.Variables["User::lineageIds"].Value;
Executables execs = (Executables)Dts.Variables["User::execsObj"].Value;
ReadExecutables(execs);
Dts.TaskResult = (int)ScriptResults.Success;
}
private void ReadExecutables(Executables executables)
{
foreach (Executable pkgExecutable in executables)
{
if (object.ReferenceEquals(pkgExecutable.GetType(), typeof(Microsoft.SqlServer.Dts.Runtime.TaskHost)))
{
TaskHost pkgExecTaskHost = (TaskHost)pkgExecutable;
if (pkgExecTaskHost.CreationName.StartsWith("SSIS.Pipeline"))
{
ProcessDataFlowTask(pkgExecTaskHost);
}
}
else if (object.ReferenceEquals(pkgExecutable.GetType(), typeof(Microsoft.SqlServer.Dts.Runtime.ForEachLoop)))
{
// Recurse into FELCs
ReadExecutables(((ForEachLoop)pkgExecutable).Executables);
}
}
}
private void ProcessDataFlowTask(TaskHost currentDataFlowTask)
{
MainPipe currentDataFlow = (MainPipe)currentDataFlowTask.InnerObject;
foreach (IDTSComponentMetaData100 currentComponent in currentDataFlow.ComponentMetaDataCollection)
{
// Get the inputs in the component.
foreach (IDTSInput100 currentInput in currentComponent.InputCollection)
foreach (IDTSInputColumn100 currentInputColumn in currentInput.InputColumnCollection)
lineageIds.Add(currentInputColumn.ID, currentInputColumn.Name);
// Get the outputs in the component.
foreach (IDTSOutput100 currentOutput in currentComponent.OutputCollection)
foreach (IDTSOutputColumn100 currentoutputColumn in currentOutput.OutputColumnCollection)
lineageIds.Add(currentoutputColumn.ID, currentoutputColumn.Name);
}
}
4. Create Script Component in Dataflow with ReadOnly access to lineageIds and the following code.
public override void Input0_ProcessInputRow(Input0Buffer Row)
{
Dictionary<int, string> lineageIds = (Dictionary<int, string>)Variables.lineageIds;
int? colNum = Row.ErrorColumn;
if (colNum.HasValue && (lineageIds != null))
{
if (lineageIds.ContainsKey(colNum.Value))
Row.ErrorColumnName = lineageIds[colNum.Value];
else
Row.ErrorColumnName = "Row error";
}
Row.ErrorDescription = this.ComponentMetaData.GetErrorDescription(Row.ErrorCode);
}
I connected to the SSIS Error message ref webpage with excel using the get data from web on the data tab. Saved the table in a sheet in excel, then imported it to SQL Server. Then joined it to my error rows table on the decimal code to get the description, and then created a view out of it. Thought this might be useful for those that don't want to mess with the script task.
I was pulling my hair for last couple of days. I did everything that is mentioned everywhere but the package/c# was throwing an error. Finally when I decided to give up, I found that my ErrorColumn was coming up as 0 (Zero) because the error was in entire row due to PK/FK constraint violation.
So I modified the script as below:
public override void Input0_ProcessInputRow(Input0Buffer Row)
{
Row.ErrorDescription = this.ComponentMetaData.GetErrorDescription(Row.ErrorCode);
var componentMetaData130 = this.ComponentMetaData as IDTSComponentMetaData130;
if (componentMetaData130 != null)
{
if (Row.ErrorColumn == 0) //Checking if the Column is zero
{
Row.ColumnName = "Entire Row. Check PK FK constraints"; //Hardcoded error message
}
else
{
Row.ColumnName = componentMetaData130.GetIdentificationStringByID(Row.ErrorColumn);
}
}
}
For usual process: https://learn.microsoft.com/en-us/sql/integration-services/extending-packages-scripting-data-flow-script-component-examples/enhancing-an-error-output-with-the-script-component?view=sql-server-2017
Why ErrorColumn value is Zero?: SSIS 2016 - ErrorColumn is 0 (zero)
Hope that helps !!!
I've been spending some of my spare time working a set of collections for ActionScript 3 but I've hit a pretty serious roadblock thanks for the way ActionScript 3 handles equality checks inside Dictionary Objects.
When you compare a key in a dictionary, ActionScript uses the === operator to perform the comparison, this has a bit of a nasty side effect whereby only references to the same instance will resolve true and not objects of equality. Here's what I mean:
const jonny1 : Person = new Person("jonny", 26);
const jonny2 : Person = new Person("jonny", 26);
const table : Dictionary = new Dictionary();
table[jonny1] = "That's me";
trace(table[jonny1]) // traces: "That's me"
trace(table[jonny2]) // traces: undefined.
The way I am attempting to combat this is to provide an Equalizer interface which looks like this:
public interface Equalizer
{
function equals(object : Object) : Boolean;
}
This allows to to perform an instanceOf-esq. check whenever I need to perform an equality operation inside my collections (falling back on the === operator when the object doesn't implement Equalizer); however, this doesn't get around the fact that my underlying datastructure (the Dictionary Object) has no knowledge of this.
The way I am currently working around the issue is by iterating through all the keys in the dictionary and performing the equality check whenever I perform a containsKey() or get() operation - however, this pretty much defeats the entire point of a hashmap (cheap lookup operations).
If I am unable to continue using a Dictionary instance as the backing for map, how would I go about creating the hashes for unique object instances passed in as keys so I can still maintain equality?
How about you compute a hash code for your objects when you insert them, and then look them up by the hash code in your backing dictionary? The hashcode should compare === just fine. Of course, that would require you to have a Hashable interface for your object types instead of your Equalizer interface, so it isn't much less work than you are already doing, but you do get the cheap lookups.
How about rather doing this:
public interface Hashable {
function hash():String;
}
personally, I ask myself, why you want to do this ... hashing objects to obtain keys makes little sense if they are mutable ...
also, you might consider using a different approach, as for example this factory:
package {
public class Person {
/**
* don't use this!
* #private
*/
public function Person(name:String, age:int) {
if (!instantiationAllowed)
throw new Error("use Person.getPerson instead of constructor");
//...
}
private static var instantiationAllowed:Boolean = false;
private static var map:Object = {};
private static function create(name:String, age:int):Person {
instantiationAllowed = true;
var ret:Person = new Person(name, age);
instantiationAllowed = false;
}
public static function getPerson(name:String, age:int):Person {
var ageMap:Array = map[name];
if (ageMap == null) {
map[name] = ageMap = [];
return ageMap[age] = Person.create(name, age);
}
if (ageMap.hasOwnProperty(age))
return ageMap[age];
return ageMap[age] = Person.create(name, age);
}
}
}
it ensures, there's only one person with a given name and age (if that makes any sense) ...
Old thread I know, but still worth posting.
const jonny1 : Person = new Person("jonny", 26); const jonny2 : Person = new Person("jonny", 26);
is creating two completely different objects that will not compare using ==, guess I don't see why it's any more of a road block because of as3
The problem with AS3/JavaScript/EcmaScript is not that they create two different, equivalent objects.
The problem is that they cannot equate those two equivalent objects--only identity works, since there is no equals or hashCode methods that can be overriden with class-specific comparison logic.
For Map implementations such as dynamic Object or Dictionary, this means that you have to either use Strings or references as keys: you cannot recover objects from a map using different but equivalent objects.
To work around that problem, people either resort to strict toString implementations (for Object maps) which is undesirable, or to instance control for Dictionaries, as in #back2dos example, which introduces different problems (Also, note that #back2dos solution does not really guarantee unique Person instances since there is a time window during which asynchronous threads will be allowed to instantiate new Persons).
#A.Levy's solution is good except that in general, hashCodes are not strictly required to issue unique values (they are meant to map entries to buckets allowing for fast lookups, wherein fine-grained differentiation is done through equals method).
You need both a hashCode and an equals method, e.g.
public interface IEquable
{
function equals(object : Object) : Boolean;
function hash():String;
}
In any programming language,
const jonny1 : Person = new Person("jonny", 26);
const jonny2 : Person = new Person("jonny", 26);
is creating two completely different objects that will not compare using ==, guess I don't see why it's any more of a road block because of as3