Finder.js

var ut = require("./Utils.js");
const CLASS = require("./CoreClass.js");
const FILE = require("./File.js");
const Logger = require("./Logger.js")();
var CONST = require("./CoreConst.js");
var MemoryDb = require("../connectors/inmemory/InMemoryDb.js");
var AppCmp = require("./AndroidAppComponents.js");
const Accessor = require("./AccessFlags");
const ModelPackage = require('./ModelPackage');


var DataModel = {
    package: new ModelPackage("stub"),
    class: new CLASS.Class(),
    field: new CLASS.Field(),
    method: new CLASS.Method(),
    call: new CLASS.Call(),
    modifier: new Accessor.AccessFlags(), 
    objectType: new CLASS.ObjectType(),
    basicType: new CLASS.BasicType(),
    value: new CLASS.ValueConst(),
    string: new CLASS.StringValue(),
    syscall: new CLASS.Syscall(),
    missing: new CLASS.MissingReference(),
    file: new FILE.File(),
    datablock: new CLASS.DataBlock(),
    
    activity: new AppCmp.Activity(),
    receiver: new AppCmp.Receiver(),
    provider: new AppCmp.Provider(),
    service: new AppCmp.Service(),
    permission: new AppCmp.Permission(),
};

function PreparedRequest(name){
    this.name = name;
    this.actions = [];
    //this._run = null;

    return this;
}
PreparedRequest.prototype.pushSelect = function(name, params){
    this.actions.push({ fn:name, param:params });
    return this;
};
PreparedRequest.prototype.pushAction = function(name, params){
    this.actions.push({ fn:name, param:params });
    return this;
};
PreparedRequest.prototype.exec = function(ctx){
    let i = 0;
    let res = ctx.find;

    while(i<this.actions.length){
        res = res[this.actions[0]]
  //      if(this.actions instanceof )
        i++;
    }   
}

class SearchPattern
{
    constructor(cfg){
        this.fn = null;
        this.pattern = null;
        this.field = null;
        this.isModifier = false;
        this.isStructField = false;
        this.isDeepSearch = false;

        // init
        if(cfg!==undefined)
            for(let i in cfg) this[i] = cfg[i];   
    }

    serialize(){
        let o = new Object();
        o.isModifier = this.isModifier;
        o.isStructField = this.isStructField;
        o.isDeepSearch = this.isDeepSearch;
        o.field = this.field;
        o.pattern = this.pattern;

        return o;
    }
}

class FinderJoin
{
    constructor(rootData,joinData,finder){
        this.rootData = rootData;
        this.joinData = joinData
        this._finder = finder;
    }

    // do rootData[]-joinData[]
    sub(){
        let res=new MemoryDb.Index(), x=0;

        this.rootData.map((k,v)=>{
            // element ignored if joinData contains 
            if(this.joinData.hasEntry(v))
                return;
            else
                res.insert(v);
        });

        return new FinderResult(res,this._finder);
    }

    on(pattern){
        let prop = pattern.split(".");
        let lp = prop.length, pref=null, res=new MemoryDb.Index(), x=0;

        this.joinData.map((k,v)=>{
            
            x=0;
            pref = v;

            do{ pref = pref[prop[x++]] }while(x<lp);

            this.rootData.map((i,j)=>{
                let y=0, xref = j
                do{ xref = xref[prop[y++]] }while(y<lp);

                if(xref==pref) 
                    res.insert(this.rootData[j]);
            });
        });
        
        return new FinderResult(res,this._finder);
    };
}



function FinderResult(data,finder){
    this.data = data;
    this._finder = finder;

    this.foreach = function(fn){
        this.data.map(fn);
        //for(let i in this.data) fn(this.data[i]);
    };

    return this;
}

FinderResult.prototype.getData = function(){
    return this.data.getAll();
}

/**
 * To get reference to the method calling each object
 */
FinderResult.prototype.caller = function(){
    let meth = new MemoryDb.Index(), obj=null;
    this.data.map((k,v)=>{
        obj=this.data[i];

        if(v instanceof CLASS.StringValue){
            meth.insert(v.src);
        }
        else if(v instanceof CLASS.ValueConst){
            console.error("[!] Not implemented : [ValueConst].method() ")
        }
        else if(v instanceof CLASS.Field
            || v instanceof CLASS.Method
            || v instanceof CLASS.Class){

            for(let k=0; k<v._callers.length; k++ ){
                meth.insert(v._callers[k]);
            }
        }
        else if(v instanceof CLASS.Call){
            meth.insert(v.caller)
        }
    });/*
    for(let i in this.data){

        obj=this.data[i];

        if(obj instanceof CLASS.StringValue){
            meth.push(obj.src);
        }
        else if(obj instanceof CLASS.ValueConst){
            console.error("[!] Not implemented : [ValueConst].method() ")
        }
        else if(obj instanceof CLASS.Field
            || obj instanceof CLASS.Method
            || obj instanceof CLASS.Class){

            for(let k=0; k<obj._callers.length; k++ ){
                meth.push(obj._callers[k]);
            }
        }
        else if(obj instanceof CLASS.Call){
            meth.push(obj.caller)
        }
    }
    */
    return new FinderResult(meth,this._finder);
};

