28
OO STYLE - LINQ TO OBJECTS IMPLEMENTATION PROPOSAL ActionScript3 collection query

ActionScript3 collection query API proposal

Embed Size (px)

DESCRIPTION

ActionScript3 collection query API proposal. Collection Querying API similar to LINQ 2 Objects (OO style).

Citation preview

Page 1: ActionScript3 collection query API proposal

OO STYLE - L INQ TO OBJECTS IMPLEMENTATION PROPOSAL

ActionScript3 collection query

Page 2: ActionScript3 collection query API proposal

Introduction

Draft implementation, just an idea, would be great if we have something like this in ActionScript (but more robust, complex, optimized of course)

Purpose – collection sorting, filtering, grouping wrapper

OO-Style LINQ to Objects lib for flex

Page 3: ActionScript3 collection query API proposal

Usage examples – first, last, any, itemAt

//get first with title containing case insensitive string “Voislav Seselj”var query:IQuery = new CollectionQuery(_news);var firstItem:NewsFeed = query.where(newConditions().contains("title", “Voislav Seselj", false)).first() as NewsFeed;

//last item published in 2013 or 2014var lastItem:NewsFeed = query.where(newConditions().eq("year", 2014).OR.eq("year", 2013)).last() as NewsFeed;

var usersQuery:IQuery = new CollectionQuery();if(usersQuery.from(users).where(new Conditions().eq("name", "Peter Pan")).any()){//excellent, there is a user (or users) with name "Peter Pan“}//get third adault from the end of the collection ordered by name ascending and lastname //descending.var user:User = new CollectionQuery(users).where(new Conditions().gte("years", 18)).orderBy("name").orderBy("lastname", false).itemAt(2) as User;

Page 4: ActionScript3 collection query API proposal

Usage examples – where, select, limit

//get users for given conditions ordered by company and then by years propertyvar users:IList = usersQuery.where(new Conditions().lt("years", 18).gt("years", 5).OR.eq("company", "Company 1")).orderBy("company“).orderBy("years").execute() as IList;//select collection containing anonymous objects with properties given in select clause //extracted from user objects, for given conditions, ordered by salary with ascending order //and compare as numerics. //Limit to maximum 10 resultsvar users:IList = new CollectionQuery().select("name, lastname, company, salary").from(users).where(new Conditions().gte("salary", 40000).lte("salary", 45000)) //AND by default.orderBy("salary", true, true).limit(10).execute() as IList;

Page 5: ActionScript3 collection query API proposal

Usage examples – nested complex objects

//select streets from complex member “address” for each user in users collectionvar streets:IList = usersQuery.select("address.street").from(users).execute() as IList;//select anonymous objects containing user’s name, lastname, address->number, address-//>street, address->uniqueId->value, for users having address number greater then 400 in //Kragujevac city, or having address number lower then 400 and live in Belgrade city.//Order list by city ascending and by address number descending comparing address.number //property as numbers.var users:IList = usersQuery.select("name, lastname, address.number, address.street, address.uniqueId.value").from(users).where(new Conditions()

.gt("address.number", 400).eq("address.city", "Kragujevac").OR.lt("address.number", 400).eq("address.city", "Belgrade"))

.orderBy("address.city")

.orderBy("address.number", false, true)

.execute() as IList;

Page 6: ActionScript3 collection query API proposal

Usage examples – groupBy

//group users named Marija or Slavisa by city, ordere by name and then by lastnamevar query:IQuery = new CollectionQuery();var groupResults:IQueryGroupResult = query.select("id, name, lastname, address.street") //get anonimous objects.from(users).where(new Conditions()

.eq("name", "Slaviša").OR

