diff --git a/complex_queries.js b/complex_queries.js new file mode 100644 index 0000000000000000000000000000000000000000..00477f8ea14a0d9b320870a4f93aab987faa5b96 --- /dev/null +++ b/complex_queries.js @@ -0,0 +1,901 @@ +(function (scope) { + "use strict"; + Object.defineProperty(scope, "ComplexQueries", { + configurable: false, + enumerable: false, + writable: false, + value: {} + }); +Object.defineProperty(scope.ComplexQueries, "parse", { + configurable: false, + enumerable: false, + writable: false, + value: function (string) { + +/* + Default template driver for JS/CC generated parsers running as + browser-based JavaScript/ECMAScript applications. + + WARNING: This parser template will not run as console and has lesser + features for debugging than the console derivates for the + various JavaScript platforms. + + Features: + - Parser trace messages + - Integrated panic-mode error recovery + + Written 2007, 2008 by Jan Max Meyer, J.M.K S.F. Software Technologies + + This is in the public domain. +*/ + +var _dbg_withtrace = false; +var _dbg_string = new String(); + +function __dbg_print( text ) +{ + _dbg_string += text + "\n"; +} + +function __lex( info ) +{ + var state = 0; + var match = -1; + var match_pos = 0; + var start = 0; + var pos = info.offset + 1; + + do + { + pos--; + state = 0; + match = -2; + start = pos; + + if( info.src.length <= start ) + return 19; + + do + { + +switch( state ) +{ + case 0: + if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 8 ) || ( info.src.charCodeAt( pos ) >= 10 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || info.src.charCodeAt( pos ) == 59 || ( info.src.charCodeAt( pos ) >= 63 && info.src.charCodeAt( pos ) <= 64 ) || ( info.src.charCodeAt( pos ) >= 66 && info.src.charCodeAt( pos ) <= 77 ) || ( info.src.charCodeAt( pos ) >= 80 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; + else if( info.src.charCodeAt( pos ) == 9 ) state = 2; + else if( info.src.charCodeAt( pos ) == 40 ) state = 3; + else if( info.src.charCodeAt( pos ) == 41 ) state = 4; + else if( info.src.charCodeAt( pos ) == 60 || info.src.charCodeAt( pos ) == 62 ) state = 5; + else if( info.src.charCodeAt( pos ) == 34 ) state = 11; + else if( info.src.charCodeAt( pos ) == 79 ) state = 12; + else if( info.src.charCodeAt( pos ) == 32 ) state = 13; + else if( info.src.charCodeAt( pos ) == 61 ) state = 14; + else if( info.src.charCodeAt( pos ) == 65 ) state = 18; + else if( info.src.charCodeAt( pos ) == 78 ) state = 19; + else state = -1; + break; + + case 1: + if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; + else if( info.src.charCodeAt( pos ) == 58 ) state = 6; + else state = -1; + match = 10; + match_pos = pos; + break; + + case 2: + if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; + else if( info.src.charCodeAt( pos ) == 58 ) state = 6; + else state = -1; + match = 1; + match_pos = pos; + break; + + case 3: + state = -1; + match = 3; + match_pos = pos; + break; + + case 4: + state = -1; + match = 4; + match_pos = pos; + break; + + case 5: + if( info.src.charCodeAt( pos ) == 61 ) state = 14; + else state = -1; + match = 11; + match_pos = pos; + break; + + case 6: + state = -1; + match = 8; + match_pos = pos; + break; + + case 7: + state = -1; + match = 9; + match_pos = pos; + break; + + case 8: + if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; + else if( info.src.charCodeAt( pos ) == 58 ) state = 6; + else state = -1; + match = 6; + match_pos = pos; + break; + + case 9: + if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; + else if( info.src.charCodeAt( pos ) == 58 ) state = 6; + else state = -1; + match = 5; + match_pos = pos; + break; + + case 10: + if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; + else if( info.src.charCodeAt( pos ) == 58 ) state = 6; + else state = -1; + match = 7; + match_pos = pos; + break; + + case 11: + if( info.src.charCodeAt( pos ) == 34 ) state = 7; + else if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 33 ) || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 91 ) || ( info.src.charCodeAt( pos ) >= 93 && info.src.charCodeAt( pos ) <= 254 ) ) state = 11; + else if( info.src.charCodeAt( pos ) == 92 ) state = 15; + else state = -1; + break; + + case 12: + if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 81 ) || ( info.src.charCodeAt( pos ) >= 83 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; + else if( info.src.charCodeAt( pos ) == 58 ) state = 6; + else if( info.src.charCodeAt( pos ) == 82 ) state = 8; + else state = -1; + match = 10; + match_pos = pos; + break; + + case 13: + state = -1; + match = 1; + match_pos = pos; + break; + + case 14: + state = -1; + match = 11; + match_pos = pos; + break; + + case 15: + if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 254 ) ) state = 11; + else state = -1; + break; + + case 16: + if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 67 ) || ( info.src.charCodeAt( pos ) >= 69 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; + else if( info.src.charCodeAt( pos ) == 58 ) state = 6; + else if( info.src.charCodeAt( pos ) == 68 ) state = 9; + else state = -1; + match = 10; + match_pos = pos; + break; + + case 17: + if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 83 ) || ( info.src.charCodeAt( pos ) >= 85 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; + else if( info.src.charCodeAt( pos ) == 58 ) state = 6; + else if( info.src.charCodeAt( pos ) == 84 ) state = 10; + else state = -1; + match = 10; + match_pos = pos; + break; + + case 18: + if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 77 ) || ( info.src.charCodeAt( pos ) >= 79 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; + else if( info.src.charCodeAt( pos ) == 58 ) state = 6; + else if( info.src.charCodeAt( pos ) == 78 ) state = 16; + else state = -1; + match = 10; + match_pos = pos; + break; + + case 19: + if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 78 ) || ( info.src.charCodeAt( pos ) >= 80 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; + else if( info.src.charCodeAt( pos ) == 58 ) state = 6; + else if( info.src.charCodeAt( pos ) == 79 ) state = 17; + else state = -1; + match = 10; + match_pos = pos; + break; + +} + + + pos++; + + } + while( state > -1 ); + + } + while( 1 > -1 && match == 1 ); + + if( match > -1 ) + { + info.att = info.src.substr( start, match_pos - start ); + info.offset = match_pos; + + + } + else + { + info.att = new String(); + match = -1; + } + + return match; +} + + +function __parse( src, err_off, err_la ) +{ + var sstack = new Array(); + var vstack = new Array(); + var err_cnt = 0; + var act; + var go; + var la; + var rval; + var parseinfo = new Function( "", "var offset; var src; var att;" ); + var info = new parseinfo(); + +/* Pop-Table */ +var pop_tab = new Array( + new Array( 0/* begin' */, 1 ), + new Array( 13/* begin */, 1 ), + new Array( 12/* search_text */, 1 ), + new Array( 12/* search_text */, 2 ), + new Array( 12/* search_text */, 3 ), + new Array( 14/* and_expression */, 1 ), + new Array( 14/* and_expression */, 3 ), + new Array( 15/* boolean_expression */, 2 ), + new Array( 15/* boolean_expression */, 1 ), + new Array( 16/* expression */, 3 ), + new Array( 16/* expression */, 2 ), + new Array( 16/* expression */, 1 ), + new Array( 17/* value */, 2 ), + new Array( 17/* value */, 1 ), + new Array( 18/* string */, 1 ), + new Array( 18/* string */, 1 ) +); + +/* Action-Table */ +var act_tab = new Array( + /* State 0 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ), + /* State 1 */ new Array( 19/* "$" */,0 ), + /* State 2 */ new Array( 19/* "$" */,-1 ), + /* State 3 */ new Array( 6/* "OR" */,14 , 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 , 19/* "$" */,-2 , 4/* "RIGHT_PARENTHESE" */,-2 ), + /* State 4 */ new Array( 5/* "AND" */,16 , 19/* "$" */,-5 , 7/* "NOT" */,-5 , 3/* "LEFT_PARENTHESE" */,-5 , 8/* "COLUMN" */,-5 , 11/* "OPERATOR" */,-5 , 10/* "WORD" */,-5 , 9/* "STRING" */,-5 , 6/* "OR" */,-5 , 4/* "RIGHT_PARENTHESE" */,-5 ), + /* State 5 */ new Array( 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ), + /* State 6 */ new Array( 19/* "$" */,-8 , 7/* "NOT" */,-8 , 3/* "LEFT_PARENTHESE" */,-8 , 8/* "COLUMN" */,-8 , 11/* "OPERATOR" */,-8 , 10/* "WORD" */,-8 , 9/* "STRING" */,-8 , 6/* "OR" */,-8 , 5/* "AND" */,-8 , 4/* "RIGHT_PARENTHESE" */,-8 ), + /* State 7 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ), + /* State 8 */ new Array( 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ), + /* State 9 */ new Array( 19/* "$" */,-11 , 7/* "NOT" */,-11 , 3/* "LEFT_PARENTHESE" */,-11 , 8/* "COLUMN" */,-11 , 11/* "OPERATOR" */,-11 , 10/* "WORD" */,-11 , 9/* "STRING" */,-11 , 6/* "OR" */,-11 , 5/* "AND" */,-11 , 4/* "RIGHT_PARENTHESE" */,-11 ), + /* State 10 */ new Array( 10/* "WORD" */,12 , 9/* "STRING" */,13 ), + /* State 11 */ new Array( 19/* "$" */,-13 , 7/* "NOT" */,-13 , 3/* "LEFT_PARENTHESE" */,-13 , 8/* "COLUMN" */,-13 , 11/* "OPERATOR" */,-13 , 10/* "WORD" */,-13 , 9/* "STRING" */,-13 , 6/* "OR" */,-13 , 5/* "AND" */,-13 , 4/* "RIGHT_PARENTHESE" */,-13 ), + /* State 12 */ new Array( 19/* "$" */,-14 , 7/* "NOT" */,-14 , 3/* "LEFT_PARENTHESE" */,-14 , 8/* "COLUMN" */,-14 , 11/* "OPERATOR" */,-14 , 10/* "WORD" */,-14 , 9/* "STRING" */,-14 , 6/* "OR" */,-14 , 5/* "AND" */,-14 , 4/* "RIGHT_PARENTHESE" */,-14 ), + /* State 13 */ new Array( 19/* "$" */,-15 , 7/* "NOT" */,-15 , 3/* "LEFT_PARENTHESE" */,-15 , 8/* "COLUMN" */,-15 , 11/* "OPERATOR" */,-15 , 10/* "WORD" */,-15 , 9/* "STRING" */,-15 , 6/* "OR" */,-15 , 5/* "AND" */,-15 , 4/* "RIGHT_PARENTHESE" */,-15 ), + /* State 14 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ), + /* State 15 */ new Array( 19/* "$" */,-3 , 4/* "RIGHT_PARENTHESE" */,-3 ), + /* State 16 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ), + /* State 17 */ new Array( 19/* "$" */,-7 , 7/* "NOT" */,-7 , 3/* "LEFT_PARENTHESE" */,-7 , 8/* "COLUMN" */,-7 , 11/* "OPERATOR" */,-7 , 10/* "WORD" */,-7 , 9/* "STRING" */,-7 , 6/* "OR" */,-7 , 5/* "AND" */,-7 , 4/* "RIGHT_PARENTHESE" */,-7 ), + /* State 18 */ new Array( 4/* "RIGHT_PARENTHESE" */,23 ), + /* State 19 */ new Array( 19/* "$" */,-10 , 7/* "NOT" */,-10 , 3/* "LEFT_PARENTHESE" */,-10 , 8/* "COLUMN" */,-10 , 11/* "OPERATOR" */,-10 , 10/* "WORD" */,-10 , 9/* "STRING" */,-10 , 6/* "OR" */,-10 , 5/* "AND" */,-10 , 4/* "RIGHT_PARENTHESE" */,-10 ), + /* State 20 */ new Array( 19/* "$" */,-12 , 7/* "NOT" */,-12 , 3/* "LEFT_PARENTHESE" */,-12 , 8/* "COLUMN" */,-12 , 11/* "OPERATOR" */,-12 , 10/* "WORD" */,-12 , 9/* "STRING" */,-12 , 6/* "OR" */,-12 , 5/* "AND" */,-12 , 4/* "RIGHT_PARENTHESE" */,-12 ), + /* State 21 */ new Array( 19/* "$" */,-4 , 4/* "RIGHT_PARENTHESE" */,-4 ), + /* State 22 */ new Array( 19/* "$" */,-6 , 7/* "NOT" */,-6 , 3/* "LEFT_PARENTHESE" */,-6 , 8/* "COLUMN" */,-6 , 11/* "OPERATOR" */,-6 , 10/* "WORD" */,-6 , 9/* "STRING" */,-6 , 6/* "OR" */,-6 , 4/* "RIGHT_PARENTHESE" */,-6 ), + /* State 23 */ new Array( 19/* "$" */,-9 , 7/* "NOT" */,-9 , 3/* "LEFT_PARENTHESE" */,-9 , 8/* "COLUMN" */,-9 , 11/* "OPERATOR" */,-9 , 10/* "WORD" */,-9 , 9/* "STRING" */,-9 , 6/* "OR" */,-9 , 5/* "AND" */,-9 , 4/* "RIGHT_PARENTHESE" */,-9 ) +); + +/* Goto-Table */ +var goto_tab = new Array( + /* State 0 */ new Array( 13/* begin */,1 , 12/* search_text */,2 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ), + /* State 1 */ new Array( ), + /* State 2 */ new Array( ), + /* State 3 */ new Array( 12/* search_text */,15 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ), + /* State 4 */ new Array( ), + /* State 5 */ new Array( 16/* expression */,17 , 17/* value */,9 , 18/* string */,11 ), + /* State 6 */ new Array( ), + /* State 7 */ new Array( 12/* search_text */,18 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ), + /* State 8 */ new Array( 16/* expression */,19 , 17/* value */,9 , 18/* string */,11 ), + /* State 9 */ new Array( ), + /* State 10 */ new Array( 18/* string */,20 ), + /* State 11 */ new Array( ), + /* State 12 */ new Array( ), + /* State 13 */ new Array( ), + /* State 14 */ new Array( 12/* search_text */,21 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ), + /* State 15 */ new Array( ), + /* State 16 */ new Array( 14/* and_expression */,22 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ), + /* State 17 */ new Array( ), + /* State 18 */ new Array( ), + /* State 19 */ new Array( ), + /* State 20 */ new Array( ), + /* State 21 */ new Array( ), + /* State 22 */ new Array( ), + /* State 23 */ new Array( ) +); + + + +/* Symbol labels */ +var labels = new Array( + "begin'" /* Non-terminal symbol */, + "WHITESPACE" /* Terminal symbol */, + "WHITESPACE" /* Terminal symbol */, + "LEFT_PARENTHESE" /* Terminal symbol */, + "RIGHT_PARENTHESE" /* Terminal symbol */, + "AND" /* Terminal symbol */, + "OR" /* Terminal symbol */, + "NOT" /* Terminal symbol */, + "COLUMN" /* Terminal symbol */, + "STRING" /* Terminal symbol */, + "WORD" /* Terminal symbol */, + "OPERATOR" /* Terminal symbol */, + "search_text" /* Non-terminal symbol */, + "begin" /* Non-terminal symbol */, + "and_expression" /* Non-terminal symbol */, + "boolean_expression" /* Non-terminal symbol */, + "expression" /* Non-terminal symbol */, + "value" /* Non-terminal symbol */, + "string" /* Non-terminal symbol */, + "$" /* Terminal symbol */ +); + + + + info.offset = 0; + info.src = src; + info.att = new String(); + + if( !err_off ) + err_off = new Array(); + if( !err_la ) + err_la = new Array(); + + sstack.push( 0 ); + vstack.push( 0 ); + + la = __lex( info ); + + while( true ) + { + act = 25; + for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 ) + { + if( act_tab[sstack[sstack.length-1]][i] == la ) + { + act = act_tab[sstack[sstack.length-1]][i+1]; + break; + } + } + + if( _dbg_withtrace && sstack.length > 0 ) + { + __dbg_print( "\nState " + sstack[sstack.length-1] + "\n" + + "\tLookahead: " + labels[la] + " (\"" + info.att + "\")\n" + + "\tAction: " + act + "\n" + + "\tSource: \"" + info.src.substr( info.offset, 30 ) + ( ( info.offset + 30 < info.src.length ) ? + "..." : "" ) + "\"\n" + + "\tStack: " + sstack.join() + "\n" + + "\tValue stack: " + vstack.join() + "\n" ); + } + + + //Panic-mode: Try recovery when parse-error occurs! + if( act == 25 ) + { + if( _dbg_withtrace ) + __dbg_print( "Error detected: There is no reduce or shift on the symbol " + labels[la] ); + + err_cnt++; + err_off.push( info.offset - info.att.length ); + err_la.push( new Array() ); + for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 ) + err_la[err_la.length-1].push( labels[act_tab[sstack[sstack.length-1]][i]] ); + + //Remember the original stack! + var rsstack = new Array(); + var rvstack = new Array(); + for( var i = 0; i < sstack.length; i++ ) + { + rsstack[i] = sstack[i]; + rvstack[i] = vstack[i]; + } + + while( act == 25 && la != 19 ) + { + if( _dbg_withtrace ) + __dbg_print( "\tError recovery\n" + + "Current lookahead: " + labels[la] + " (" + info.att + ")\n" + + "Action: " + act + "\n\n" ); + if( la == -1 ) + info.offset++; + + while( act == 25 && sstack.length > 0 ) + { + sstack.pop(); + vstack.pop(); + + if( sstack.length == 0 ) + break; + + act = 25; + for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 ) + { + if( act_tab[sstack[sstack.length-1]][i] == la ) + { + act = act_tab[sstack[sstack.length-1]][i+1]; + break; + } + } + } + + if( act != 25 ) + break; + + for( var i = 0; i < rsstack.length; i++ ) + { + sstack.push( rsstack[i] ); + vstack.push( rvstack[i] ); + } + + la = __lex( info ); + } + + if( act == 25 ) + { + if( _dbg_withtrace ) + __dbg_print( "\tError recovery failed, terminating parse process..." ); + break; + } + + + if( _dbg_withtrace ) + __dbg_print( "\tError recovery succeeded, continuing" ); + } + + /* + if( act == 25 ) + break; + */ + + + //Shift + if( act > 0 ) + { + if( _dbg_withtrace ) + __dbg_print( "Shifting symbol: " + labels[la] + " (" + info.att + ")" ); + + sstack.push( act ); + vstack.push( info.att ); + + la = __lex( info ); + + if( _dbg_withtrace ) + __dbg_print( "\tNew lookahead symbol: " + labels[la] + " (" + info.att + ")" ); + } + //Reduce + else + { + act *= -1; + + if( _dbg_withtrace ) + __dbg_print( "Reducing by producution: " + act ); + + rval = void(0); + + if( _dbg_withtrace ) + __dbg_print( "\tPerforming semantic action..." ); + +switch( act ) +{ + case 0: + { + rval = vstack[ vstack.length - 1 ]; + } + break; + case 1: + { + result = vstack[ vstack.length - 1 ]; + } + break; + case 2: + { + rval = vstack[ vstack.length - 1 ]; + } + break; + case 3: + { + rval = mkComplexQuery('OR',[vstack[ vstack.length - 2 ],vstack[ vstack.length - 1 ]]); + } + break; + case 4: + { + rval = mkComplexQuery('OR',[vstack[ vstack.length - 3 ],vstack[ vstack.length - 1 ]]); + } + break; + case 5: + { + rval = vstack[ vstack.length - 1 ] ; + } + break; + case 6: + { + rval = mkComplexQuery('AND',[vstack[ vstack.length - 3 ],vstack[ vstack.length - 1 ]]); + } + break; + case 7: + { + rval = mkNotQuery(vstack[ vstack.length - 1 ]); + } + break; + case 8: + { + rval = vstack[ vstack.length - 1 ]; + } + break; + case 9: + { + rval = vstack[ vstack.length - 2 ]; + } + break; + case 10: + { + simpleQuerySetId(vstack[ vstack.length - 1 ],vstack[ vstack.length - 2 ].split(':').slice(0,-1).join(':')); rval = vstack[ vstack.length - 1 ]; + } + break; + case 11: + { + rval = vstack[ vstack.length - 1 ]; + } + break; + case 12: + { + vstack[ vstack.length - 1 ].operator = vstack[ vstack.length - 2 ] ; rval = vstack[ vstack.length - 1 ]; + } + break; + case 13: + { + rval = vstack[ vstack.length - 1 ]; + } + break; + case 14: + { + rval = mkSimpleQuery('',vstack[ vstack.length - 1 ]); + } + break; + case 15: + { + rval = mkSimpleQuery('',vstack[ vstack.length - 1 ].split('"').slice(1,-1).join('"')); + } + break; +} + + + + if( _dbg_withtrace ) + __dbg_print( "\tPopping " + pop_tab[act][1] + " off the stack..." ); + + for( var i = 0; i < pop_tab[act][1]; i++ ) + { + sstack.pop(); + vstack.pop(); + } + + go = -1; + for( var i = 0; i < goto_tab[sstack[sstack.length-1]].length; i+=2 ) + { + if( goto_tab[sstack[sstack.length-1]][i] == pop_tab[act][0] ) + { + go = goto_tab[sstack[sstack.length-1]][i+1]; + break; + } + } + + if( act == 0 ) + break; + + if( _dbg_withtrace ) + __dbg_print( "\tPushing non-terminal " + labels[ pop_tab[act][0] ] ); + + sstack.push( go ); + vstack.push( rval ); + } + + if( _dbg_withtrace ) + { + alert( _dbg_string ); + _dbg_string = new String(); + } + } + + if( _dbg_withtrace ) + { + __dbg_print( "\nParse complete." ); + alert( _dbg_string ); + } + + return err_cnt; +} + + + +var arrayExtend = function () { + var j,i,newlist=[],listoflists = arguments; + for (j=0; j<listoflists.length; ++j) { + for (i=0; i<listoflists[j].length; ++i) { + newlist.push(listoflists[j][i]); + } + } + return newlist; +}; +var mkSimpleQuery = function (id,value,operator) { + return {type:'simple',operator:'=',id:id,value:value}; +}; +var mkNotQuery = function (query) { + if (query.operator === 'NOT') { + return query.query_list[0]; + } + return {type:'complex',operator:'NOT',query_list:[query]}; +}; +var mkComplexQuery = function (operator,query_list) { + var i,query_list2 = []; + for (i=0; i<query_list.length; ++i) { + if (query_list[i].operator === operator) { + query_list2 = arrayExtend(query_list2,query_list[i].query_list); + } else { + query_list2.push(query_list[i]); + } + } + return {type:'complex',operator:operator,query_list:query_list2}; +}; +var simpleQuerySetId = function (query, id) { + var i; + if (query.type === 'complex') { + for (i = 0; i < query.query_list.length; ++i) { + simpleQuerySetId (query.query_list[i],id); + } + return true; + } + if (query.type === 'simple' && !query.id) { + query.id = id; + return true; + } + return false; +}; +var error_offsets = []; +var error_lookaheads = []; +var error_count = 0; +var result; +if ( ( error_count = __parse( string, error_offsets, error_lookaheads ) ) > 0 ) { + var i; + for (i = 0; i < error_count; ++i) { + throw new Error ( "Parse error near \"" + + string.substr ( error_offsets[i] ) + + "\", expecting \"" + + error_lookaheads[i].join() + "\"" ); + } +} + + return result; + } + +}); +Object.defineProperty(scope.ComplexQueries,"serialize",{ + configurable:false,enumerable:false,writable:false,value:function(query){ + var str_list = [], i; + if (query.type === 'complex') { + str_list.push ( '(' ); + for (i=0; i<query.query_list.length; ++i) { + str_list.push( scope.ComplexQueries.serialize(query.query_list[i]) ); + str_list.push( query.operator ); + } + str_list.length --; + str_list.push ( ')' ); + return str_list.join(' '); + } else if (query.type === 'simple') { + return query.id + (query.id?': ':'') + query.operator + ' "' + query.value + '"'; + } + return query; + } +}); +Object.defineProperty(scope.ComplexQueries,"query",{ + configurable:false,enumerable:false,writable:false, + value: function (query, object_list) { + var wildcard_character = typeof query.wildcard_character === 'string' ? + query.wildcard_character : '%', + operator_actions = { + '=': function (value1, value2) { + value1 = '' + value1; + return value1.match (convertToRegexp ( + value2, wildcard_character + )) || false && true; + }, + '!=': function (value1, value2) { + value1 = '' + value1; + return !(value1.match (convertToRegexp ( + value2, wildcard_character + ))); + }, + '<': function (value1, value2) { return value1 < value2; }, + '<=': function (value1, value2) { return value1 <= value2; }, + '>': function (value1, value2) { return value1 > value2; }, + '>=': function (value1, value2) { return value1 >= value2; }, + 'AND': function (item, query_list) { + var i; + for (i=0; i<query_list.length; ++i) { + if (! itemMatchesQuery (item, query_list[i])) { + return false; + } + } + return true; + }, + 'OR': function (item, query_list) { + var i; + for (i=0; i<query_list.length; ++i) { + if (itemMatchesQuery (item, query_list[i])) { + return true; + } + } + return false; + }, + 'NOT': function (item, query_list) { + return !itemMatchesQuery(item, query_list[0]); + } + }, + convertToRegexp = function (string) { + return subString('^' + string.replace( + new RegExp( + '([\\{\\}\\(\\)\\^\\$\\&\\.\\*\\?\\\/\\+\\|\\[\\]\\-\\\\])'. + replace (wildcard_character? + '\\'+wildcard_character:undefined,''), + 'g' + ), + '\\$1' + ) + '$',(wildcard_character||undefined), '.*'); + }, + subString = function (string, substring, newsubstring) { + var res = '', i = 0; + if (substring === undefined) { + return string; + } + while (1) { + var tmp = string.indexOf(substring,i); + if (tmp === -1) { + break; + } + for (; i < tmp; ++i) { + res += string[i]; + } + res += newsubstring; + i += substring.length; + } + for (; i<string.length; ++i) { + res += string[i]; + } + return res; + }, + itemMatchesQuery = function (item, query_object) { + var i; + if (query_object.type === 'complex') { + return operator_actions[query_object.operator]( + item, query_object.query_list + ); + } else { + if (query_object.id) { + if (typeof item[query_object.id] !== 'undefined') { + return operator_actions[query_object.operator]( + item[query_object.id], query_object.value + ); + } else { + return false; + } + } else { + return true; + } + } + }, + select = function (list, select_list) { + var i; + if (select_list.length === 0) { + return; + } + for (i=0; i<list.length; ++i) { + var list_value = {}, k; + for (k=0; k<select_list.length; ++k) { + list_value[select_list[k]] = + list[i][select_list[k]]; + } + list[i] = list_value; + } + }, + sortFunction = function (key, asc) { + if (asc === 'descending') { + return function (a,b) { + return a[key] < b[key] ? 1 : a[key] > b[key] ? -1 : 0; + }; + } + return function (a,b) { + return a[key] > b[key] ? 1 : a[key] < b[key] ? -1 : 0; + }; + }, + mergeList = function (list, list_to_merge, index) { + var i,j; + for (i = index,j = 0; i < list_to_merge.length + index; ++i, ++j) { + list[i] = list_to_merge[j]; + } + }, + sort = function (list, sort_list) { + var i, tmp, key, asc, sortAndMerge = function() { + sort(tmp,sort_list.slice(1)); + mergeList(list,tmp,i-tmp.length); + tmp = [list[i]]; + }; + if (list.length < 2) { + return; + } + if (sort_list.length === 0) { + return; + } + key = sort_list[0][0]; + asc = sort_list[0][1]; + list.sort (sortFunction (key,asc)); + tmp = [list[0]]; + for (i = 1; i < list.length; ++i) { + if (tmp[0][key] === list[i][key]) { + tmp.push(list[i]); + } else { + sortAndMerge(); + } + } + sortAndMerge(); + }, + limit = function (list, limit_list) { + var i; + if (typeof limit_list[0] !== 'undefined') { + if (typeof limit_list[1] !== 'undefined') { + if (list.length > limit_list[1] + limit_list[0]) { + list.length = limit_list[1] + limit_list[0]; + } + list.splice(0,limit_list[0]); + } else { + list.length = limit_list[0]; + } + } + }, + //////////////////////////////////////////////////////////// + result_list = [], result_list_tmp = [], j; + object_list = object_list || []; + for (j=0; j<object_list.length; ++j) { + if ( itemMatchesQuery ( + object_list[j], scope.ComplexQueries.parse (query.query) + )) { + result_list.push(object_list[j]); + } + } + if (query.filter) { + select(result_list,query.filter.select_list || []); + sort(result_list,query.filter.sort_on || []); + limit(result_list,query.filter.limit || []); + } + return result_list; + } +}); + +}(jIO)); diff --git a/jio.js b/jio.js new file mode 100644 index 0000000000000000000000000000000000000000..b304bee52b9206d431831aa922d97404ecec366c --- /dev/null +++ b/jio.js @@ -0,0 +1,2509 @@ +(function (scope, hex_md5) { + "use strict"; + var localstorage; + if (typeof localStorage !== "undefined") { + localstorage = { + getItem: function (item) { + var value = localStorage.getItem(item); + return value === null ? null : JSON.parse(value); + }, + setItem: function (item, value) { + return localStorage.setItem(item, JSON.stringify(value)); + }, + removeItem: function (item) { + delete localStorage[item]; + }, + clone: function () { + return JSON.parse(JSON.stringify(localStorage)); + } + }; + } else { + (function () { + var pseudo_localStorage = {}; + localstorage = { + getItem: function (item) { + var value = pseudo_localStorage[item]; + return value === undefined ? + null : JSON.parse(pseudo_localStorage[item]); + }, + setItem: function (item, value) { + pseudo_localStorage[item] = JSON.stringify(value); + }, + removeItem: function (item) { + delete pseudo_localStorage[item]; + }, + clone: function () { + return JSON.parse(JSON.stringify(pseudo_localStorage)); + } + }; + }()); + } +/*jslint indent:2, maxlen: 80, sloppy: true */ +var jioException = function (spec, my) { + var that = {}; + spec = spec || {}; + my = my || {}; + that.name = 'jioException'; + that.message = spec.message || 'Unknown Reason.'; + that.toString = function () { + return that.name + ': ' + that.message; + }; + return that; +}; + +var invalidCommandState = function (spec, my) { + var that = jioException(spec, my), command = spec.command; + spec = spec || {}; + that.name = 'invalidCommandState'; + that.toString = function () { + return that.name + ': ' + + command.getLabel() + ', ' + that.message; + }; + return that; +}; + +var invalidStorage = function (spec, my) { + var that = jioException(spec, my), type = spec.storage.getType(); + spec = spec || {}; + that.name = 'invalidStorage'; + that.toString = function () { + return that.name + ': ' + + 'Type "' + type + '", ' + that.message; + }; + return that; +}; + +var invalidStorageType = function (spec, my) { + var that = jioException(spec, my), type = spec.type; + that.name = 'invalidStorageType'; + that.toString = function () { + return that.name + ': ' + + type + ', ' + that.message; + }; + return that; +}; + +var jobNotReadyException = function (spec, my) { + var that = jioException(spec, my); + that.name = 'jobNotReadyException'; + return that; +}; + +var tooMuchTriesJobException = function (spec, my) { + var that = jioException(spec, my); + that.name = 'tooMuchTriesJobException'; + return that; +}; + +var invalidJobException = function (spec, my) { + var that = jioException(spec, my); + that.name = 'invalidJobException'; + return that; +}; +var jio = function(spec) { +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global command: true, jobManager: true, job: true */ +var storage = function (spec, my) { + var that = {}, priv = {}; + spec = spec || {}; + my = my || {}; + // Attributes // + priv.type = spec.type || ''; + + // Methods // + Object.defineProperty(that, "getType", { + configurable: false, + enumerable: false, + writable: false, + value: function () { + return priv.type; + } + }); + + /** + * Execute the command on this storage. + * @method execute + * @param {object} command The command + */ + that.execute = function (command) { + that.success = command.success; + that.error = command.error; + that.retry = command.retry; + that.end = command.end; + if (that.validate(command)) { + command.executeOn(that); + } + }; + + /** + * Override this function to validate specifications. + * @method isValid + * @return {boolean} true if ok, else false. + */ + that.isValid = function () { + return true; + }; + + that.validate = function () { + var mess = that.validateState(); + if (mess) { + that.error({ + "status": 0, + "statusText": "Invalid Storage", + "error": "invalid_storage", + "message": mess, + "reason": mess + }); + return false; + } + return true; + }; + + /** + * Returns a serialized version of this storage. + * @method serialized + * @return {object} The serialized storage. + */ + that.serialized = function () { + var o = that.specToStore() || {}; + o.type = that.getType(); + return o; + }; + + /** + * Returns an object containing spec to store on localStorage, in order to + * be restored later if something wrong happen. + * Override this method! + * @method specToStore + * @return {object} The spec to store + */ + that.specToStore = function () { + return {}; + }; + + /** + * Validate the storage state. It returns a empty string all is ok. + * @method validateState + * @return {string} empty: ok, else error message. + */ + that.validateState = function () { + return ''; + }; + + that.post = function () { + setTimeout(function () { + that.error({ + "status": 0, + "statusText": "Not Implemented", + "error": "not_implemented", + "message": "\"Post\" command is not implemented", + "reason": "Command not implemented" + }); + }); + }; + + that.put = function () { + setTimeout(function () { + that.error({ + "status": 0, + "statusText": "Not Implemented", + "error": "not_implemented", + "message": "\"Put\" command is not implemented", + "reason": "Command not implemented" + }); + }); + }; + + that.putAttachment = function () { + setTimeout(function () { + that.error({ + "status": 0, + "statusText": "Not Implemented", + "error": "not_implemented", + "message": "\"PutAttachment\" command is not implemented", + "reason": "Command not implemented" + }); + }); + }; + + that.get = function () { + setTimeout(function () { + that.error({ + "status": 0, + "statusText": "Not Implemented", + "error": "not_implemented", + "message": "\"Get\" command is not implemented", + "reason": "Command not implemented" + }); + }); + }; + + that.allDocs = function () { + setTimeout(function () { + that.error({ + "status": 0, + "statusText": "Not Implemented", + "error": "not_implemented", + "message": "\"AllDocs\" command is not implemented", + "reason": "Command not implemented" + }); + }); + }; + + that.remove = function () { + setTimeout(function () { + that.error({ + "status": 0, + "statusText": "Not Implemented", + "error": "not_implemented", + "message": "\"Remove\" command is not implemented", + "reason": "Command not implemented" + }); + }); + }; + + that.success = function () {}; + that.retry = function () {}; + that.error = function () {}; + that.end = function () {}; // terminate the current job. + + priv.newCommand = function (method, spec) { + var o = spec || {}; + o.label = method; + return command(o, my); + }; + + priv.storage = my.storage; + delete my.storage; + + that.addJob = function (method, storage_spec, doc, option, success, error) { + var command_opt = { + options: option, + callbacks: {success: success, error: error} + }; + if (doc) { + if (method === 'get') { + command_opt.docid = doc; + } else { + command_opt.doc = doc; + } + } + jobManager.addJob(job({ + storage: priv.storage(storage_spec || {}), + command: priv.newCommand(method, command_opt) + }, my)); + }; + + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global command: true */ +var allDocsCommand = function (spec, my) { + var that = command(spec, my); + + spec = spec || {}; + my = my || {}; + // Attributes // + // Methods // + that.getLabel = function () { + return 'allDocs'; + }; + + that.executeOn = function (storage) { + storage.allDocs(that); + }; + + that.canBeRestored = function () { + return false; + }; + + that.validateState = function () { + return true; + }; + + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */ +/*global postCommand: true, putCommand: true, getCommand: true, + removeCommand: true, allDocsCommand: true, + putAttachmentCommand: true, failStatus: true, doneStatus: true, + hex_md5: true */ +var command = function (spec, my) { + var that = {}, + priv = {}; + + spec = spec || {}; + my = my || {}; + + priv.commandlist = { + 'post': postCommand, + 'put': putCommand, + 'get': getCommand, + 'remove': removeCommand, + 'allDocs': allDocsCommand, + 'putAttachment': putAttachmentCommand + }; + // creates the good command thanks to his label + if (spec.label && priv.commandlist[spec.label]) { + priv.label = spec.label; + delete spec.label; + return priv.commandlist[priv.label](spec, my); + } + + priv.tried = 0; + priv.doc = spec.doc || {}; + if (typeof priv.doc !== "object") { + priv.doc = { + "_id": priv.doc.toString() + }; + } + priv.docid = spec.docid || priv.doc._id; + priv.option = spec.options || {}; + priv.callbacks = spec.callbacks || {}; + priv.success = priv.callbacks.success || function () {}; + priv.error = priv.callbacks.error || function () {}; + priv.retry = function () { + that.error({ + status: 13, + statusText: 'Fail Retry', + error: 'fail_retry', + message: 'Impossible to retry.', + reason: 'Impossible to retry.' + }); + }; + priv.end = function () {}; + priv.on_going = false; + + // Methods // + /** + * Returns a serialized version of this command. + * @method serialized + * @return {object} The serialized command. + */ + that.serialized = function () { + var o = {}; + o.label = that.getLabel(); + o.tried = priv.tried; + o.doc = that.cloneDoc(); + o.option = that.cloneOption(); + return o; + }; + + /** + * Returns the label of the command. + * @method getLabel + * @return {string} The label. + */ + that.getLabel = function () { + return 'command'; + }; + + /** + * Gets the document id + * @method getDocId + * @return {string} The document id + */ + that.getDocId = function () { + if (typeof priv.docid !== "string") { + return undefined; + } + return priv.docid.split('/')[0]; + }; + + /** + * Gets the attachment id + * @method getAttachmentId + * @return {string} The attachment id + */ + that.getAttachmentId = function () { + if (typeof priv.docid !== "string") { + return undefined; + } + return priv.docid.split('/')[1]; + }; + + /** + * Returns the label of the command. + * @method getDoc + * @return {object} The document. + */ + that.getDoc = function () { + return priv.doc; + }; + + /** + * Returns the data of the attachment + * @method getAttachmentData + * @return {string} The data + */ + that.getAttachmentData = function () { + return priv.doc._data || ""; + }; + + /** + * Returns the data length of the attachment + * @method getAttachmentLength + * @return {number} The length + */ + that.getAttachmentLength = function () { + return (priv.doc._data || "").length; + }; + + /** + * Returns the mimetype of the attachment + * @method getAttachmentMimeType + * @return {string} The mimetype + */ + that.getAttachmentMimeType = function () { + return priv.doc._mimetype; + }; + + /** + * Generate the md5sum of the attachment data + * @method md5SumAttachmentData + * @return {string} The md5sum + */ + that.md5SumAttachmentData = function () { + return hex_md5(priv.doc._data || ""); + }; + + /** + * Returns an information about the document. + * @method getDocInfo + * @param {string} infoname The info name. + * @return The info value. + */ + that.getDocInfo = function (infoname) { + return priv.doc[infoname]; + }; + + /** + * Returns the value of an option. + * @method getOption + * @param {string} optionname The option name. + * @return The option value. + */ + that.getOption = function (optionname) { + return priv.option[optionname]; + }; + + /** + * Validates the storage. + * @param {object} storage The storage. + */ + that.validate = function (storage) { + if (typeof priv.docid === "string" && + !priv.docid.match("^[^\/]+([\/][^\/]+)?$")) { + that.error({ + status: 21, + statusText: 'Invalid Document Id', + error: 'invalid_document_id', + message: 'The document id must be like "abc" or "abc/def".', + reason: 'The document id is no like "abc" or "abc/def"' + }); + return false; + } + if (!that.validateState()) { + return false; + } + return storage.validate(); + }; + + /* + * Extend this function + */ + that.validateState = function () { + return true; + }; + + /** + * Check if the command can be retried. + * @method canBeRetried + * @return {boolean} The result + */ + that.canBeRetried = function () { + return (priv.option.max_retry === undefined || + priv.option.max_retry === 0 || + priv.tried < priv.option.max_retry); + }; + + /** + * Gets the number time the command has been tried. + * @method getTried + * @return {number} The number of time the command has been tried + */ + that.getTried = function () { + return priv.tried; + }; + + /** + * Delegate actual excecution the storage. + * @param {object} storage The storage. + */ + that.execute = function (storage) { + if (!priv.on_going) { + if (that.validate(storage)) { + priv.tried += 1; + priv.on_going = true; + storage.execute(that); + } + } + }; + /** + * Execute the good method from the storage. + * Override this function. + * @method executeOn + * @param {object} storage The storage. + */ + that.executeOn = function (storage) {}; + that.success = function (return_value) { + priv.on_going = false; + priv.success(return_value); + priv.end(doneStatus()); + }; + that.retry = function (return_error) { + priv.on_going = false; + if (that.canBeRetried()) { + priv.retry(); + } else { + that.error(return_error); + } + }; + that.error = function (return_error) { + priv.on_going = false; + priv.error(return_error); + priv.end(failStatus()); + }; + that.end = function () { + priv.end(doneStatus()); + }; + that.onSuccessDo = function (fun) { + if (fun) { + priv.success = fun; + } else { + return priv.success; + } + }; + that.onErrorDo = function (fun) { + if (fun) { + priv.error = fun; + } else { + return priv.error; + } + }; + that.onEndDo = function (fun) { + priv.end = fun; + }; + that.onRetryDo = function (fun) { + priv.retry = fun; + }; + /** + * Is the command can be restored by another JIO : yes. + * @method canBeRestored + * @return {boolean} true + */ + that.canBeRestored = function () { + return true; + }; + /** + * Clones the command and returns it. + * @method clone + * @return {object} The cloned command. + */ + that.clone = function () { + return command(that.serialized(), my); + }; + /** + * Clones the command options and returns the clone version. + * @method cloneOption + * @return {object} The clone of the command options. + */ + that.cloneOption = function () { + return JSON.parse(JSON.stringify(priv.option)); + }; + /** + * Clones the document and returns the clone version. + * @method cloneDoc + * @return {object} The clone of the document. + */ + that.cloneDoc = function () { + return JSON.parse(JSON.stringify(priv.doc)); + }; + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global command: true */ +var getCommand = function (spec, my) { + var that = command(spec, my); + + spec = spec || {}; + my = my || {}; + // Attributes // + // Methods // + that.getLabel = function () { + return 'get'; + }; + + that.validateState = function () { + if (!(typeof that.getDocId() === "string" && + that.getDocId() !== "")) { + that.error({ + "status": 20, + "statusText": "Document Id Required", + "error": "document_id_required", + "message": "The document id is not provided", + "reason": "Document id is undefined" + }); + return false; + } + if (typeof that.getAttachmentId() === "string") { + if (that.getAttachmentId() === "") { + that.error({ + "status": 23, + "statusText": "Invalid Attachment Id", + "error": "invalid_attachment_id", + "message": "The attachment id must not be an empty string", + "reason": "Attachment id is empty" + }); + return false; + } + } + return true; + }; + + that.executeOn = function (storage) { + storage.get(that); + }; + + that.canBeRestored = function () { + return false; + }; + + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global command: true */ +var postCommand = function (spec, my) { + var that = command(spec, my); + + spec = spec || {}; + my = my || {}; + + // Methods // + that.getLabel = function () { + return 'post'; + }; + + that.validateState = function () { + if (that.getAttachmentId() !== undefined) { + that.error({ + "status": 21, + "statusText": "Invalid Document Id", + "error": "invalid_document_id", + "message": "The document id contains '/' characters " + + "which are forbidden", + "reason": "Document id contains '/' character(s)" + }); + return false; + } + return true; + }; + that.executeOn = function (storage) { + storage.post(that); + }; + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global command: true */ +var putAttachmentCommand = function (spec, my) { + var that = command(spec, my); + spec = spec || {}; + my = my || {}; + // Attributes // + // Methods // + that.getLabel = function () { + return 'putAttachment'; + }; + + that.executeOn = function (storage) { + storage.putAttachment(that); + }; + + that.validateState = function () { + if (!(typeof that.getDocId() === "string" && that.getDocId() !== + "")) { + that.error({ + "status": 20, + "statusText": "Document Id Required", + "error": "document_id_required", + "message": "The document id is not provided", + "reason": "Document id is undefined" + }); + return false; + } + if (typeof that.getAttachmentId() !== "string") { + that.error({ + "status": 22, + "statusText": "Attachment Id Required", + "error": "attachment_id_required", + "message": "The attachment id must be set", + "reason": "Attachment id not set" + }); + return false; + } + if (that.getAttachmentId() === "") { + that.error({ + "status": 23, + "statusText": "Invalid Attachment Id", + "error": "invalid_attachment_id", + "message": "The attachment id must not be an empty string", + "reason": "Attachment id is empty" + }); + } + return true; + }; + + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global command: true */ +var putCommand = function (spec, my) { + var that = command(spec, my); + spec = spec || {}; + my = my || {}; + + // Methods // + that.getLabel = function () { + return 'put'; + }; + + that.validateState = function () { + if (!(typeof that.getDocId() === "string" && that.getDocId() !== + "")) { + that.error({ + "status": 20, + "statusText": "Document Id Required", + "error": "document_id_required", + "message": "The document id is not provided", + "reason": "Document id is undefined" + }); + return false; + } + if (that.getAttachmentId() !== undefined) { + that.error({ + "status": 21, + "statusText": "Invalid Document Id", + "error": "invalid_document_id", + "message": "The document id contains '/' characters " + + "which are forbidden", + "reason": "Document id contains '/' character(s)" + }); + return false; + } + return true; + }; + that.executeOn = function (storage) { + storage.put(that); + }; + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global command: true */ +var removeCommand = function (spec, my) { + var that = command(spec, my); + spec = spec || {}; + my = my || {}; + // Attributes // + // Methods // + that.getLabel = function () { + return 'remove'; + }; + + that.validateState = function () { + if (!(typeof that.getDocId() === "string" && that.getDocId() !== + "")) { + that.error({ + "status": 20, + "statusText": "Document Id Required", + "error": "document_id_required", + "message": "The document id is not provided", + "reason": "Document id is undefined" + }); + return false; + } + if (typeof that.getAttachmentId() === "string") { + if (that.getAttachmentId() === "") { + that.error({ + "status": 23, + "statusText": "Invalid Attachment Id", + "error": "invalid_attachment_id", + "message": "The attachment id must not be an empty string", + "reason": "Attachment id is empty" + }); + return false; + } + } + return true; + }; + + that.executeOn = function (storage) { + storage.remove(that); + }; + + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global jobStatus: true */ +var doneStatus = function (spec, my) { + var that = jobStatus(spec, my); + spec = spec || {}; + my = my || {}; + // Attributes // + // Methods // + that.getLabel = function () { + return 'done'; + }; + + that.canStart = function () { + return false; + }; + that.canRestart = function () { + return false; + }; + + that.isDone = function () { + return true; + }; + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global jobStatus: true */ +var failStatus = function (spec, my) { + var that = jobStatus(spec, my); + spec = spec || {}; + my = my || {}; + // Attributes // + // Methods // + that.getLabel = function () { + return 'fail'; + }; + + that.canStart = function () { + return false; + }; + that.canRestart = function () { + return true; + }; + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global jobStatus: true */ +var initialStatus = function (spec, my) { + var that = jobStatus(spec, my); + spec = spec || {}; + my = my || {}; + // Attributes // + // Methods // + that.getLabel = function () { + return "initial"; + }; + + that.canStart = function () { + return true; + }; + that.canRestart = function () { + return true; + }; + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global jobStatus: true */ +var jobStatus = function (spec, my) { + var that = {}; + spec = spec || {}; + my = my || {}; + // Attributes // + // Methods // + that.getLabel = function () { + return 'job status'; + }; + + that.canStart = function () {}; + that.canRestart = function () {}; + + that.serialized = function () { + return {"label": that.getLabel()}; + }; + + that.isWaitStatus = function () { + return false; + }; + + that.isDone = function () { + return false; + }; + + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global jobStatus: true */ +var onGoingStatus = function (spec, my) { + var that = jobStatus(spec, my); + spec = spec || {}; + my = my || {}; + // Attributes // + // Methods // + that.getLabel = function () { + return 'on going'; + }; + + that.canStart = function () { + return false; + }; + that.canRestart = function () { + return false; + }; + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global jobStatus: true, jobManager: true */ +var waitStatus = function (spec, my) { + var that = jobStatus(spec, my), priv = {}; + spec = spec || {}; + my = my || {}; + // Attributes // + priv.job_id_array = spec.job_id_array || []; + priv.threshold = 0; + + // Methods // + /** + * Returns the label of this status. + * @method getLabel + * @return {string} The label: 'wait'. + */ + that.getLabel = function () { + return 'wait'; + }; + + /** + * Refresh the job id array to wait. + * @method refreshJobIdArray + */ + priv.refreshJobIdArray = function () { + var tmp_job_id_array = [], i; + for (i = 0; i < priv.job_id_array.length; i += 1) { + if (jobManager.jobIdExists(priv.job_id_array[i])) { + tmp_job_id_array.push(priv.job_id_array[i]); + } + } + priv.job_id_array = tmp_job_id_array; + }; + + /** + * The status must wait for the job end before start again. + * @method waitForJob + * @param {object} job The job to wait for. + */ + that.waitForJob = function (job) { + var i; + for (i = 0; i < priv.job_id_array.length; i += 1) { + if (priv.job_id_array[i] === job.getId()) { + return; + } + } + priv.job_id_array.push(job.getId()); + }; + + /** + * The status stops to wait for this job. + * @method dontWaitForJob + * @param {object} job The job to stop waiting for. + */ + that.dontWaitForJob = function (job) { + var i, tmp_job_id_array = []; + for (i = 0; i < priv.job_id_array.length; i += 1) { + if (priv.job_id_array[i] !== job.getId()) { + tmp_job_id_array.push(priv.job_id_array[i]); + } + } + priv.job_id_array = tmp_job_id_array; + }; + + /** + * The status must wait for some milliseconds. + * @method waitForTime + * @param {number} ms The number of milliseconds + */ + that.waitForTime = function (ms) { + priv.threshold = Date.now() + ms; + }; + + /** + * The status stops to wait for some time. + * @method stopWaitForTime + */ + that.stopWaitForTime = function () { + priv.threshold = 0; + }; + + that.canStart = function () { + priv.refreshJobIdArray(); + return (priv.job_id_array.length === 0 && Date.now() >= priv.threshold); + }; + that.canRestart = function () { + return that.canStart(); + }; + + that.serialized = function () { + return { + "label": that.getLabel(), + "waitfortime": priv.threshold, + "waitforjob": priv.job_id_array + }; + }; + + /** + * Checks if this status is waitStatus + * @method isWaitStatus + * @return {boolean} true + */ + that.isWaitStatus = function () { + return true; + }; + + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global jobIdHandler: true, initialStatus: true, invalidJobException: true, +waitStatus: true, failStatus: true, tooMuchTriesJobException: true, +jobManager: true, jobNotReadyException: true, onGoingStatus: true */ +var job = function (spec) { + var that = {}, + priv = {}; + + spec = spec || {}; + + priv.id = jobIdHandler.nextId(); + priv.command = spec.command; + priv.storage = spec.storage; + priv.status = initialStatus(); + priv.date = new Date(); + + // Initialize // + if (!priv.storage) { + throw invalidJobException({ + job: that, + message: 'No storage set' + }); + } + if (!priv.command) { + throw invalidJobException({ + job: that, + message: 'No command set' + }); + } + // Methods // + /** + * Returns the job command. + * @method getCommand + * @return {object} The job command. + */ + that.getCommand = function () { + return priv.command; + }; + + that.getStatus = function () { + return priv.status; + }; + + that.getId = function () { + return priv.id; + }; + + that.getStorage = function () { + return priv.storage; + }; + + that.getDate = function () { + return priv.date; + }; + + /** + * Checks if the job is ready. + * @method isReady + * @return {boolean} true if ready, else false. + */ + that.isReady = function () { + if (priv.command.getTried() === 0) { + return priv.status.canStart(); + } + return priv.status.canRestart(); + }; + + /** + * Returns a serialized version of this job. + * @method serialized + * @return {object} The serialized job. + */ + that.serialized = function () { + return { + id: priv.id, + date: priv.date.getTime(), + status: priv.status.serialized(), + command: priv.command.serialized(), + storage: priv.storage.serialized() + }; + }; + + /** + * Tells the job to wait for another one. + * @method waitForJob + * @param {object} job The job to wait for. + */ + that.waitForJob = function (job) { + if (priv.status.getLabel() !== 'wait') { + priv.status = waitStatus({}); + } + priv.status.waitForJob(job); + }; + + /** + * Tells the job to do not wait for a job. + * @method dontWaitForJob + * @param {object} job The other job. + */ + that.dontWaitFor = function (job) { + if (priv.status.getLabel() === 'wait') { + priv.status.dontWaitForJob(job); + } + }; + + /** + * Tells the job to wait for a while. + * @method waitForTime + * @param {number} ms Time to wait in millisecond. + */ + that.waitForTime = function (ms) { + if (priv.status.getLabel() !== 'wait') { + priv.status = waitStatus({}); + } + priv.status.waitForTime(ms); + }; + + /** + * Tells the job to do not wait for a while anymore. + * @method stopWaitForTime + */ + that.stopWaitForTime = function () { + if (priv.status.getLabel() === 'wait') { + priv.status.stopWaitForTime(); + } + }; + + that.eliminated = function () { + priv.command.error({ + status: 10, + statusText: 'Stopped', + error: 'stopped', + message: 'This job has been stopped by another one.', + reason: 'this job has been stopped by another one' + }); + }; + + that.notAccepted = function () { + priv.command.onEndDo(function () { + priv.status = failStatus(); + jobManager.terminateJob(that); + }); + priv.command.error({ + status: 11, + statusText: 'Not Accepted', + error: 'not_accepted', + message: 'This job is already running.', + reason: 'this job is already running' + }); + }; + + /** + * Updates the date of the job with the another one. + * @method update + * @param {object} job The other job. + */ + that.update = function (job) { + priv.command.error({ + status: 12, + statusText: 'Replaced', + error: 'replaced', + message: 'Job has been replaced by another one.', + reason: 'job has been replaced by another one' + }); + priv.date = new Date(job.getDate().getTime()); + priv.command = job.getCommand(); + priv.status = job.getStatus(); + }; + + /** + * Executes this job. + * @method execute + */ + that.execute = function () { + if (!that.getCommand().canBeRetried()) { + throw tooMuchTriesJobException({ + job: that, + message: 'The job was invoked too much time.' + }); + } + if (!that.isReady()) { + throw jobNotReadyException({ + job: that, + message: 'Can not execute this job.' + }); + } + priv.status = onGoingStatus(); + priv.command.onRetryDo(function () { + var ms = priv.command.getTried(); + ms = ms * ms * 200; + if (ms > 10000) { + ms = 10000; + } + that.waitForTime(ms); + }); + priv.command.onEndDo(function (status) { + priv.status = status; + jobManager.terminateJob(that); + }); + priv.command.execute(priv.storage); + }; + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global announcement: true */ +var announcement = function (spec, my) { + var that = {}, + callback_a = [], + announcer = spec.announcer || {}; + + spec = spec || {}; + my = my || {}; + + // Methods // + that.add = function (callback) { + callback_a.push(callback); + }; + + that.remove = function (callback) { + var i, tmp_callback_a = []; + for (i = 0; i < callback_a.length; i += 1) { + if (callback_a[i] !== callback) { + tmp_callback_a.push(callback_a[i]); + } + } + callback_a = tmp_callback_a; + }; + + that.register = function () { + announcer.register(that); + }; + + that.unregister = function () { + announcer.unregister(that); + }; + + that.trigger = function (args) { + var i; + for (i = 0; i < callback_a.length; i += 1) { + callback_a[i].apply(null, args); + } + }; + + return that; +}; +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global localstorage: true, setInterval: true, clearInterval: true */ +var activityUpdater = (function (spec, my) { + var that = {}, priv = {}; + spec = spec || {}; + my = my || {}; + + priv.id = spec.id || 0; + priv.interval = 400; + priv.interval_id = null; + + // Methods // + /** + * Update the last activity date in the localStorage. + * @method touch + */ + priv.touch = function () { + localstorage.setItem('jio/id/' + priv.id, Date.now()); + }; + + /** + * Sets the jio id into the activity. + * @method setId + * @param {number} id The jio id. + */ + that.setId = function (id) { + priv.id = id; + }; + + /** + * Sets the interval delay between two updates. + * @method setIntervalDelay + * @param {number} ms In milliseconds + */ + that.setIntervalDelay = function (ms) { + priv.interval = ms; + }; + + /** + * Gets the interval delay. + * @method getIntervalDelay + * @return {number} The interval delay. + */ + that.getIntervalDelay = function () { + return priv.interval; + }; + + /** + * Starts the activity updater. It will update regulary the last activity + * date in the localStorage to show to other jio instance that this instance + * is active. + * @method start + */ + that.start = function () { + if (!priv.interval_id) { + priv.touch(); + priv.interval_id = setInterval(function () { + priv.touch(); + }, priv.interval); + } + }; + + /** + * Stops the activity updater. + * @method stop + */ + that.stop = function () { + if (priv.interval_id !== null) { + clearInterval(priv.interval_id); + priv.interval_id = null; + } + }; + + return that; +}()); +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global announcement: true */ +var announcer = (function (spec, my) { + var that = {}, + announcement_o = {}; + spec = spec || {}; + my = my || {}; + + // Methods // + that.register = function (name) { + if (!announcement_o[name]) { + announcement_o[name] = announcement(); + } + }; + + that.unregister = function (name) { + if (announcement_o[name]) { + delete announcement_o[name]; + } + }; + + that.at = function (name) { + return announcement_o[name]; + }; + + that.on = function (name, callback) { + that.register(name); + that.at(name).add(callback); + }; + + that.trigger = function (name, args) { + that.at(name).trigger(args); + }; + + return that; +}()); +/*jslint indent: 2, maxlen: 80, sloppy: true */ +var jobIdHandler = (function (spec) { + var that = {}, + id = 0; + spec = spec || {}; + + // Methods // + that.nextId = function () { + id = id + 1; + return id; + }; + + return that; +}()); +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global localstorage: true, setInterval: true, clearInterval: true, + command: true, job: true, jobRules: true */ +var jobManager = (function (spec) { + var that = {}, + job_array_name = 'jio/job_array', + priv = {}; + + spec = spec || {}; + // Attributes // + priv.id = spec.id; + priv.interval_id = null; + priv.interval = 200; + priv.job_array = []; + + // Methods // + /** + * Get the job array name in the localStorage + * @method getJobArrayName + * @return {string} The job array name + */ + priv.getJobArrayName = function () { + return job_array_name + '/' + priv.id; + }; + + /** + * Returns the job array from the localStorage + * @method getJobArray + * @return {array} The job array. + */ + priv.getJobArray = function () { + return localstorage.getItem(priv.getJobArrayName()) || []; + }; + + /** + * Does a backup of the job array in the localStorage. + * @method copyJobArrayToLocal + */ + priv.copyJobArrayToLocal = function () { + var new_a = [], + i; + for (i = 0; i < priv.job_array.length; i += 1) { + new_a.push(priv.job_array[i].serialized()); + } + localstorage.setItem(priv.getJobArrayName(), new_a); + }; + + /** + * Removes a job from the current job array. + * @method removeJob + * @param {object} job The job object. + */ + priv.removeJob = function (job) { + var i, + tmp_job_array = []; + for (i = 0; i < priv.job_array.length; i += 1) { + if (priv.job_array[i] !== job) { + tmp_job_array.push(priv.job_array[i]); + } + } + priv.job_array = tmp_job_array; + priv.copyJobArrayToLocal(); + }; + + /** + * Sets the job manager id. + * @method setId + * @param {number} id The id. + */ + that.setId = function (id) { + priv.id = id; + }; + + /** + * Starts listening to the job array, executing them regulary. + * @method start + */ + that.start = function () { + var i; + if (priv.interval_id === null) { + priv.interval_id = setInterval(function () { + priv.restoreOldJio(); + for (i = 0; i < priv.job_array.length; i += 1) { + that.execute(priv.job_array[i]); + } + }, priv.interval); + } + }; + + /** + * Stops listening to the job array. + * @method stop + */ + that.stop = function () { + if (priv.interval_id !== null) { + clearInterval(priv.interval_id); + priv.interval_id = null; + if (priv.job_array.length === 0) { + localstorage.removeItem(priv.getJobArrayName()); + } + } + }; + + /** + * Try to restore an the inactive older jio instances. + * It will restore the on going or initial jobs from their job array + * and it will add them to this job array. + * @method restoreOldJio + */ + priv.restoreOldJio = function () { + var i, + jio_id_a; + priv.lastrestore = priv.lastrestore || 0; + if (priv.lastrestore > (Date.now()) - 2000) { + return; + } + jio_id_a = localstorage.getItem('jio/id_array') || []; + for (i = 0; i < jio_id_a.length; i += 1) { + priv.restoreOldJioId(jio_id_a[i]); + } + priv.lastrestore = Date.now(); + }; + + /** + * Try to restore an old jio according to an id. + * @method restoreOldJioId + * @param {number} id The jio id. + */ + priv.restoreOldJioId = function (id) { + var jio_date; + jio_date = localstorage.getItem('jio/id/' + id) || 0; + if (new Date(jio_date).getTime() < (Date.now() - 10000)) { // 10 sec + priv.restoreOldJobFromJioId(id); + priv.removeOldJioId(id); + priv.removeJobArrayFromJioId(id); + } + }; + + /** + * Try to restore all jobs from another jio according to an id. + * @method restoreOldJobFromJioId + * @param {number} id The jio id. + */ + priv.restoreOldJobFromJioId = function (id) { + var i, + command_object, + jio_job_array; + jio_job_array = localstorage.getItem('jio/job_array/' + id) || []; + for (i = 0; i < jio_job_array.length; i += 1) { + command_object = command(jio_job_array[i].command); + if (command_object.canBeRestored()) { + that.addJob(job({ + storage: that.storage(jio_job_array[i].storage), + command: command_object + })); + } + } + }; + /** + * Removes a jio instance according to an id. + * @method removeOldJioId + * @param {number} id The jio id. + */ + priv.removeOldJioId = function (id) { + var i, + jio_id_array, + new_array = []; + jio_id_array = localstorage.getItem('jio/id_array') || []; + for (i = 0; i < jio_id_array.length; i += 1) { + if (jio_id_array[i] !== id) { + new_array.push(jio_id_array[i]); + } + } + localstorage.setItem('jio/id_array', new_array); + localstorage.removeItem('jio/id/' + id); + }; + /** + * Removes a job array from a jio instance according to an id. + * @method removeJobArrayFromJioId + * @param {number} id The jio id. + */ + priv.removeJobArrayFromJioId = function (id) { + localstorage.removeItem('jio/job_array/' + id); + }; + /** + * Executes a job. + * @method execute + * @param {object} job The job object. + */ + that.execute = function (job) { + try { + job.execute(); + } catch (e) { + switch (e.name) { + case 'jobNotReadyException': + break; // do nothing + case 'tooMuchTriesJobException': + break; // do nothing + default: + throw e; + } + } + priv.copyJobArrayToLocal(); + }; + /** + * Checks if a job exists in the job array according to a job id. + * @method jobIdExists + * @param {number} id The job id. + * @return {boolean} true if exists, else false. + */ + that.jobIdExists = function (id) { + var i; + for (i = 0; i < priv.job_array.length; i += 1) { + if (priv.job_array[i].getId() === id) { + return true; + } + } + return false; + }; + /** + * Terminate a job. It only remove it from the job array. + * @method terminateJob + * @param {object} job The job object + */ + that.terminateJob = function (job) { + priv.removeJob(job); + }; + /** + * Adds a job to the current job array. + * @method addJob + * @param {object} job The new job. + */ + that.addJob = function (job) { + var result_array = that.validateJobAccordingToJobList(priv.job_array, job); + priv.appendJob(job, result_array); + }; + /** + * Generate a result array containing action string to do with the good job. + * @method validateJobAccordingToJobList + * @param {array} job_array A job array. + * @param {object} job The new job to compare with. + * @return {array} A result array. + */ + that.validateJobAccordingToJobList = function (job_array, job) { + var i, + result_array = []; + for (i = 0; i < job_array.length; i += 1) { + result_array.push(jobRules.validateJobAccordingToJob(job_array[i], job)); + } + return result_array; + }; + /** + * It will manage the job in order to know what to do thanks to a result + * array. The new job can be added to the job array, but it can also be + * not accepted. It is this method which can tells jobs to wait for another + * one, to replace one or to eliminate some while browsing. + * @method appendJob + * @param {object} job The job to append. + * @param {array} result_array The result array. + */ + priv.appendJob = function (job, result_array) { + var i; + if (priv.job_array.length !== result_array.length) { + throw new RangeError("Array out of bound"); + } + for (i = 0; i < result_array.length; i += 1) { + if (result_array[i].action === 'dont accept') { + return job.notAccepted(); + } + } + for (i = 0; i < result_array.length; i += 1) { + switch (result_array[i].action) { + case 'eliminate': + result_array[i].job.eliminated(); + priv.removeJob(result_array[i].job); + break; + case 'update': + result_array[i].job.update(job); + priv.copyJobArrayToLocal(); + return; + case 'wait': + job.waitForJob(result_array[i].job); + break; + default: + break; + } + } + priv.job_array.push(job); + priv.copyJobArrayToLocal(); + }; + that.serialized = function () { + var a = [], + i, + job_array = priv.job_array || []; + for (i = 0; i < job_array.length; i += 1) { + a.push(job_array[i].serialized()); + } + return a; + }; + return that; +}()); +/*jslint indent: 2, maxlen: 80, sloppy: true */ +var jobRules = (function () { + var that = {}, priv = {}; + + priv.compare = {}; + priv.action = {}; + + Object.defineProperty(that, "eliminate", { + configurable: false, + enumerable: false, + writable: false, + value: function () { + return 'eliminate'; + } + }); + Object.defineProperty(that, "update", { + configurable: false, + enumerable: false, + writable: false, + value: function () { + return 'update'; + } + }); + Object.defineProperty(that, "dontAccept", { + configurable: false, + enumerable: false, + writable: false, + value: function () { + return 'dont accept'; + } + }); + Object.defineProperty(that, "wait", { + configurable: false, + enumerable: false, + writable: false, + value: function () { + return 'wait'; + } + }); + Object.defineProperty(that, "none", { + configurable: false, + enumerable: false, + writable: false, + value: function () { + return 'none'; + } + }); + that.default_action = that.none; + that.default_compare = function (job1, job2) { + return (job1.getCommand().getDocId() === job2.getCommand().getDocId() && + job1.getCommand().getDocInfo('_rev') === + job2.getCommand().getDocInfo('_rev') && + job1.getCommand().getOption('rev') === + job2.getCommand().getOption('rev') && + JSON.stringify(job1.getStorage().serialized()) === + JSON.stringify(job2.getStorage().serialized())); + }; + + // Methods // + /** + * Returns an action according the jobs given in parameters. + * @method getAction + * @param {object} job1 The already existant job. + * @param {object} job2 The job to compare with. + * @return {string} An action string. + */ + priv.getAction = function (job1, job2) { + var j1label, j2label, j1status; + j1label = job1.getCommand().getLabel(); + j2label = job2.getCommand().getLabel(); + j1status = (job1.getStatus().getLabel() === 'on going' ? + 'on going' : 'not on going'); + if (priv.action[j1label] && priv.action[j1label][j1status] && + priv.action[j1label][j1status][j2label]) { + return priv.action[j1label][j1status][j2label](job1, job2); + } + return that.default_action(job1, job2); + }; + + /** + * Checks if the two jobs are comparable. + * @method canCompare + * @param {object} job1 The already existant job. + * @param {object} job2 The job to compare with. + * @return {boolean} true if comparable, else false. + */ + priv.canCompare = function (job1, job2) { + var job1label = job1.getCommand().getLabel(), + job2label = job2.getCommand().getLabel(); + if (priv.compare[job1label] && priv.compare[job2label]) { + return priv.compare[job1label][job2label](job1, job2); + } + return that.default_compare(job1, job2); + }; + + /** + * Returns an action string to show what to do if we want to add a job. + * @method validateJobAccordingToJob + * @param {object} job1 The current job. + * @param {object} job2 The new job. + * @return {string} The action string. + */ + Object.defineProperty(that, "validateJobAccordingToJob", { + configurable: false, + enumerable: false, + writable: false, + value: function (job1, job2) { + if (priv.canCompare(job1, job2)) { + return { + action: priv.getAction(job1, job2), + job: job1 + }; + } + return { + action: that.default_action(job1, job2), + job: job1 + }; + } + }); + + /** + * Adds a rule the action rules. + * @method addActionRule + * @param {string} method1 The action label from the current job. + * @param {boolean} ongoing Is this action is on going or not? + * @param {string} method2 The action label from the new job. + * @param {function} rule The rule that return an action string. + */ + Object.defineProperty(that, "addActionRule", { + configurable: false, + enumerable: false, + writable: false, + value: function (method1, ongoing, method2, rule) { + var ongoing_s = (ongoing ? 'on going' : 'not on going'); + priv.action[method1] = priv.action[method1] || {}; + priv.action[method1][ongoing_s] = priv.action[method1][ongoing_s] || {}; + priv.action[method1][ongoing_s][method2] = rule; + } + }); + + /** + * Adds a rule the compare rules. + * @method addCompareRule + * @param {string} method1 The action label from the current job. + * @param {string} method2 The action label from the new job. + * @param {function} rule The rule that return a boolean + * - true if job1 and job2 can be compared, else false. + */ + Object.defineProperty(that, "addCompareRule", { + configurable: false, + enumerable: false, + writable: false, + value: function (method1, method2, rule) { + priv.compare[method1] = priv.compare[method1] || {}; + priv.compare[method1][method2] = rule; + } + }); + + //////////////////////////////////////////////////////////////////////////// + // Adding some rules + /* + LEGEND: + - s: storage + - m: method + - n: name + - c: content + - o: options + - =: are equal + - !: are not equal + + select ALL s= n= + removefailordone fail|done + / elim repl nacc wait + Remove !ongoing Save 1 x x x + Save !ongoing Remove 1 x x x + GetList !ongoing GetList 0 1 x x + Remove !ongoing Remove 0 1 x x + Load !ongoing Load 0 1 x x + Save c= !ongoing Save 0 1 x x + Save c! !ongoing Save 0 1 x x + GetList ongoing GetList 0 0 1 x + Remove ongoing Remove 0 0 1 x + Remove ongoing Load 0 0 1 x + Remove !ongoing Load 0 0 1 x + Load ongoing Load 0 0 1 x + Save c= ongoing Save 0 0 1 x + Remove ongoing Save 0 0 0 1 + Load ongoing Remove 0 0 0 1 + Load ongoing Save 0 0 0 1 + Load !ongoing Remove 0 0 0 1 + Load !ongoing Save 0 0 0 1 + Save ongoing Remove 0 0 0 1 + Save ongoing Load 0 0 0 1 + Save c! ongoing Save 0 0 0 1 + Save !ongoing Load 0 0 0 1 + GetList ongoing Remove 0 0 0 0 + GetList ongoing Load 0 0 0 0 + GetList ongoing Save 0 0 0 0 + GetList !ongoing Remove 0 0 0 0 + GetList !ongoing Load 0 0 0 0 + GetList !ongoing Save 0 0 0 0 + Remove ongoing GetList 0 0 0 0 + Remove !ongoing GetList 0 0 0 0 + Load ongoing GetList 0 0 0 0 + Load !ongoing GetList 0 0 0 0 + Save ongoing GetList 0 0 0 0 + Save !ongoing GetList 0 0 0 0 + + For more information, see documentation + */ + that.addActionRule('post', true, 'post', that.dontAccept); + that.addActionRule('post', true, 'put', that.wait); + that.addActionRule('post', true, 'get', that.wait); + that.addActionRule('post', true, 'remove', that.wait); + that.addActionRule('post', true, 'putAttachment', that.wait); + that.addActionRule('post', false, 'post', that.update); + that.addActionRule('post', false, 'put', that.wait); + that.addActionRule('post', false, 'get', that.wait); + that.addActionRule('post', false, 'remove', that.eliminate); + that.addActionRule('post', false, 'putAttachment', that.wait); + + that.addActionRule('put', true, 'post', that.dontAccept); + that.addActionRule('put', true, 'put', that.wait); + that.addActionRule('put', true, 'get', that.wait); + that.addActionRule('put', true, 'remove', that.wait); + that.addActionRule('put', true, 'putAttachment', that.wait); + that.addActionRule('put', false, 'post', that.dontAccept); + that.addActionRule('put', false, 'put', that.update); + that.addActionRule('put', false, 'get', that.wait); + that.addActionRule('put', false, 'remove', that.eliminate); + that.addActionRule('put', false, 'putAttachment', that.wait); + + that.addActionRule('get', true, 'post', that.wait); + that.addActionRule('get', true, 'put', that.wait); + that.addActionRule('get', true, 'get', that.dontAccept); + that.addActionRule('get', true, 'remove', that.wait); + that.addActionRule('get', true, 'putAttachment', that.wait); + that.addActionRule('get', false, 'post', that.wait); + that.addActionRule('get', false, 'put', that.wait); + that.addActionRule('get', false, 'get', that.update); + that.addActionRule('get', false, 'remove', that.wait); + that.addActionRule('get', false, 'putAttachment', that.wait); + + that.addActionRule('remove', true, 'post', that.wait); + that.addActionRule('remove', true, 'get', that.dontAccept); + that.addActionRule('remove', true, 'remove', that.dontAccept); + that.addActionRule('remove', true, 'putAttachment', that.dontAccept); + that.addActionRule('remove', false, 'post', that.eliminate); + that.addActionRule('remove', false, 'put', that.dontAccept); + that.addActionRule('remove', false, 'get', that.dontAccept); + that.addActionRule('remove', false, 'remove', that.update); + that.addActionRule('remove', false, 'putAttachment', that.dontAccept); + + that.addActionRule('allDocs', true, 'allDocs', that.dontAccept); + that.addActionRule('allDocs', false, 'allDocs', that.update); + + that.addActionRule('putAttachment', true, 'post', that.dontAccept); + that.addActionRule('putAttachment', true, 'put', that.wait); + that.addActionRule('putAttachment', true, 'get', that.wait); + that.addActionRule('putAttachment', true, 'remove', that.wait); + that.addActionRule('putAttachment', true, 'putAttachment', that.wait); + that.addActionRule('putAttachment', false, 'post', that.dontAccept); + that.addActionRule('putAttachment', false, 'put', that.wait); + that.addActionRule('putAttachment', false, 'get', that.wait); + that.addActionRule('putAttachment', false, 'remove', that.eliminate); + that.addActionRule('putAttachment', false, 'putAttachment', that.update); + // end adding rules + //////////////////////////////////////////////////////////////////////////// + return that; +}()); +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global spec: true, localstorage: true, + activityUpdater: true, jobManager: true, storage: true, + storage_type_object: true, invalidStorageType: true, jobRules: true, + job: true, postCommand: true, putCommand: true, getCommand:true, + allDocsCommand: true, putAttachmentCommand: true, + removeCommand: true */ +// Class jio +var that = {}, priv = {}, jio_id_array_name = 'jio/id_array'; +spec = spec || {}; +// Attributes // +priv.id = null; + +priv.storage_spec = spec; + +priv.environments = {}; + +// initialize // +priv.init = function () { + // Initialize the jio id and add the new id to the list + if (priv.id === null) { + var i, jio_id_a = + localstorage.getItem(jio_id_array_name) || []; + priv.id = 1; + for (i = 0; i < jio_id_a.length; i += 1) { + if (jio_id_a[i] >= priv.id) { + priv.id = jio_id_a[i] + 1; + } + } + jio_id_a.push(priv.id); + localstorage.setItem(jio_id_array_name, jio_id_a); + activityUpdater.setId(priv.id); + jobManager.setId(priv.id); + } +}; + +// Methods // +/** + * Returns a storage from a storage description. + * @method storage + * @param {object} spec The specifications. + * @param {object} my The protected object. + * @param {string} forcetype Force storage type + * @return {object} The storage object. + */ +Object.defineProperty(that, "storage", { + configurable: false, + enumerable: false, + writable: false, + value: function (spec, my, forcetype) { + var spec_str, type; + spec = spec || {}; + my = my || {}; + my.basicStorage = storage; + spec_str = JSON.stringify(spec); + // environment initialization + priv.environments[spec_str] = priv.environments[spec_str] || {}; + my.env = priv.environments[spec_str]; + my.storage = that.storage; // NOTE : or proxy storage + type = forcetype || spec.type || 'base'; + if (type === 'base') { + return storage(spec, my); + } + if (!storage_type_object[type]) { + throw invalidStorageType({ + "type": type, + "message": "Storage does not exists." + }); + } + return storage_type_object[type](spec, my); + } +}); +jobManager.storage = that.storage; + +Object.defineProperty(that, "start", { + configurable: false, + enumerable: false, + writable: false, + value: function () { + priv.init(); + activityUpdater.start(); + jobManager.start(); + } +}); + +Object.defineProperty(that, "stop", { + configurable: false, + enumerable: false, + writable: false, + value: function () { + jobManager.stop(); + } +}); + +Object.defineProperty(that, "close", { + configurable: false, + enumerable: false, + writable: false, + value: function () { + activityUpdater.stop(); + jobManager.stop(); + priv.id = null; + } +}); + +/** + * Returns the jio id. + * @method getId + * @return {number} The jio id. + */ +Object.defineProperty(that, "getId", { + configurable: false, + enumerable: false, + writable: false, + value: function () { + return priv.id; + } +}); + +/** + * Returns the jio job rules object used by the job manager. + * @method getJobRules + * @return {object} The job rules object + */ +Object.defineProperty(that, "getJobRules", { + configurable: false, + enumerable: false, + writable: false, + value: function () { + return jobRules; + } +}); + +/** + * Checks if the storage description is valid or not. + * @method validateStorageDescription + * @param {object} description The description object. + * @return {boolean} true if ok, else false. + */ +Object.defineProperty(that, "validateStorageDescription", { + configurable: false, + enumerable: false, + writable: false, + value: function (description) { + return that.storage(description).isValid(); + } +}); + +Object.defineProperty(that, "getJobArray", { + configurable: false, + enumerable: false, + writable: false, + value: function () { + return jobManager.serialized(); + } +}); + +priv.makeCallbacks = function (param, callback1, callback2) { + param.callback = function (err, val) { + if (err) { + param.error(err); + } else { + param.success(val); + } + }; + param.success = function (val) { + param.callback(undefined, val); + }; + param.error = function (err) { + param.callback(err, undefined); + }; + if (typeof callback1 === 'function') { + if (typeof callback2 === 'function') { + param.success = callback1; + param.error = callback2; + } else { + param.callback = callback1; + } + } else { + param.callback = function () {}; + } +}; + +priv.parametersToObject = function (list, default_options) { + var k, i = 0, callbacks = [], param = {"options": {}}; + for (i = 0; i < list.length; i += 1) { + if (typeof list[i] === 'object') { + // this is the option + param.options = list[i]; + for (k in default_options) { + if ((typeof default_options[k]) !== (typeof list[i][k])) { + param.options[k] = default_options[k]; + } + } + } + if (typeof list[i] === 'function') { + // this is a callback + callbacks.push(list[i]); + } + } + priv.makeCallbacks(param, callbacks[0], callbacks[1]); + return param; +}; + +priv.addJob = function (commandCreator, spec) { + jobManager.addJob(job({ + "storage": that.storage(priv.storage_spec), + "command": commandCreator(spec) + })); +}; + +/** + * Post a document. + * @method post + * @param {object} doc The document object. Contains at least: + * - {string} _id The document id (optional), "/" are forbidden + * @param {object} options (optional) Contains some options: + * - {number} max_retry The number max of retries, 0 = infinity. + * - {boolean} revs Include revision history of the document. + * - {boolean} revs_info Retreive the revisions. + * - {boolean} conflicts Retreive the conflict list. + * @param {function} callback (optional) The callback(err,response). + * @param {function} error (optional) The callback on error, if this + * callback is given in parameter, "callback" is changed as "success", + * called on success. + */ +Object.defineProperty(that, "post", { + configurable: false, + enumerable: false, + writable: false, + value: function (doc, options, success, error) { + var param = priv.parametersToObject( + [options, success, error], + {max_retry: 0} + ); + + priv.addJob(postCommand, { + doc: doc, + options: param.options, + callbacks: {success: param.success, error: param.error} + }); + } +}); + +/** + * Put a document. + * @method put + * @param {object} doc The document object. Contains at least: + * - {string} _id The document id, "/" are forbidden + * @param {object} options (optional) Contains some options: + * - {number} max_retry The number max of retries, 0 = infinity. + * - {boolean} revs Include revision history of the document. + * - {boolean} revs_info Retreive the revisions. + * - {boolean} conflicts Retreive the conflict list. + * @param {function} callback (optional) The callback(err,response). + * @param {function} error (optional) The callback on error, if this + * callback is given in parameter, "callback" is changed as "success", + * called on success. + */ +Object.defineProperty(that, "put", { + configurable: false, + enumerable: false, + writable: false, + value: function (doc, options, success, error) { + var param = priv.parametersToObject( + [options, success, error], + {max_retry: 0} + ); + + priv.addJob(putCommand, { + doc: doc, + options: param.options, + callbacks: {success: param.success, error: param.error} + }); + } +}); + +/** + * Get a document. + * @method get + * @param {string} docid The document id: "doc_id" or "doc_id/attachmt_id". + * @param {object} options (optional) Contains some options: + * - {number} max_retry The number max of retries, 0 = infinity. + * - {string} rev The revision we want to get. + * - {boolean} revs Include revision history of the document. + * - {boolean} revs_info Include list of revisions, and their availability. + * - {boolean} conflicts Include a list of conflicts. + * @param {function} callback (optional) The callback(err,response). + * @param {function} error (optional) The callback on error, if this + * callback is given in parameter, "callback" is changed as "success", + * called on success. + */ +Object.defineProperty(that, "get", { + configurable: false, + enumerable: false, + writable: false, + value: function (id, options, success, error) { + var param = priv.parametersToObject( + [options, success, error], + {max_retry: 3} + ); + + priv.addJob(getCommand, { + docid: id, + options: param.options, + callbacks: {success: param.success, error: param.error} + }); + } +}); + +/** + * Remove a document. + * @method remove + * @param {object} doc The document object. Contains at least: + * - {string} _id The document id: "doc_id" or "doc_id/attachment_id" + * @param {object} options (optional) Contains some options: + * - {number} max_retry The number max of retries, 0 = infinity. + * - {boolean} revs Include revision history of the document. + * - {boolean} revs_info Include list of revisions, and their availability. + * - {boolean} conflicts Include a list of conflicts. + * @param {function} callback (optional) The callback(err,response). + * @param {function} error (optional) The callback on error, if this + * callback is given in parameter, "callback" is changed as "success", + * called on success. + */ +Object.defineProperty(that, "remove", { + configurable: false, + enumerable: false, + writable: false, + value: function (doc, options, success, callback) { + var param = priv.parametersToObject( + [options, success, callback], + {max_retry: 0} + ); + + priv.addJob(removeCommand, { + doc: doc, + options: param.options, + callbacks: {success: param.success, error: param.error} + }); + } +}); + +/** + * Get a list of documents. + * @method allDocs + * @param {object} options (optional) Contains some options: + * - {number} max_retry The number max of retries, 0 = infinity. + * - {boolean} include_docs Include document metadata + * - {boolean} revs Include revision history of the document. + * - {boolean} revs_info Include revisions. + * - {boolean} conflicts Include conflicts. + * @param {function} callback (optional) The callback(err,response). + * @param {function} error (optional) The callback on error, if this + * callback is given in parameter, "callback" is changed as "success", + * called on success. + */ +Object.defineProperty(that, "allDocs", { + configurable: false, + enumerable: false, + writable: false, + value: function (options, success, error) { + var param = priv.parametersToObject( + [options, success, error], + {max_retry: 3} + ); + + priv.addJob(allDocsCommand, { + options: param.options, + callbacks: {success: param.success, error: param.error} + }); + } +}); + +/** + * Put an attachment to a document. + * @method putAttachment + * @param {object} doc The document object. Contains at least: + * - {string} id The document id: "doc_id/attchment_id" + * - {string} data Base64 attachment data + * - {string} mimetype The attachment mimetype + * - {string} rev The attachment revision + * @param {object} options (optional) Contains some options: + * - {number} max_retry The number max of retries, 0 = infinity. + * - {boolean} revs Include revision history of the document. + * - {boolean} revs_info Include revisions. + * - {boolean} conflicts Include conflicts. + * @param {function} callback (optional) The callback(err,respons) + * @param {function} error (optional) The callback on error, if this + * callback is given in parameter, "callback" is changed as "success", + * called on success. + */ +Object.defineProperty(that, "putAttachment", { + configurable: false, + enumerable: false, + writable: false, + value: function (doc, options, success, error) { + var param, k, doc_with_underscores = {}; + param = priv.parametersToObject( + [options, success, error], + {max_retry: 0} + ); + for (k in doc) { + if (doc.hasOwnProperty(k) && k.match('[^_].*')) { + doc_with_underscores["_" + k] = doc[k]; + } + } + + priv.addJob(putAttachmentCommand, { + doc: doc_with_underscores, + options: param.options, + callbacks: {success: param.success, error: param.error} + }); + } +}); + return that; +}; // End Class jio +/*jslint indent: 2, maxlen: 80, sloppy: true */ +/*global jio: true, invalidStorageType: true */ +var storage_type_object = { // -> 'key':constructorFunction + 'base': function () {} // overriden by jio +}; +var jioNamespace = (function (spec) { + var that = {}; + spec = spec || {}; + // Attributes // + + // Methods // + + /** + * Creates a new jio instance. + * @method newJio + * @param {object} spec The storage description + * @return {object} The new Jio instance. + */ + Object.defineProperty(that, "newJio", { + configurable: false, + enumerable: false, + writable: false, + value: function (spec) { + var storage = spec, + instance = null; + if (typeof storage === 'string') { + storage = JSON.parse(storage); + } else { + storage = JSON.stringify(storage); + if (storage !== undefined) { + storage = JSON.parse(storage); + } + } + storage = storage || { + type: 'base' + }; + instance = jio(storage); + instance.start(); + return instance; + } + }); + + /** + * Add a storage type to jio. + * @method addStorageType + * @param {string} type The storage type + * @param {function} constructor The associated constructor + */ + Object.defineProperty(that, "addStorageType", { + configurable: false, + enumerable: false, + writable: false, + value: function (type, constructor) { + constructor = constructor || function () { + return null; + }; + if (storage_type_object[type]) { + throw invalidStorageType({ + type: type, + message: 'Already known.' + }); + } + storage_type_object[type] = constructor; + } + }); + + return that; +}()); + +Object.defineProperty(scope, "jIO", { + configurable: false, + enumerable: false, + writable: false, + value: jioNamespace +}); +}(window, hex_md5));