/**
 * Perform lzing filtering
 * @param {*} pattern 
 */
FinderResult.prototype.filter = function(pattern, caseSensitive=true){
    // perform search with lazy mode
    // not detects fails 
    return this._finder._find(this.data, null, pattern, caseSensitive, true);
};

FinderResult.prototype.ifilter = function(pattern){
    // perform search with lazy mode
    // not detects fails 
    return this.filter(pattern, false);
};


/**
 * To merge two results sets, object already present are ignored
 * @param {FinderResult} data Another result set or list of object
 * @returns {FinderResult} 
 */
FinderResult.prototype.union = function(resultSet){

    if(typeof resultSet === 'string' || resultSet instanceof String){
        let res = this._finder._find(resultSet);
        res.data.map((k,v)=>{
            if(!this.contains(v))
                this.data.insert(v);
        });
    }else{
        resultSet.data.map((k,v)=>{
            if(!this.contains(v))
                this.data.insert(v);
        });
    }
    
    return this;
};


/**
 * To get the list of callers of each object contained into the current result set
 * @returns {FinderResult} A list of instructions using a reference to this object
 */
FinderResult.prototype.callers = function(){
    let rset = new FinderResult();

    this.data.map((k,v) => {
        for(let i in v._callers){
            rset.data.insert(v._callers[i]);
        }
    });
    /*
    for(let i in this.data){ 
        for(let k in this.data[i]._callers){
            rset.data.push(this.data[i]._callers[k]);
        }
    }*/

    return rset;
};

/**
 * To get the count of object into the result set
 * @return {int} The count of object into the result set
 */
FinderResult.prototype.count = function(){
    return this.data.size();
};

FinderResult.prototype.get = function(offset){
    return this.data.getEntry(offset);
};

FinderResult.prototype.select = function(member){
    let data = new MemoryDb.Index();

    this.data.map((k,v)=>{
        if(v[member] !==undefined) data.insert(v[member]);
    });
    
    return new FinderResult(data,this._finder);
};

FinderResult.prototype.toString = function(){
    let out = "";
    this.data.map((k,x)=>{ 
        out += x._hashcode+"\n";
    });
    return out;
}

FinderResult.prototype.dump = function(){
    console.log(this.toString());
};


FinderResult.prototype.toJsonObject = function(fields){
    let data=[], stub={};

    this.data.map((k,v)=>{
        if(v.toJsonObject == undefined){
            console.log("ERROR : toJsonObject() not found");
        }else if(! (v instanceof CLASS.MissingReference)){
            data.push(v.toJsonObject(fields));
        }else{
            stub = {};
            for(let k in fields) stub[fields[k]] = "[MissingReference] Object";
            data.push(stub);
        }
    });
    /*
    for(let i in this.data){
        if(this.data[i].toJsonObject == undefined){
            console.log("ERROR : toJsonObject() not found");
        }else if(! (this.data[i] instanceof CLASS.MissingReference)){
            data.push(this.data[i].toJsonObject(fields));
        }else{
            stub = {};
            for(let k in fields) stub[fields[k]] = "[MissingReference]";
            data.push(stub);
        }
    }*/
    return data;
};

/**
 * To search references to the given objects
 */
FinderResult.prototype.xref = function(){
    let data=new MemoryDb.Index();

    this.data.map((k,v)=>{
        data.insert(new CLASS.XRef(v,v._callers));
    });

    return new FinderResult(data,this._finder);
};