.eq("name“,"Marija").AND.contains("lastname", "ić")).orderBy("name").orderBy("lastname", false) //order by name , lastname descending.groupBy("address.city") //group by city.execute() as IQueryGroupResult; //get grouped results

for each(var city:Object in groupResults.keys) //iterate through key collection {printUsersFromCity(key, groupResults[key] as IList); //and print collection for each key} if(groupResults.keys.contains("Kragujevac")) //test if there is a key{ var kragujevcani:IList = groupResults["Kragujevac"] as IList; //print the collection}

Page 7: ActionScript3 collection query API proposal

API

IConditionIQueryIQueryGroupResultIQueryItemResultCollectionQueryConditionsPredicateQueryGroupResultQueryResultItem

Page 8: ActionScript3 collection query API proposal

IQuery

/** * Query interface * @author Slavisa */public interface IQuery{

function select(value:String = null):IQuery; //select columnsfunction from(coll:IList):IQuery; //sets collection to be queriedfunction where(conditions:ICondition):IQuery; //sets condition chainfunction groupBy(property:String):IQuery; //sets group keysfunction orderBy(property:String, ascending:Boolean = true, numeric:Boolean = false):IQuery; //order by given property

function limit(count:int = 100):IQuery; //limits result’s item countfunction first():Object; //returns first or null function last():Object; //returns last or nullfunction itemAt(index:int):Object; //returns item by the given index, or nullfunction execute():Object; //actual execution, returns IList or IQueryGroupResult objectfunction any():Boolean; //determines if at least one result exists

}

Page 9: ActionScript3 collection query API proposal

ICondition

/** * Condition interface * @author Slavisa */public interface ICondition{

function eq(prop:String, value:Object):ICondition; //equalfunction diff(prop:String, value:Object):ICondition; //not equalfunction contains(prop:String, value:String, caseSensitive:Boolean = true):ICondition;

//containsfunction lt(prop:String, value:Object):ICondition; //lower thanfunction lte(prop:String, value:Object):ICondition; //lower than or equalfunction gt(prop:String, value:Object):ICondition; //greater thanfunction gte(prop:String, value:Object):ICondition; //greater than or equal

function get OR():ICondition; //OR – new condition groupfunction get AND():ICondition; //AND – new condition group

function get root():ICondition;//get root condition}

Page 10: ActionScript3 collection query API proposal

IQueryItemResult, QueryResultItem, IQueryGroupResult

/** * Query Item result interface * @author Slavisa */public interface IQueryItemResult{ }

/** * Query Item result dynamic class * Stores custom selection (anonimous object) * @author Slavisa */public dynamic class QueryResultItem implements IQueryItemResult{}/**

* Query Grouped results * @author pokimsla */public interface IQueryGroupResult{

function get keys():ArrayCollection; //gets keys collectionfunction get length():int; //gets number of groups

}

Page 11: ActionScript3 collection query API proposal

QueryGroupResult

public dynamic class QueryGroupResult extends Dictionary implements IQueryGroupResult{ //Gets array collection of group keys

public function get keys():ArrayCollection {

var keys:ArrayCollection = new ArrayCollection();for(var key:Object in this){

keys.addItem(key);}return keys;

}

//number of keys public function get length():int{

return keys.length;}

}

Page 12: ActionScript3 collection query API proposal

Predicate

/** * Predicate class * @author Slavisa * Used for combining multiple conditions joined with AND operator */[Bindable]public class Predicate implements ICondition{

/* Predicate types */public static const EQ:String = "equal";public static const DIFF:String = "different";public static const CONTAINS:String = "contains";public static const LT:String = "less";public static const GT:String = "greater";public static const GTE:String = "greater or equal";public static const LTE:String = "lower or equal";

/* operators */public static const OPERATOR_OR:String = "or";public static const OPERATOR_AND:String = "and";

public var property:String; //property for comparisonpublic var value:Object; //value to compare withpublic var type:String; //predicate typepublic var conditions:Conditions; //joining conditions (group of predicates to which this predicate belongs)public var attributes:Object; //additional attributes private var _root:ICondition; //root condition

Page 13: ActionScript3 collection query API proposal

Predicate

public function Predicate(cond:ICondition, prop:String, val:Object, type:String, attributes:Object = null){

this._root = cond.root; //set the root condition this.property = prop;this.value = val;this.type = type;this.conditions = cond as Conditions;this.conditions.addPredicate(this); //authomaticly add me to the predicate listthis.attributes = attributes;

}//creates next predicate (not equal)public function diff(prop:String, value:Object):ICondition{ return createNextPredicate(prop, value, DIFF); }//creates next predicate (equal)public function eq(prop:String, value:Object):ICondition{ return createNextPredicate(prop, value, EQ); }…//create next predicate for given valuesprivate function createNextPredicate(prop:String, value:Object, type:String, attributes:Object = null):ICondition{ var pred:Predicate = new Predicate(conditions, prop, value, type, attributes); return pred;}

Page 14: ActionScript3 collection query API proposal

Predicate

// Creates new condition with OR relation public function get OR():ICondition{

var condition:Conditions = new Conditions();condition.type = OPERATOR_OR;condition.root = root;this.conditions.next = condition;return condition;

}

//creates new condition with AND relation public function get AND():ICondition{

var condition:Conditions = new Conditions();condition.type = OPERATOR_AND;condition.root = root;this.conditions.next = condition;return condition;

}

//gets root condition public function get root():ICondition{ return _root;}}

Page 15: ActionScript3 collection query API proposal

Conditions