FinderResult.prototype.help = function(){
    let t="+-------------------- HELP --------------------+";
    t += "\n\t.foreach(<fn>)\t\tExecute the function <fn> for each row of the result set";
    t += "\n\t.caller()\t\tSearch the xref for each row of the result set";
    t += "\n\t.filter(<pattern>)\tvFilter the result set by searching a pattern (same format than .find(<pattern>) )";
    t += "\n\t.ifilter(<pattern>)\t\tSame than .filter() but case insensitive";
    t += "\n\t.contains(<object>)\tvCheck the result set contains the given <object>";
    t += "\n\t.union(<FinderResult>)\tvPerform an union between two result sets";
    t += "\n\t.exclude(<pattern>)\tvExclude a subset of objects matching the pattern <pattern>";
    t += "\n\t.show()\t\tDisplay the result data with a formatted style";
    t += "\n\t.sshow()\t\tSame than .show() but with lesser data. (Small Show)";
    t += "\n\t.ssshow(<length>)\t\tSame than .sshow() but with truncate data at <length> char. (Small Small Show)";
    t += "\n\t.get(<id>)\t\tGet the <id> matching object from the result set.";
    t += "\n\t.using(<pattern>)\t\tFilter by class/field/method used by the subject";
    t += "\n\t.count()\t\tGet the count of matching object contained in the result set";
    t += "\n\t.help()\t\tThis help";
    t += "\n";

    console.log(t)
};

FinderResult.prototype.using = function(pattern){
    let data = new MemoryDb.Index();

    if(pattern == null) return this;

    data =  this._finder._find(this.data, null, "_useClass."+pattern, caseSensitive, true);
    data.union(this._finder._find(this.data, null, "_useMethod."+pattern, caseSensitive, true));
    data.union(this._finder._find(this.data, null, "_useField."+pattern, caseSensitive, true));

    return new FinderResult(data,this._finder);
};

FinderResult.prototype.exclude = function(pattern){
    let res = new MemoryDb.Index();
    let arg = this._finder.cache[this._finder.cache.length-1];

    let result = this._finder._find(arg.index, arg.model, pattern, arg.case, arg.lazy);
    
    return new FinderJoin(this.data,result,this._finder);
};

FinderResult.prototype.intersect = function(property,pattern){
    let res = new MemoryDb.Index();
    let arg = this._finder.cache[this._finder.cache.length-1];

    let result = this._finder._find(arg.index, arg.model, pattern, arg.case, arg.lazy);
    
    return (new FinderJoin(this.data,result,this._finder)).sub();
};

/**
 * To check is a result set contains an object
 * @param {*} obj Should has a field _hashcode containing the unique identifier of the object
 */
FinderResult.prototype.contains = function(obj){
    let f=0;
    this.data.map((k,v)=>{
        if(obj._hashcode===v._hashcode) f++;
    });
    
    // TODO : remove
    //        console.log("[DBG] "+obj._hashcode+" not contained");
    return (f>0);
};
/**
 * To display data with formatting
 * Short Show 
 */
FinderResult.prototype.sshow = function(){
    let sub = [];
    this.data.map((k,x)=>{
        if(x instanceof CLASS.Method){
            sub.push({ 
                Class: x.enclosingClass.package+"."+x.enclosingClass.simpleName, 
                Method: x.name,
            });
        }
        else if(x instanceof CLASS.Class){
            sub.push({
                Class: x.name
            });
        }
        else if(x instanceof CLASS.Field){
            sub.push({ 
                Class: x.enclosingClass.package+"."+x.enclosingClass.simpleName, 
                Field: x.name
            });
        }
        else if(x instanceof CLASS.StringValue){
            sub.push({ 
                Value: x.value
            });
        }
        else if(x instanceof CLASS.Call){
            sub.push({ 
                Type: CONST.INSTR_TYPE_LABEL[x.instr.opcode.type],
                Calleed: x.calleed.signature()
            });
        }
        else if(x instanceof FILE.File){
            sub.push({ 
                Name: x.name, 
                Extension: (x.type!=null)? x.type.ext : "[NULL]" 
            });
        }
        else if(x instanceof CLASS.XRef){
            sub.push({ Subject: x.calleed.signature() });
            if(x.empty)
                sub.push({ Subject: "\t  No reference found" });
            else
                for(let k in x.xref) sub.push({ Subject: "\t  "+x.xref[k].signature() });
        }
    });
    console.log(ut.makeTable(sub));
    sub = null;
};
/**
 * To display data with formatting
 */
FinderResult.prototype.show = function(){
    let sub = [];
    this.data.map((k,x)=>{

        if(x instanceof CLASS.Method){
            sub.push({ 
                Class: x._hashcode.substr(0,x._hashcode.indexOf('|')), 
                Modifiers: x.modifiers.sprint(),
                Method: x.name,
            });
        }
        else if(x instanceof CLASS.Class){
            sub.push({ 
                Package: x.package, 
                SimpleName: x.simpleName
            });
        }
        else if(x instanceof CLASS.Field){
            sub.push({ 
                Class: x.enclosingClass.package+"."+x.enclosingClass.simpleName, 
                Field: x.name,
                Modifiers: x.modifiers.sprint()
            });
        }
        else if(x instanceof CLASS.StringValue){
            sub.push({ 
                Value: x.value, 
                Caller: x.src.signature()
            });
        }
        else if(x instanceof CLASS.Call){
            sub.push({ 
                Type: CONST.INSTR_TYPE_LABEL[x.instr.opcode.type],
                Caller: x.caller.signature(),
                Calleed: x.calleed.signature()
            });
        }
        else if(x instanceof CLASS.XRef){
            sub.push({ Subject: x.calleed.signature() });
            if(x.empty)
                sub.push({ Subject: "\t  No reference found" });
            else
                for(let k in x.xref) sub.push({ Subject: "\t  "+x.xref[k].signature() });
        }
        else if(x instanceof FILE.File){
            sub.push({ 
                Name: x.path, 
                Extension: (x.type!=null)? x.type.ext : "[NULL]" 
            });
        }
        else if(x instanceof CLASS.Syscall){
            sub.push({ 
                "num": x.sysnum.join(","),
                "Function": x.func_name,
                "Syscall": x.sys_name,
                "Params": x.args.join(","),
                "Return Type": x.ret
            });
        }
        else{
            sub.push({ Value: x.toString() });
        }
    });
    console.log(ut.makeTable(sub));
    sub = null;
};

class Finder
{
    constructor(pDatabase){
        this.__DB = pDatabase;
        this.cache = [];

        this._test = {     
            /**
             * Check if the <data> object has the <pattern> modifier
             * 
             */
            hasModifier: function(request,data){
                //console.log(request.field, data.modifiers[request.field])
                return data.modifiers[request.field]; //(data.modifiers[request.field]!==0);
            }, 
            /**
             * Check if the <data> object is tagged with <pattern>
             * 
             */ 
            hasTag: function(request,data){
                if(data.tags === undefined) 
                    console.error("Object "+data+" has not 'tags' field");
    
                //console.log(data.tags.indexOf(request.pattern), data.tags, request.pattern);
                return data.tags.indexOf(request.pattern)>-1; 
            }, 
            /**
             * Mock
             */
            NO_TEST: true
        };

        if(pDatabase != null){
            Logger.info("[SEARCH] Finder inittialized");
        }else{
            Logger.info("[SEARCH] Finder DB is empty");
        }
    }

    updateDB(pDatabase){
        this.__DB = pDatabase;
    }

    /**
     * To parse a pattern like [native:]*ssl*.
        - wildcard : replace any char
        - case sensitive
        - add unicode
     * @param {*} dataModel 
     * @param String pattern 
     * @param Boolean caseSensitive 
     * @param Boolean lazy If FALSE, verify if the field exists 
     * @returns {SearchPattern} The parsed search pattern, ready to be used  
     */
    _getTestFn(dataModel, pattern, caseSensitive, lazy=false){
        
        //if(lazy===true) console.debug("LAZY mode detected !");

        if(pattern==undefined || pattern.length==0){
            console.log("[!] find : Pattern cannot be null");
            return null;
        } 

        let token = "name", lex=-1, isDeepSearch=false, rx=null, fn=null, flags="";
        // test si le motif s'applique sur un champs particulier
        
        // parse pattern
        if(pattern.substr(0,3)=="is."){
            if((lex=pattern.indexOf(":"))>-1){
                token = pattern.substr(3,lex-3); 
                pattern = pattern.substr(lex+1,pattern.length-lex-1);
            }else{
                token = pattern.substr(3, pattern.length-3); 
                pattern = "";
            }

            //console.debug("Modifier search ... "+token+"."+pattern+" == true");
            if(lazy === false){
                if(DataModel.modifier[token] !== undefined)
                    return new SearchPattern({ 
                            pattern:pattern, 
                            field:token,  
                            isModifier:true, 
                            fn:this._test.hasModifier 
                        });
                else{
                    console.log("[!] The modifier '"+token+"' not exists for these objects");
                    return null;
                }
            }else{
                //console.debug("LAZY filtering ...");
                return new SearchPattern({ 
                    pattern:pattern, 
                    field:token, 
                    isModifier:true, 
                    fn:this._test.hasModifier 
                });
            }
        }
        else if(pattern.substr(0,4)=="has."){
            //console.debug("Tag-based request detected");
            return new SearchPattern({ 
                pattern: pattern.substr(4),  
                isModifier: false, 
                hasTag: true, 
                fn:this._test.hasTag
            });
        }
        /*
        // exact match is not a RegExp-based search
        else if(pattern.substr(0,7)=="#exact#"){
            //console.debug("Tag-based request detected");
            return new SearchPattern({ 
                pattern: pattern.substr(4),  
                isModifier: false, 
                hasTag: true, 
                fn:this._test.hasExactToken
            });
        }*/


        if((lex=pattern.indexOf(":"))>-1){
            token = pattern.substr(0,lex); 
            pattern = pattern.substr(lex+1);
        }else{
            // DEFAULT field must be parameterized 
            token = "name";
            pattern = "";
        }


        // check if it is a deep search
        if(token.indexOf(".")>-1){
            token = token.split(".");
            isDeepSearch = true;
            //console.debug("Deep search detected !");
        }


        if(lazy === false && isDeepSearch === false && dataModel[token] === undefined){
            console.log("[!] The property '"+token+"' not exists for these objects");
            return null;
        }
        
        // make corresponding regexp
        flags += (caseSensitive?"":"i");
        rx = new RegExp(pattern,flags);

        if(rx != null){
            fn = function(x){ 
                return rx.test(x) 
            } ;
        }else{
            fn = this._test.NO_TEST
        }

        let struct = false;
        if(lazy === false && isDeepSearch===false)
            struct = (dataModel[token] instanceof Array)||(dataModel[token] instanceof Object);
        
        return new SearchPattern({ 
            pattern: pattern, 
            field: token, 
            isStructField: struct,
            isDeepSearch: isDeepSearch,
            fn: fn, 
        });
    }