/** * Condition contains group of predicates, used for joining multiple condition groups (predicates) * @author Slavisa * <br/> * Condition chain boolean result is calculated from last to root condition group */public class Conditions implements ICondition{

private var _predicates:ArrayCollection=new ArrayCollection(); //predicate collectionprivate var _next:ICondition; //next condition in a rowprivate var _root:Conditions; //starting conditionpublic var type:String; //starting condition shouldn't have a type property populated. EQ, OR, AND, LT...

//Condition contains group of predicates, used for joining multiple condition groups (predicates)public function Conditions(){ _root = this;}/** * add predicates to a collection * @param predicate * predicates are validated in groups with ANR logical operator */public function addPredicate(predicate:ICondition):void{ _predicates.addItem(predicate);}

Page 16: ActionScript3 collection query API proposal

Conditions

//creates and appends diff predicate public function diff(prop:String, value:Object):ICondition{ return new Predicate(this, prop, value, Predicate.DIFF);}

//creates and appends eq predicate public function eq(prop:String, value:Object):ICondition{ return new Predicate(this, prop, value, Predicate.EQ);}//… other predicates, getters and setters

//OR condition does nothing on Conditions instance public function get OR():ICondition{ return this;}

//AND condition does nothing on Conditions instance public function get AND():ICondition{ return this;}

}

Page 17: ActionScript3 collection query API proposal

CollectionQuery

/** * Actual collection query implementation * @author Slavisa */public class CollectionQuery implements IQuery{

private var _coll:IList; //collectionprivate var _conditions:ICondition; //conditionsprivate var _comparatorGroups:ArrayCollection; //comparator groups built before collection iteration private const _comparatorMap:Object = createComparatorMap(); //comparator delegatesprivate var _selectColumns:String = null; //select columns. If null, row items are selectedprivate var _limitCount:int = 0; //limited item countprivate var _groupBy:String = null; //group by this propertyprivate var _orderBy:ArrayCollection = new ArrayCollection(); //order by these properties

private var _hasGroupBy:Boolean = false;private var _hasOrderBy:Boolean = false;

//ctorpublic function CollectionQuery(coll:IList = null, selectColumns:String = null){

_coll = coll;this._selectColumns = selectColumns;

}

Page 18: ActionScript3 collection query API proposal

CollectionQuery

//creates comparator delegate mapprivate function createComparatorMap():Object{

var map:Object = new Object();map[Predicate.DIFF] = diff;map[Predicate.EQ] = eq;map[Predicate.GT] = gt;map[Predicate.GTE] = gte;map[Predicate.LT] = lt;map[Predicate.LTE] = lte;map[Predicate.CONTAINS] = contains;return map;

}//contains comparatorprivate function contains(a:Object, b:Object, attributes:Object = null):Boolean{

var strA:String = attributes && attributes.caseSensitive ? String(a) : String(a).toLowerCase();var strB:String = attributes && attributes.caseSensitive ? String(b) : String(b).toLowerCase();return strA.indexOf(strB) > -1;

}//equal comparatorprivate function eq(a:Object, b:Object, attributes:Object = null):Boolean{ return a == b;}

//…other comparators

Page 19: ActionScript3 collection query API proposal

CollectionQuery

//sets select propertiespublic function select(value:String = null):IQuery{

_selectColumns = value;return this;

}//sets target collectionpublic function from(coll:IList):IQuery{

_coll = coll;return this;

}//sets conditions rootpublic function where(conditions:ICondition):IQuery{

this._conditions = conditions.root;return this;

}

/*

Similar for :groupByorderBylimit

*/

Page 20: ActionScript3 collection query API proposal

CollectionQuery

public function first():Object{

if(_hasGroupBy) throw new Error("First() is allowed only for non-grouped results");

var result:IList = runExecution(1) as IList;return result.length > 0 ? result[0] : null;

}public function last():Object{

if(_hasGroupBy) throw new Error("Last() is allowed only for non-grouped results");

var result:IList = runExecution(1, true) as IList;return result.length > 0 ? result[0] : null;

}public function itemAt(index:int):Object{

if(_hasGroupBy) throw new Error("ItemAt() is allowed only for non-grouped results");if(index < 0) throw new ArgumentError("Invalid argument for itemAt method");

var result:IList = runExecution(index + 1) as IList;return result.length > (index + 1) ? null : result[index];

}

Page 21: ActionScript3 collection query API proposal

CollectionQuery

public function execute():Object //interface impl{ return runExecution(_limitCount);}//actual query executionprivate function runExecution(limit:int = 0, reverse:Boolean = false):Object{ implementation details excluded for insufficient space, if anyone cares send me an email to [email protected], I’ll be happy to provide all you need}