    _findObject(index, search_pattern, includeMissing=false){
        let matches= new MemoryDb.Index(), k=0, field=undefined;
        
        //console.log(search_pattern);
        index.map((k,v)=>{
            if(!includeMissing && (v instanceof CLASS.MissingReference)) 
                return;

            if((v instanceof CLASS.Method) 
                && (v.modifiers === undefined || v.modifiers === null))
                return;

            field = v[search_pattern.field];
            if(field!==undefined && search_pattern.fn(field)) 
                matches.insert(v);
        });
        /*
        for(let i in index){

            if(!includeMissing && index[i] instanceof CLASS.MissingReference) 
                continue;

            if(index[i] instanceof CLASS.Method 
                && (index[i].modifiers === undefined || index[i].modifiers === null))
                continue;

            field = index[i][search_pattern.field];
            if(field!==undefined && search_pattern.fn(field)) 
                matches.push(index[i]);
        }
        //console.log("[*] "+matches.length+" items found");*/
        return matches;
    };

    /**
     * To search an object by applying the condition on nested fields.
     * The depth level is ignored, the field is searched recursively by following the path
     * give by the search argument.
     * 
     * @param {*} object 
     * @param {SearchPattern} search A search pattern containing the full path to the field to compare  
     * @returns {Boolean} Return the check result 
     */
    __checkDeepField(object,search, offset=0){
        let ref=object, i=offset;

        if(object == null) return false;


        if(ref[search.field[i]]!==undefined && ref[search.field[i]]!==null){
            // if nested ppt is an array - such ars method.args
            /*if(ref[search.field[i]] instanceof Array){
                for(let k=0; k<ref[search.field[i]].length; k++){
                    if(ref[search.field[i]][k] instanceof CLASS.ObjectType){
                        console.log(search.field[i], ref[search.field[i]][k].name, search.field[i+1]);
                        return this.__checkDeepField( this.__DB.classes.getEntry(ref[search.field[i]][k].name), search, i+1);
                    }
                    else if(ref[search.field[i]][k] instanceof CLASS.BasicType){
                        console.log(search.field[i], ref[search.field[i]][k].name, search.field[i+1]);
                        return this.__checkDeepField( this.__DB.classes.getEntry(ref[search.field[i]][k].name), search, i+1);
                    }else{
                        console.log(search.field[i],ref[search.field[i]]); 
                        return this.__checkDeepField(ref[search.field[i]][k], search, i+1);
                    }                
                }
            }*/

            if(i<search.field.length-1){
                if(ref[search.field[i]] instanceof Array){
                    for(let k=0; k<ref[search.field[i]].length; k++){
                        if(ref[search.field[i]][k] instanceof CLASS.ObjectType){
                            //console.log(search.field[i], ref[search.field[i]][k].name, search.field[i+1]);
                            return this.__checkDeepField( this.__DB.classes.getEntry(ref[search.field[i]][k].name), search, i+1);
                        }
                        else if(ref[search.field[i]][k] instanceof CLASS.BasicType){
                            // terminal node (ignore array tag)
                            return false;
                        }else{
                            //console.log(search.field[i],ref[search.field[i]]); 
                            return this.__checkDeepField(ref[search.field[i]][k], search, i+1);
                        }                
                    }
                }else{
                    if(ref[search.field[i]] instanceof CLASS.ObjectType){
                        return this.__checkDeepField( this.__DB.classes.getEntry(ref[search.field[i]].name), search, i+1);
                    }
                    else if(ref[search.field[i]] instanceof CLASS.BasicType){
                        return false;
                    }else{
                        return this.__checkDeepField(ref[search.field[i]], search, i+1);
                    }   

                    return this.__checkDeepField(ref[search.field[i]], search, i+1);
                }
            }else{
                ref = ref[search.field[i]];
            }
        }

        if(ref != null){
            return search.fn(ref);
        }else
            return false;

        /*
        do{
            if(ref[search.field[i]]!==undefined && ref[search.field[i]]!==null){
                // if nested ppt is an array - such ars method.args
                if(ref[search.field[i]] instanceof Array){
                    for(let k=0; k<ref[search.field[i]].length; k++){
                        if(ref[search.field[i]][k] instanceof CLASS.ObjectType){
                            console.log(ref[search.field[i]][k].name, search.field[i+1]);
                            return this.__checkDeepField( this.__DB.classes.getEntry(ref[search.field[i]][k].name), search, i+1);
                        }
                        console.log(search.field[i],ref[search.field[i]]); 
                        return this.__checkDeepField(ref[search.field[i]][k], search, i+1);
                    }
                }
                // else if 
                else{
                    ref = ref[search.field[i]];
                }
            }else{
                // don't treat intermediate noed 
                ref = null;
            }
            i++;
        }while(i<search.field.length);*/

        //console.log(ref);
        
    };

    _findDeepObject(index, search_pattern){
        let matches=new MemoryDb.Index(), k=0, field=undefined;
        
        index.map((k,v)=>{
            if(this.__checkDeepField(v, search_pattern))
                matches.insert(v);
        });
/*
        for(let i in index){
            if(this.__checkDeepField(index[i], search_pattern))
                matches.push(index[i]);
        }*/

        return matches;
    };

 
    // TODO : Factoriser tous les finds
    _findObjectByTag(index, search_pattern){
        let matches=new MemoryDb.Index();
        
        index.map((k,v)=>{
            if(search_pattern.fn(search_pattern,v)) 
                matches.insert(v);
        });
        /*
        for(let i in index){
            if(search_pattern.fn(search_pattern,index[i])) 
                matches.push(index[i]);
        }*/
        //console.log("[*] "+matches.length+" items found");
        return matches;
    };

    _findObjectByModifier(index, search_pattern){
        let matches=new MemoryDb.Index(), k=0, field=undefined;
        
        index.map((k,v)=>{
            if(v.modifiers === undefined || v.modifiers === null)
                return;

            if(search_pattern.fn(search_pattern,v)) 
                matches.insert(v);
        });
        /*
        for(let i in index){
            if(index[i].modifiers === undefined || index[i].modifiers === null)
                continue;

            if(search_pattern.fn(search_pattern,index[i])) 
                matches.push(index[i]);
        }*/
        //console.log("[*] "+matches.length+" items found");
        return matches;
    };

    _listObject(obj_type){
        return db[obj_type].getAll();
    };
    
    _find(index, model, pattern, caseSensitive, lazy=false, includeMissing=false){
        if(pattern === null || pattern === undefined) return new FinderResult(index,this);

        this.cache.push({ index:index, model:model, case:caseSensitive, lazy:lazy });

        let spatt = this._getTestFn(model, pattern, caseSensitive, lazy);
        
        if(spatt!=null){
            if(spatt.isModifier)
                return new FinderResult(this._findObjectByModifier(index, spatt), this); 
            if(spatt.hasTag)
                return new FinderResult(this._findObjectByTag(index, spatt), this); 
            else if(spatt.isDeepSearch){
                console.debug("Running deep search ...")
                //return new FinderResult(this._findDeepObject(index, spatt), this);
                return new FinderResult(this._findDeepObject(index, spatt), this);
            }
            else
                return new FinderResult(this._findObject(index, spatt, includeMissing), this);
        }else{
            return new FinderResult(new MemoryDb.Index(), this); 
        }
    }

}


/**
 * A specialization of the searchAPI for searching missing object 
 * and around
 * @param {SearchAPI} searchAPI
 * @constructor 
 */
function MissingObjectAPI(searchAPI){
    this._search = searchAPI;
}
/**
 * To get statistics about missing reference by type
 * @returns {Object} A array of number of unique missing reference per type
 * @function
 */