private function orderCollection(collection:ArrayCollection):void{

if(_orderBy.length == 0) return;

var sort:Sort = new Sort();sort.fields = new Array();

for each(var fieldDef:Object in _orderBy){

var sortField:SortField = new SortField(fieldDef.field, false, !fieldDef.ascending, fieldDef.numeric); sort.fields.push(sortField);

}

collection.sort = sort;collection.refresh();

}

Page 22: ActionScript3 collection query API proposal

CollectionQuery

/** * Creates result item * @param item * @return Row Item or anonymous object with properties generated from selectColumns variable*/private function createResultItem(item:Object):Object{

if(_selectColumns == null) //return row item return item;

var columns:Array = _selectColumns.split(","); //split columns

if(columns.length == 1) return extractPropertyValue(item, columns[0]); //if only one property, than return it

var res:QueryResultItem = new QueryResultItem(); //anonymous dynamic object

for each(var column:String in columns) //populate anonymous object{

var col:String = StringUtil.trim(column);var newPropname:String = col.replace(/[.]/g, '_');res[newPropname] = extractPropertyValue(item, col); //set property value

}

return res; }

Page 23: ActionScript3 collection query API proposal

CollectionQuery

/** * extracts property from given item */private function extractPropertyValue(item:Object, property:String):Object{

var single:String = StringUtil.trim(property);

if(single.indexOf(".") == -1){

if( !item.hasOwnProperty(single) ) throw new ArgumentError("Property with name \"" + single + "\" does not exist!");

return item[single];}

//recursive call when extracting complex member’s propertiesreturn extractPropertyValue(item[property.substring(0, property.indexOf("."))], property.substring(property.indexOf(".") + 1));

}

Page 24: ActionScript3 collection query API proposal

CollectionQuery

/** * Actual filtering logic * @param item collection item currently being validated * @param validationGroups condition groups to be validated with * @return Boolean * <ul> * <li>validation groups empty - return true</li> * <li>single item in validation group - return predicate validation result</li> * <li>iterate through all condition groups and its predicates and validate them all</li> * </ul> */private function filterFunction(item:Object, validationGroups:ArrayCollection):Boolean{

if(validationGroups.length == 0) return true;

if(validationGroups.length == 1) return validatePredicates(item, _comparatorGroups[0].predicates); var result:Boolean = validatePredicates(item, _comparatorGroups[0].predicates);for (var i:int = 0; i < _comparatorGroups.length - 1; i++){

result = _comparatorGroups[i].operation == Predicate.OPERATOR_AND ?result && validatePredicates(item, _comparatorGroups[i + 1].predicates) :result || validatePredicates(item, _comparatorGroups[i + 1].predicates);

}

return result;}

Page 25: ActionScript3 collection query API proposal

What’s next

OptimizationValidationInterface expansion

“In” and “Between” conditions Joins Comparison between two properties Query reset Nested conditions And much more…

Page 26: ActionScript3 collection query API proposal

Also kewl to have - code snippets I

<template autoinsert="true"context="com.adobe.flexide.as.core.codetemplates.action_script"deleted="false" description="Query first item by the given conditions"enabled="true" name="query_first">var query:IQuery = new CollectionQuery(${_collection});var firstItem:${type} = query.where(newConditions().${operation:values(eq, lt, lte, gt, gte, diff,contains)}("${prop}", ${value})).first() as ${type};

</template>

<template autoinsert="true"context="com.adobe.flexide.as.core.codetemplates.action_script"deleted="false" description="Order queried collection"enabled="true" name="query_order">var query:IQuery = new CollectionQuery(${_collection});var orderedResults:IList = query.where(new Conditions()

.${operation:values(eq, lt, lte, gt, gte, diff, contains)}("${prop}", ${value}))

.orderBy("${orderProp}", ${ascending:values(true, false)}, ${numeric:values(false, true)})

.execute() as IList;</template>

Page 27: ActionScript3 collection query API proposal

Code snippets II

<template autoinsert="true"context="com.adobe.flexide.as.core.codetemplates.action_script"deleted="false" description="Grouping query results"enabled="true" name="query_group">var query:IQuery = newCollectionQuery(${_collection});var groups:IQueryGroupResult = query.where(new Conditions().${operation:values(eq, lt, lte, gt, gte, diff, contains)}("${prop}", $

{value})).groupBy("${groupProperty}").execute() as IQueryGroupResult;

for each(var key:Object in groups.keys){var groupedItems:ArrayCollection = groups[key] as ArrayCollection;}

</template>