MissingObjectAPI.prototype.stats = function(){
    let stats = {};
    stats.field = this.search("_log_tag:FIELD").count();
    stats.type = this.search("_log_tag:TYPE").count();
    stats.method = this.search("_log_tag:METHOD").count();
    stats.class = this.search("_log_tag:CLASS").count();
    return stats;
}
/**
 * To search object into the list of missing reference
 * @param {String} pattern The search pattern following the same syntax than the SearchAPI
 * @returns {FinderResult} A set of occurences
 * @function
 */
MissingObjectAPI.prototype.search = function(pattern){
    return this._search._finder._find(this._search._db.missing, DataModel.missing, pattern, this._search._caseSensitive, true, true);     
}

MissingObjectAPI.prototype.method = function(pattern){
    let db = this.search("_log_tag:METHOD");
    return this._search._finder._find(db.data, DataModel.method, pattern, this._search._caseSensitive, true, true);     
}
MissingObjectAPI.prototype.field = function(pattern){
    let db = this.search("_log_tag:FIELD");
    return this._search._finder._find(db.data, DataModel.field, pattern, this._search._caseSensitive, true, true);     
}
MissingObjectAPI.prototype.type = function(pattern){
    let db = this.search("_log_tag:TYPE");
    return this._search._finder._find(db.data, DataModel.type, pattern, this._search._caseSensitive, true, true);     
}




/**
 * The SearchAPI. Allow the user to perform search into the object
 * database. 
 * @param {Object} data The database of objects 
 * @constructor
 */
function SearchAPI(data){
    
    // AnalyzerDatabase (specialize InMemoryDb)
    var _db = this._db = data;
    this._queryCache = [];
    
    // set default case sensitivity for all search
    this._caseSensitive = true;
  
    var finder = this._finder = new Finder(this._db);

    this.help = "Usage: .help()";
    this.help = function(){
        ut.msgBox("HELP : Search API",[
            "class(<pattern>)\t\tSearch a class by any properties",
            "field(<pattern>)\t\tSearch a field by any properties",
            "method(<pattern>)\t\tSearch a method by any properties",
            "string(<pattern>)\t\tSearch a defined string by any properties",
            "call(<pattern>)\t\tSearch a call by caller/calleed/instructions properties",
            "invoke(<pattern>|<method>)\t\tSearch a call to a given method (called) by a pattern or a method object",
            "getter(<pattern>|<field>)\t\tSearch for getter of given field by a pattern or a field object",
            "setter(<pattern>|<field>)\t\tSearch for setter of given field by a pattern or a field object",
            "new(<pattern>|<class>)\t\tSearch for new instance of a given class object or class properties ",
            "array(<pattern>|<*>)\t\tSearch for static array by array properties",
            "nocase()\t\tSwitch ON/OFF the case sensitive flag of the following search"
        ]);
    };

    /**
     * Switch case sensitive On/Off of following search
     */
    this.nocase = function(){ 
        this._caseSensitive = false; 
        return this; 
    };

    this.class = function(pattern){ return finder._find(_db.classes, DataModel.class, pattern, this._caseSensitive); };
    this.package = function(pattern){ return finder._find(_db.packages, DataModel.package, pattern, this._caseSensitive); };
    this.method = function(pattern){ return finder._find(_db.methods, DataModel.method, pattern, this._caseSensitive); };
    this.field = function(pattern){ return finder._find(_db.fields, DataModel.field, pattern, this._caseSensitive); };
    this.file = function(pattern){ return finder._find(_db.files, DataModel.file, pattern, this._caseSensitive); };
    this.array = function(pattern){ return finder._find(_db.datablock, DataModel.datablock, pattern, this._caseSensitive); };

    this.activity = function(pattern){ return finder._find(_db.activities, DataModel.activity, pattern, this._caseSensitive); };
    this.service = function(pattern){ return finder._find(_db.services, DataModel.service, pattern, this._caseSensitive); };
    this.receiver = function(pattern){ return finder._find(_db.receivers, DataModel.receiver, pattern, this._caseSensitive); };
    this.provider = function(pattern){ return finder._find(_db.providers, DataModel.provider, pattern, this._caseSensitive); };
    this.permission = function(pattern){ return finder._find(_db.permissions, DataModel.permission, pattern, this._caseSensitive); };

    this.call = function(pattern){ 
        return finder._find(_db.call, DataModel.call, pattern, this._caseSensitive); 
    };
    this.string = function(pattern){ 
        let rf = finder._find(_db.strings, DataModel.string, pattern, this._caseSensitive); 
        return rf;
    };
    this.syscall = function(pattern){ return finder._find(_db.syscalls, DataModel.syscall, pattern, this._caseSensitive); };
    //this.syscall = function(pattern){ return finder._find(_db.syscalls, DataModel.syscall, pattern, this._caseSensitive); };
    this.syscallnum = function(id){
        return this.syscall("sysnum:^"+id+"$");
    };
 
    this.missing = new MissingObjectAPI(this);

    this.get = {
        package: function(id){ return _db.packages.getEntry(id) },
        class: function(id){ return _db.classes.getEntry(id) },
        method: function(id){ return _db.methods.getEntry(id) },
        field: function(id){ return _db.fields.getEntry(id) },
        syscalls: function(id){ return _db.syscalls.getEntry(id) },

        activity: function(id){ return _db.activities.getEntry(id) },
        provider: function(id){ return _db.providers.getEntry(id) },
        receiver: function(id){ return _db.receivers.getEntry(id) },
        service: function(id){ return _db.services.getEntry(id) },
        permission: function(id){ return _db.permissions.getEntry(id) }
        
        /*datablock: function(id){

        }*/
    };
    
    
    /**
     * To seach only method call
     * @param {*} pattern 
     */
    this.invoke = function(pattern){
        let res = finder._find(
            _db.call, DataModel.call, 
            "instr.opcode.instr:invoke", false, true);

        if(pattern === null)
            return res;
        if(typeof pattern === 'string' || pattern instanceof String)
            return res.filter(pattern);
        else if(pattern instanceof CLASS.Method)
            return res.filter("calleed.__signature__:"+pattern.__signature__);
        else
            return res;
    };


    /**
     * @param {String} pattern Search pattern
     */
    this.setter = function(pattern=null){
        let res = null;
        if(pattern != null){
            res = finder._find(
                _db.call, DataModel.call, 
                "calleed."+pattern, false, true);
            res = res.filter("instr.opcode.type:"+CONST.INSTR_TYPE.SETTER);
        }
        else{
            res = finder._find(
                _db.call, DataModel.call, 
                "instr.opcode.type:"+CONST.INSTR_TYPE.SETTER, false, true);
        }
 
        return res;
    }

    /**
     * 
     * @param {String} pattern Field signature
     */
    this.settersOf = function(signature){
        return this.setter("__signature__:"+signature);
    }

    /**
     * @param {String} pattern Field signature
     */
    this.getter = function(pattern=null){
        let res = null;
        if(pattern != null){
            res = finder._find(
                _db.call, DataModel.call, 
                "calleed."+pattern, false, true);
            res = res.filter("instr.opcode.type:"+CONST.INSTR_TYPE.GETTER);
        }
        else{
            res = finder._find(
                _db.call, DataModel.call, 
                "instr.opcode.type:"+CONST.INSTR_TYPE.GETTER, false, true);
        }
 
        return res;
    }


    /**
     * @param {String} pattern Field signature
     */
    this.gettersOf = function(signature){

    
        let f = new FinderResult([this.get.field(signature)], this);

        return finder._find(
            _db.call, DataModel.call, 
            "instr.opcode.type:"+CONST.INSTR_TYPE.GETTER, false, true);

        return this.getter("__signature__:"+signature);
    }

    this.calls = {
        setter: function(pattern=null){
            let res = finder._find(
                _db.call, DataModel.call, 
                "instr.opcode.type:"+CONST.INSTR_TYPE.SETTER, false, true);
            //console.log(res);
            if(pattern !== null)
                return res.filter(pattern);
            else
                return res;
        },
        getter: function(pattern=null){
            let res = finder._find(
                _db.call, DataModel.call, 
                "instr.opcode.type:"+CONST.INSTR_TYPE.GETTER, false, true);
            
            if(pattern !== null)
                return res.filter(pattern);
            else
                return res;
        },
        print: function(){
            _db.call.map((k,v) => { v.print() });;
        },
        raw: function(){
            _db.call.map((k,v) => { console.log("\t"+v.instr._raw) });;
        },
        //find: function(pattern){ return finder.instr(pattern,true); }
    };

    this.updateDB = function(data){
        _db = this._db = data;
        this._finder.updateDB(data);
    };

    /**
     * Set the flag "preparing" which means the research should not be executed.
     * Create a new PreparedRequest instance. 
     */
    this.beginPrepare = function(name){
        this.preparing = true;
        this.preparingReq = new PreparedRequest(name);
        
        return this;
    }

    /**
     * 
     */
    this.endPrepare = function(){
        this.preparing = false;
        return this.preparingReq;
    }

    /**
     * To execute a prepared request. Almost time it is when 
     * an inspector is running.
     * @param {*} preparedRequest 
     */
    this.exec = function(preparedRequest){

    }
}

module.exports = {
    SearchAPI: SearchAPI,
    SearchPattern: SearchPattern,
    Finder: Finder,
    FinderResult: FinderResult
};