diff --git a/featureRequests.html b/featureRequests.html index 5ce7ce92..f5102531 100644 --- a/featureRequests.html +++ b/featureRequests.html @@ -20,6 +20,7 @@ - - - -

Showcase projects

- -
- - MIDAS - - Git - - Cool examples (?) -
- - - - - - - - \ No newline at end of file diff --git a/showcase/images/midas.png b/showcase/images/midas.png new file mode 100644 index 00000000..4c69a913 Binary files /dev/null and b/showcase/images/midas.png differ diff --git a/showcase/index.html b/showcase/index.html new file mode 100644 index 00000000..dba5a0ec --- /dev/null +++ b/showcase/index.html @@ -0,0 +1,93 @@ + + + + + + + Feature Requests + + + + + + + + + + + + + +

Showcase projects

+ +
+ +
+ +
+
+ M.I.D.A.S. : Manufacturing Incident Detection Agent Solution +
+
+ MIDAS collects statistics of incidents during the production process. + It analyses the manufacturing process and handles disruptive events during the ramp-up phase. + Almende developed the software tool especially for this project, based on the inhouse toolchain, + specifically Eve, AIM and vis.js. +
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/showcase/projects/midas/arumAgents/agentGenerator.js b/showcase/projects/midas/arumAgents/agentGenerator.js new file mode 100644 index 00000000..038257cb --- /dev/null +++ b/showcase/projects/midas/arumAgents/agentGenerator.js @@ -0,0 +1,216 @@ +'use strict'; + +function AgentGenerator(id) { + // execute super constructor + eve.Agent.call(this, id); + this.rpc = this.loadModule('rpc', this.rpcFunctions); + var me = this; + this.amountOfEvents = 0; + this.eventNumber = 0; + this.eventsToFire = 0; + this.lastEndOfDayTime = null; + this.events = []; + + conn = this.connect(eve.system.transports.getAll()); + + // connect to websocket if not online only + if (conn[0].connect !== undefined) { + conn[0].connect(EVENTS_AGENT_ADDRESS) + .then(function () { + console.log('Connected to ', EVENTS_AGENT_ADDRESS); + me.rpc.request(EVENTS_AGENT_ADDRESS, { + method: "loadEvents", + params: {filename: "events.csv", actuallySend: true} + }).done(function (reply) { + me.amountOfEvents = reply; + me.getEvents(AMOUNT_OF_INITIAL_EVENTS); + }); + }) + .catch(function (err) { + console.log('Error: Failed to connect to the conductor agent'); + console.log(err); + + // keep trying until the conductor agent is online + setTimeout(connect, RECONNECT_DELAY); + }); + } + //use local connection + else { + EVENTS_AGENT_ADDRESS = 'eventGenerator'; + setTimeout(function() { + me.rpc.request(EVENTS_AGENT_ADDRESS, { + method: "loadEvents", + params: {filename: "events.csv", actuallySend: true} + }).done(function (reply) { + me.amountOfEvents = reply; + me.getEvents(AMOUNT_OF_INITIAL_EVENTS); + }); + },40); + } +} + +// extend the eve.Agent prototype +AgentGenerator.prototype = Object.create(eve.Agent.prototype); +AgentGenerator.prototype.constructor = AgentGenerator; + +// define RPC functions, preferably in a separated object to clearly distinct +// exposed functions from local functions. +AgentGenerator.prototype.rpcFunctions = {}; + +AgentGenerator.prototype.rpcFunctions.receiveEvent = function(params) { + // setup timeline + this.events.push(JSON.stringify(params)); + + if (params.performedBy == "global") { + this.imposeWorkingHours(params); + } + else { + if (agentList[params.performedBy] === undefined) { + agentList[params.performedBy] = new GenericAgent(params.performedBy, params.type); + } + this.rpc.request(params.performedBy, {method: "newEvent", params: params}); + } + + // check if we need to get another event, its done here to avoid raceconditions + if (this.eventsToFire != 0) { + var me = this; + setTimeout(function() { + me.eventNumber += 1; + eventCounter.innerHTML = me.eventNumber +""; // make string so it works + me.rpc.request(EVENTS_AGENT_ADDRESS, {method:'nextEvent', params:{}}).done(); + me.eventsToFire -= 1; + },EVENT_DELAY); + if (INCREASE_SPEED == true) { + EVENT_DELAY = Math.max(0, 1000 - (1000 * (me.eventNumber / 205))); + } + } +}; + +AgentGenerator.prototype.getEvents = function (count) { + if (this.eventNumber + count > this.amountOfEvents) { + count = this.amountOfEvents - this.eventNumber; + } + if (count != 0) { + this.eventsToFire = count - 1; + this.rpc.request(EVENTS_AGENT_ADDRESS, {method: 'nextEvent', params: {}}).done(); + this.eventNumber += 1; + eventCounter.innerHTML = this.eventNumber + ""; // make string so it works + } +}; + +AgentGenerator.prototype.rpcFunctions.updateOpenJobs = function(params) { + var skipJob = params.jobId; + var time = params.time; + this.moveTimeline(params); + for (var agentId in agentList) { + if (agentList.hasOwnProperty(agentId)) { + agentList[agentId].jobs.updateJobs(time, skipJob); + } + } +}; + +AgentGenerator.prototype.moveTimeline = function(params) { + timeline.setCustomTime(params.time); + var range = timeline.getWindow(); + var duration = range.end - range.start; + var hiddenDates = timeline.body.hiddenDates; + var DateUtil = vis.timeline.DateUtil; + var hiddenDuration = DateUtil.getHiddenDurationBetween(hiddenDates, range.start, range.end); + var visibleDuration = duration - hiddenDuration; + + + var fraction = 0.15; + var requiredStartDuration = (1-fraction) * visibleDuration; + var requiredEndDuration = fraction * visibleDuration; + var convertedTime = new Date(params.time).getTime(); + var newStart; + var newEnd; + + var elapsedDuration = 0; + var previousPoint = convertedTime; + for (var i = hiddenDates.length-1; i > 0; i--) { + var startDate = hiddenDates[i].start; + var endDate = hiddenDates[i].end; + // if time after the cutout, and the + if (endDate <= convertedTime) { + elapsedDuration += previousPoint - endDate; + previousPoint = startDate; + if (elapsedDuration >= requiredStartDuration) { + newStart = endDate + (elapsedDuration - requiredStartDuration); + break; + } + } + } + if (newStart === undefined) { + newStart = endDate - (requiredStartDuration - elapsedDuration); + } + + elapsedDuration = 0; + previousPoint = convertedTime; + for (var i = 0; i < hiddenDates.length; i++) { + var startDate = hiddenDates[i].start; + var endDate = hiddenDates[i].end; + // if time after the cutout, and the + if (startDate >= convertedTime) { + elapsedDuration += startDate - previousPoint; + previousPoint = endDate; + if (elapsedDuration >= requiredEndDuration) { + newEnd = startDate - (elapsedDuration - requiredEndDuration); + break; + } + } + } + if (newEnd === undefined) { + newEnd = endDate + (requiredEndDuration - elapsedDuration); + } + + timeline.setWindow(newStart, newEnd, {animate:Math.min(100,EVENT_DELAY)}); +}; + +AgentGenerator.prototype.imposeWorkingHours = function(params) { + var time = params.time; + var operation = params.operation; + + for (var agentId in agentList) { + if (agentList.hasOwnProperty(agentId)) { + var agent = agentList[agentId]; + for (var jobId in agent.jobs.openJobs) { + if (agent.jobs.openJobs.hasOwnProperty(jobId)) { + var job = agent.jobs.openJobs[jobId]; + agent.updateAssignment(jobId, job.type, time, operation); + } + } + } + } + + if (operation == 'endOfDay') { + this.lastEndOfDayTime = time; + } + else { + if (this.lastEndOfDayTime !== null) { + timelineItems.update({ + id: 'night' + uuid(), + start: this.lastEndOfDayTime, + end: time, + type: 'background', + className: 'night' + }); + this.lastEndOfDayTime = null; + } + } + + +}; + +AgentGenerator.prototype.printEvents = function() { + var str = ""; + str += "["; + for (var i = 0; i < this.events.length; i++) { + str += this.events[i]; + if (i < this.events.length - 1) { + str += "," + } + } + str += "]"; + console.log(str); +} \ No newline at end of file diff --git a/showcase/projects/midas/arumAgents/durationData.js b/showcase/projects/midas/arumAgents/durationData.js new file mode 100644 index 00000000..65dbfa0b --- /dev/null +++ b/showcase/projects/midas/arumAgents/durationData.js @@ -0,0 +1,40 @@ +/** + * Created by Alex on 9/25/2014. + */ + +function DurationData() { + this.fields = ['duration','durationWithPause','durationWithStartup','durationWithBoth']; + for (var i = 0; i < this.fields.length; i++) { + this[this.fields[i]] = 0; + } +} + +DurationData.prototype.setData = function(otherData) { + for (var i = 0; i < this.fields.length; i++) { + this[this.fields[i]] = otherData[this.fields[i]]; + } +}; + +DurationData.prototype.getData = function() { + var dataObj = {}; + for (var i = 0; i < this.fields.length; i++) { + dataObj[this.fields[i]] = this[this.fields[i]]; + } + return dataObj; +}; + +DurationData.prototype.useHighest = function(otherData) { + for (var i = 0; i < this.fields.length; i++) { + var field = this.fields[i]; + if (this[field] < otherData[field]) { + this[field] = otherData[field]; + } + } +}; + +DurationData.prototype.calculateDuration = function(time, timeStart, elapsedTime, elapsedTimeWithPause, startupTime) { + this.duration = elapsedTime; + this.durationWithPause = elapsedTimeWithPause; + this.durationWithStartup = elapsedTime + startupTime.durationWithStartup; + this.durationWithBoth = elapsedTimeWithPause + startupTime.durationWithBoth; +}; diff --git a/showcase/projects/midas/arumAgents/durationStats.js b/showcase/projects/midas/arumAgents/durationStats.js new file mode 100644 index 00000000..f2a57f18 --- /dev/null +++ b/showcase/projects/midas/arumAgents/durationStats.js @@ -0,0 +1,142 @@ +/** + * Created by Alex on 9/25/2014. + */ + +function DurationStats() { + this.fields = ['duration','durationWithPause','durationWithStartup','durationWithBoth']; + for (var i = 0; i < this.fields.length; i++) { + this[this.fields[i]] = {mean: 0, std: 0}; + } +} + +DurationStats.prototype.clearStats = function() { + for (var i = 0; i < this.fields.length; i++) { + var field = this.fields[i]; + this[field].mean = 0; + this[field].std = 0; + } +}; + +DurationStats.prototype.sumStats = function(otherData) { + for (var i = 0; i < this.fields.length; i++) { + var field = this.fields[i]; + this[field].mean += otherData[field].mean; + this[field].std += Math.pow(otherData[field].std,2); + } +}; +DurationStats.prototype.averageStats = function(datapoints) { + for (var i = 0; i < this.fields.length; i++) { + var field = this.fields[i]; + this[field].mean /= datapoints; + this[field].std = Math.sqrt(this[field].std / datapoints); + } +}; + +DurationStats.prototype.getMeanData = function() { + var dataObj = {}; + for (var i = 0; i < this.fields.length; i++) { + dataObj[this.fields[i]] = this[this.fields[i]].mean; + } + return dataObj; +}; + +DurationStats.prototype.getData = function() { + var dataObj = {}; + for (var i = 0; i < this.fields.length; i++) { + dataObj[this.fields[i]] = {}; + dataObj[this.fields[i]].mean = this[this.fields[i]].mean; + dataObj[this.fields[i]].std = this[this.fields[i]].std; + } + + return dataObj; +}; + +DurationStats.prototype.setData = function(otherData) { + for (var i = 0; i < this.fields.length; i++) { + var field = this.fields[i]; + this[field].mean = otherData[field].mean; + this[field].std = otherData[field].std; + } +}; + +DurationStats.prototype.generateData = function(otherData) { + for (var i = 0; i < this.fields.length; i++) { + var field = this.fields[i]; + this[field].mean = otherData[i] * 3600000; + this[field].std = otherData[i] * 0.1; + } +}; + + +DurationStats.prototype.useHighest = function(otherData) { + for (var i = 0; i < this.fields.length; i++) { + var field = this.fields[i]; + if (this[field].mean < otherData[field].mean) { + this[field].mean = otherData[field].mean; + this[field].std = otherData[field].std; + } + } +}; + +DurationStats.prototype.getFakeStats = function(type) { + switch (type) { + case "Assemble Coffeemaker": + this.generateData([1.3,1.3,1.3,1.3]); + break; + case "Discuss potential NC": + this.generateData([0.5,0.5,0.9,0.9]); + break; + case "Drilling rework": + this.generateData([5,5,8,8]); + break; + case "Go to station": + var a = 0.3; + this.generateData([a,a,a,a]); + break; + case "Inspect finished Coffeemaker": + var a = 2; + this.generateData([a,a,a,a]); + break; + case "Inspect potential NC": + var a = 0.5; + this.generateData([a,a,a,a]); + break; + case "Kitting Coffeemaker": + var a = 1.2; + this.generateData([a,a,a,a]); + break; + case "NC Meeting": + var a = 15; + this.generateData([3,3.5,a,a]); + break; + case "Go to NC meeting": + var a = 12; + this.generateData([a,a,a,a]); + break; + case "Organise drilling rework": + var a = 2; + this.generateData([a,a,3,3]); + break; + case "Produce Coffeemaker": + var a = 35; + this.generateData([a,a,a,a]); + break; + case "Transport to delivery": + var a = 0.4; + this.generateData([a,a,a,a]); + break; + default: + console.log("CANNOT MATCH", type); + break; + + } + + + + + + + + + +}; \ No newline at end of file diff --git a/showcase/projects/midas/arumAgents/eventGenerator.js b/showcase/projects/midas/arumAgents/eventGenerator.js new file mode 100644 index 00000000..7750fa03 --- /dev/null +++ b/showcase/projects/midas/arumAgents/eventGenerator.js @@ -0,0 +1,2037 @@ +'use strict'; + +if (typeof window === 'undefined') { + var eve = require('evejs'); + var GenericAgent = require('./GenericAgent') +} + +function EventGenerator(id) { + // execute super constructor + eve.Agent.call(this, id); + this.rpc = this.loadModule('rpc', this.rpcFunctions); + this.connect(eve.system.transports.getAll()); + this.eventCounter = 0; + this.events = [ + { + "jobId": "100", + "time": "2014-09-16T09:25:00.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "7", + "operation": "start" + }, + { + "jobId": "101", + "time": "2014-09-16T09:25:00.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "7", + "operation": "start" + }, + { + "jobId": "101", + "time": "2014-09-16T10:49:03.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "7", + "operation": "finish" + }, + { + "jobId": "102", + "time": "2014-09-16T10:52:13.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "7", + "operation": "start" + }, + { + "jobId": "999", + "time": "2014-09-16T18:00:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "endOfDay" + }, + { + "jobId": "999", + "time": "2014-09-17T08:30:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "startOfDay" + }, + { + "jobId": "81", + "time": "2014-09-17T09:23:00.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "6", + "operation": "start" + }, + { + "jobId": "82", + "time": "2014-09-17T09:23:00.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "6", + "operation": "start" + }, + { + "jobId": "111", + "time": "2014-09-17T09:25:00.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "8", + "operation": "start" + }, + { + "jobId": "112", + "time": "2014-09-17T09:25:00.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "8", + "operation": "start" + }, + { + "jobId": "82", + "time": "2014-09-17T10:29:03.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "83", + "time": "2014-09-17T10:32:12.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "6", + "operation": "start" + }, + { + "jobId": "112", + "time": "2014-09-17T11:16:03.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "8", + "operation": "finish" + }, + { + "jobId": "113", + "time": "2014-09-17T11:18:03.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "8", + "operation": "start" + }, + { + "jobId": "113", + "time": "2014-09-17T11:23:56.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "8", + "operation": "pause" + }, + { + "jobId": "114", + "time": "2014-09-17T11:23:56.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "8", + "operation": "start" + }, + { + "jobId": "114", + "time": "2014-09-17T11:28:16.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "8", + "operation": "finish" + }, + { + "jobId": "115", + "time": "2014-09-17T11:28:19.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect potential NC", + "productId": "8", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Paolo" + } + ] + }, + { + "jobId": "115", + "time": "2014-09-17T11:43:21.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect potential NC", + "productId": "8", + "operation": "finish" + }, + { + "jobId": "113", + "time": "2014-09-17T11:44:21.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "8", + "operation": "resume" + }, + { + "jobId": "102", + "time": "2014-09-17T12:14:39.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "7", + "operation": "finish" + }, + { + "jobId": "103", + "time": "2014-09-17T12:14:42.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "7", + "operation": "start" + }, + { + "jobId": "103", + "time": "2014-09-17T12:24:39.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "7", + "operation": "finish" + }, + { + "jobId": "104", + "time": "2014-09-17T12:24:45.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "7", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Paolo" + } + ] + }, + { + "jobId": "104", + "time": "2014-09-17T14:40:01.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "7", + "operation": "finish" + }, + { + "jobId": "105", + "time": "2014-09-17T14:41:01.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "7", + "operation": "start" + }, + { + "jobId": "105", + "time": "2014-09-17T15:12:12.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "7", + "operation": "finish" + }, + { + "jobId": "100", + "time": "2014-09-17T15:12:12.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "7", + "operation": "finish" + }, + { + "jobId": "83", + "time": "2014-09-17T15:45:21.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "6", + "operation": "pause" + }, + { + "jobId": "84", + "time": "2014-09-17T15:45:21.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "6", + "operation": "start" + }, + { + "jobId": "84", + "time": "2014-09-17T15:55:11.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "85", + "time": "2014-09-17T15:58:11.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect potential NC", + "productId": "6", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Paolo" + } + ] + }, + { + "jobId": "85", + "time": "2014-09-17T16:11:12.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect potential NC", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "86", + "time": "2014-09-17T16:11:12.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Go to station", + "productId": "6", + "operation": "start" + }, + { + "jobId": "86", + "time": "2014-09-17T16:51:12.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Go to station", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "87", + "time": "2014-09-17T16:52:12.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Discuss potential NC", + "productId": "6", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Marcelo" + } + ] + }, + { + "jobId": "87", + "time": "2014-09-17T17:12:12.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Discuss potential NC", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "88", + "time": "2014-09-17T17:12:12.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Go to NC meeting", + "productId": "6", + "operation": "start" + }, + { + "jobId": "89", + "time": "2014-09-17T17:12:12.000+02:00", + "performedBy": "Giovanni", + "type": "mt", + "assignment": "Go to NC meeting", + "productId": "6", + "operation": "start" + }, + { + "jobId": "90", + "time": "2014-09-17T17:12:12.000+02:00", + "performedBy": "Cristiana", + "type": "mt", + "assignment": "Go to NC meeting", + "productId": "6", + "operation": "start" + }, + { + "jobId": "91", + "time": "2014-09-17T17:12:12.000+02:00", + "performedBy": "Claudio", + "type": "mt", + "assignment": "Go to NC meeting", + "productId": "6", + "operation": "start" + }, + { + "jobId": "999", + "time": "2014-09-17T18:00:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "endOfDay" + }, + { + "jobId": "999", + "time": "2014-09-18T08:30:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "startOfDay" + }, + { + "jobId": "41", + "time": "2014-09-18T09:25:00.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "5", + "operation": "start" + }, + { + "jobId": "42", + "time": "2014-09-18T09:25:00.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "5", + "operation": "start" + }, + { + "jobId": "42", + "time": "2014-09-18T10:49:03.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "5", + "operation": "finish" + }, + { + "jobId": "43", + "time": "2014-09-18T10:52:13.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "5", + "operation": "start" + }, + { + "jobId": "113", + "time": "2014-09-18T12:10:29.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "8", + "operation": "finish" + }, + { + "jobId": "116", + "time": "2014-09-18T12:10:29.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "8", + "operation": "start" + }, + { + "jobId": "116", + "time": "2014-09-18T12:28:29.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "8", + "operation": "finish" + }, + { + "jobId": "117", + "time": "2014-09-18T12:35:29.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "8", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Paolo" + } + ] + }, + { + "jobId": "117", + "time": "2014-09-18T15:12:01.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "8", + "operation": "finish" + }, + { + "jobId": "118", + "time": "2014-09-18T15:14:01.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "8", + "operation": "start" + }, + { + "jobId": "118", + "time": "2014-09-18T15:31:01.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "8", + "operation": "finish" + }, + { + "jobId": "111", + "time": "2014-09-18T15:31:01.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "8", + "operation": "finish" + }, + { + "jobId": "999", + "time": "2014-09-18T18:00:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "endOfDay" + }, + { + "jobId": "999", + "time": "2014-09-19T08:30:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "startOfDay" + }, + { + "jobId": "88", + "time": "2014-09-19T09:56:00.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Go to NC meeting", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "89", + "time": "2014-09-19T09:56:00.000+02:00", + "performedBy": "Giovanni", + "type": "mt", + "assignment": "Go to NC meeting", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "90", + "time": "2014-09-19T09:56:00.000+02:00", + "performedBy": "Cristiana", + "type": "mt", + "assignment": "Go to NC meeting", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "91", + "time": "2014-09-19T09:56:00.000+02:00", + "performedBy": "Claudio", + "type": "mt", + "assignment": "Go to NC meeting", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "92", + "time": "2014-09-19T10:01:00.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "NC Meeting", + "productId": "6", + "operation": "start", + "prerequisites": [ + { + "type": "Go to NC meeting" + } + ] + }, + { + "jobId": "93", + "time": "2014-09-19T10:01:00.000+02:00", + "performedBy": "Giovanni", + "type": "mt", + "assignment": "NC Meeting", + "productId": "6", + "operation": "start", + "prerequisites": [ + { + "type": "Go to NC meeting" + } + ] + }, + { + "jobId": "94", + "time": "2014-09-19T10:01:00.000+02:00", + "performedBy": "Cristiana", + "type": "mt", + "assignment": "NC Meeting", + "productId": "6", + "operation": "start", + "prerequisites": [ + { + "type": "Go to NC meeting" + } + ] + }, + { + "jobId": "95", + "time": "2014-09-19T10:01:00.000+02:00", + "performedBy": "Claudio", + "type": "mt", + "assignment": "NC Meeting", + "productId": "6", + "operation": "start", + "prerequisites": [ + { + "type": "Go to NC meeting" + } + ] + }, + { + "jobId": "44", + "time": "2014-09-19T12:10:32.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "5", + "operation": "start" + }, + { + "jobId": "43", + "time": "2014-09-19T12:10:39.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "5", + "operation": "finish" + }, + { + "jobId": "44", + "time": "2014-09-19T12:24:39.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "5", + "operation": "finish" + }, + { + "jobId": "45", + "time": "2014-09-19T12:24:45.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "5", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Paolo" + } + ] + }, + { + "jobId": "92", + "time": "2014-09-19T13:14:00.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "NC Meeting", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "93", + "time": "2014-09-19T13:14:00.000+02:00", + "performedBy": "Giovanni", + "type": "mt", + "assignment": "NC Meeting", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "94", + "time": "2014-09-19T13:14:00.000+02:00", + "performedBy": "Cristiana", + "type": "mt", + "assignment": "NC Meeting", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "95", + "time": "2014-09-19T13:14:00.000+02:00", + "performedBy": "Claudio", + "type": "mt", + "assignment": "NC Meeting", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "45", + "time": "2014-09-19T14:43:01.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "5", + "operation": "finish" + }, + { + "jobId": "46", + "time": "2014-09-19T14:43:01.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "5", + "operation": "start" + }, + { + "jobId": "46", + "time": "2014-09-19T14:59:12.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "5", + "operation": "finish" + }, + { + "jobId": "41", + "time": "2014-09-19T14:59:12.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "5", + "operation": "finish" + }, + { + "jobId": "999", + "time": "2014-09-19T18:00:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "endOfDay" + }, + { + "jobId": "999", + "time": "2014-09-22T08:30:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "startOfDay" + }, + { + "jobId": "83", + "time": "2014-09-22T09:04:39.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "6", + "operation": "resume" + }, + { + "jobId": "31", + "time": "2014-09-22T09:08:00.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "3", + "operation": "start" + }, + { + "jobId": "32", + "time": "2014-09-22T09:08:00.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "3", + "operation": "start" + }, + { + "jobId": "32", + "time": "2014-09-22T10:36:03.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "3", + "operation": "finish" + }, + { + "jobId": "33", + "time": "2014-09-22T10:38:32.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "3", + "operation": "start" + }, + { + "jobId": "83", + "time": "2014-09-22T14:42:39.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "96", + "time": "2014-09-22T14:42:39.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "6", + "operation": "start" + }, + { + "jobId": "96", + "time": "2014-09-22T14:52:39.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "97", + "time": "2014-09-22T14:54:39.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "6", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Paolo" + } + ] + }, + { + "jobId": "97", + "time": "2014-09-22T17:27:39.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "98", + "time": "2014-09-22T17:29:39.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "6", + "operation": "start" + }, + { + "jobId": "98", + "time": "2014-09-22T17:47:39.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "81", + "time": "2014-09-22T17:47:39.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "6", + "operation": "finish" + }, + { + "jobId": "999", + "time": "2014-09-22T18:00:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "endOfDay" + }, + { + "jobId": "999", + "time": "2014-09-23T08:30:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "startOfDay" + }, + { + "jobId": "51", + "time": "2014-09-23T09:25:00.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "4", + "operation": "start" + }, + { + "jobId": "52", + "time": "2014-09-23T09:25:00.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "4", + "operation": "start" + }, + { + "jobId": "52", + "time": "2014-09-23T10:49:03.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "53", + "time": "2014-09-23T10:52:13.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "4", + "operation": "start" + }, + { + "jobId": "53", + "time": "2014-09-23T11:45:21.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "4", + "operation": "pause" + }, + { + "jobId": "54", + "time": "2014-09-23T11:45:21.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "4", + "operation": "start" + }, + { + "jobId": "54", + "time": "2014-09-23T12:03:21.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "55", + "time": "2014-09-23T12:03:56.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect potential NC", + "productId": "4", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Paolo" + } + ] + }, + { + "jobId": "55", + "time": "2014-09-23T14:01:02.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect potential NC", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "56", + "time": "2014-09-23T14:01:02.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Go to station", + "productId": "4", + "operation": "start" + }, + { + "jobId": "33", + "time": "2014-09-23T14:10:29.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "3", + "operation": "finish" + }, + { + "jobId": "34", + "time": "2014-09-23T14:10:29.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "3", + "operation": "start" + }, + { + "jobId": "34", + "time": "2014-09-23T14:12:29.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "3", + "operation": "finish" + }, + { + "jobId": "35", + "time": "2014-09-23T14:12:34.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "3", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Paolo" + } + ] + }, + { + "jobId": "56", + "time": "2014-09-23T14:25:12.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Go to station", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "57", + "time": "2014-09-23T14:25:32.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Discuss potential NC", + "productId": "4", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Marcelo" + } + ] + }, + { + "jobId": "57", + "time": "2014-09-23T15:05:12.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Discuss potential NC", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "58", + "time": "2014-09-23T15:05:15.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Go to NC meeting", + "productId": "4", + "operation": "start" + }, + { + "jobId": "59", + "time": "2014-09-23T15:05:15.000+02:00", + "performedBy": "Pascale", + "type": "mt", + "assignment": "Go to NC meeting", + "productId": "4", + "operation": "start" + }, + { + "jobId": "60", + "time": "2014-09-23T15:05:15.000+02:00", + "performedBy": "Giovanni", + "type": "mt", + "assignment": "Go to NC meeting", + "productId": "4", + "operation": "start" + }, + { + "jobId": "61", + "time": "2014-09-23T15:05:15.000+02:00", + "performedBy": "Cristiana", + "type": "mt", + "assignment": "Go to NC meeting", + "productId": "4", + "operation": "start" + }, + { + "jobId": "62", + "time": "2014-09-23T15:05:15.000+02:00", + "performedBy": "Claudio", + "type": "mt", + "assignment": "Go to NC meeting", + "productId": "4", + "operation": "start" + }, + { + "jobId": "35", + "time": "2014-09-23T15:32:01.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "3", + "operation": "finish" + }, + { + "jobId": "36", + "time": "2014-09-23T15:32:01.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "3", + "operation": "start" + }, + { + "jobId": "36", + "time": "2014-09-23T15:49:12.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "3", + "operation": "finish" + }, + { + "jobId": "31", + "time": "2014-09-23T15:49:12.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "3", + "operation": "finish" + }, + { + "jobId": "999", + "time": "2014-09-23T18:00:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "endOfDay" + }, + { + "jobId": "999", + "time": "2014-09-24T08:30:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "startOfDay" + }, + { + "jobId": "21", + "time": "2014-09-24T09:08:00.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "2", + "operation": "start" + }, + { + "jobId": "22", + "time": "2014-09-24T09:08:00.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "2", + "operation": "start" + }, + { + "jobId": "22", + "time": "2014-09-24T10:36:03.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "2", + "operation": "finish" + }, + { + "jobId": "23", + "time": "2014-09-24T10:38:32.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "2", + "operation": "start" + }, + { + "jobId": "23", + "time": "2014-09-24T11:03:56.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "2", + "operation": "pause" + }, + { + "jobId": "24", + "time": "2014-09-24T11:03:56.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "2", + "operation": "start" + }, + { + "jobId": "24", + "time": "2014-09-24T11:28:16.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "2", + "operation": "finish" + }, + { + "jobId": "25", + "time": "2014-09-24T11:28:19.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect potential NC", + "productId": "2", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Paolo" + } + ] + }, + { + "jobId": "25", + "time": "2014-09-24T11:35:21.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect potential NC", + "productId": "2", + "operation": "finish" + }, + { + "jobId": "23", + "time": "2014-09-24T11:35:21.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "2", + "operation": "resume" + }, + { + "jobId": "61", + "time": "2014-09-24T15:51:00.000+02:00", + "performedBy": "Cristiana", + "type": "mt", + "assignment": "Go to NC meeting", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "62", + "time": "2014-09-24T15:56:00.000+02:00", + "performedBy": "Claudio", + "type": "mt", + "assignment": "Go to NC meeting", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "58", + "time": "2014-09-24T15:57:00.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Go to NC meeting", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "59", + "time": "2014-09-24T15:58:00.000+02:00", + "performedBy": "Pascale", + "type": "mt", + "assignment": "Go to NC meeting", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "60", + "time": "2014-09-24T15:59:00.000+02:00", + "performedBy": "Giovanni", + "type": "mt", + "assignment": "Go to NC meeting", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "64", + "time": "2014-09-24T16:00:01.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "NC Meeting", + "productId": "4", + "operation": "start", + "prerequisites": [ + { + "type": "Go to NC meeting" + } + ] + }, + { + "jobId": "65", + "time": "2014-09-24T16:00:01.000+02:00", + "performedBy": "Pascale", + "type": "mt", + "assignment": "NC Meeting", + "productId": "4", + "operation": "start", + "prerequisites": [ + { + "type": "Go to NC meeting" + } + ] + }, + { + "jobId": "66", + "time": "2014-09-24T16:00:01.000+02:00", + "performedBy": "Giovanni", + "type": "mt", + "assignment": "NC Meeting", + "productId": "4", + "operation": "start", + "prerequisites": [ + { + "type": "Go to NC meeting" + } + ] + }, + { + "jobId": "67", + "time": "2014-09-24T16:00:01.000+02:00", + "performedBy": "Cristiana", + "type": "mt", + "assignment": "NC Meeting", + "productId": "4", + "operation": "start", + "prerequisites": [ + { + "type": "Go to NC meeting" + } + ] + }, + { + "jobId": "68", + "time": "2014-09-24T16:00:01.000+02:00", + "performedBy": "Claudio", + "type": "mt", + "assignment": "NC Meeting", + "productId": "4", + "operation": "start", + "prerequisites": [ + { + "type": "Go to NC meeting" + } + ] + }, + { + "jobId": "66", + "time": "2014-09-24T17:24:30.000+02:00", + "performedBy": "Giovanni", + "type": "mt", + "assignment": "NC Meeting", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "67", + "time": "2014-09-24T17:24:30.000+02:00", + "performedBy": "Cristiana", + "type": "mt", + "assignment": "NC Meeting", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "64", + "time": "2014-09-24T17:49:30.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "NC Meeting", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "65", + "time": "2014-09-24T17:49:30.000+02:00", + "performedBy": "Pascale", + "type": "mt", + "assignment": "NC Meeting", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "68", + "time": "2014-09-24T17:49:30.000+02:00", + "performedBy": "Claudio", + "type": "mt", + "assignment": "NC Meeting", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "999", + "time": "2014-09-24T18:00:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "endOfDay" + }, + { + "jobId": "999", + "time": "2014-09-25T08:30:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "startOfDay" + }, + { + "jobId": "1", + "time": "2014-09-25T09:05:23.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "1", + "operation": "start" + }, + { + "jobId": "2", + "time": "2014-09-25T09:05:23.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "1", + "operation": "start" + }, + { + "jobId": "2", + "time": "2014-09-25T10:44:23.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Kitting Coffeemaker", + "productId": "1", + "operation": "finish" + }, + { + "jobId": "3", + "time": "2014-09-25T10:44:56.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "1", + "operation": "start" + }, + { + "jobId": "23", + "time": "2014-09-25T13:10:29.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "2", + "operation": "finish" + }, + { + "jobId": "28", + "time": "2014-09-25T13:10:29.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "2", + "operation": "start" + }, + { + "jobId": "28", + "time": "2014-09-25T13:12:29.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "2", + "operation": "finish" + }, + { + "jobId": "26", + "time": "2014-09-25T13:12:32.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "2", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Paolo" + } + ] + }, + { + "jobId": "3", + "time": "2014-09-25T13:45:21.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "1", + "operation": "pause" + }, + { + "jobId": "4", + "time": "2014-09-25T13:45:21.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "1", + "operation": "start" + }, + { + "jobId": "26", + "time": "2014-09-25T13:47:37.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "2", + "operation": "pause", + "prerequisites": [ + { + "type": "Inspect potential NC", + "agentId": "Paolo" + } + ] + }, + { + "jobId": "4", + "time": "2014-09-25T13:54:02.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "1", + "operation": "finish" + }, + { + "jobId": "5", + "time": "2014-09-25T13:54:03.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect potential NC", + "productId": "1", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Paolo" + } + ] + }, + { + "jobId": "5", + "time": "2014-09-25T14:01:02.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect potential NC", + "productId": "1", + "operation": "finish" + }, + { + "jobId": "3", + "time": "2014-09-25T14:01:23.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "1", + "operation": "resume" + }, + { + "jobId": "26", + "time": "2014-09-25T14:12:02.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "2", + "operation": "resume" + }, + { + "jobId": "26", + "time": "2014-09-25T14:32:01.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "2", + "operation": "finish" + }, + { + "jobId": "27", + "time": "2014-09-25T14:32:01.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "2", + "operation": "start" + }, + { + "jobId": "27", + "time": "2014-09-25T14:49:12.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "2", + "operation": "finish" + }, + { + "jobId": "21", + "time": "2014-09-25T14:49:12.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "2", + "operation": "finish" + }, + { + "jobId": "999", + "time": "2014-09-25T18:00:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "endOfDay" + }, + { + "jobId": "999", + "time": "2014-09-26T08:30:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "startOfDay" + }, + { + "jobId": "53", + "time": "2014-09-26T09:10:39.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "4", + "operation": "resume" + }, + { + "jobId": "3", + "time": "2014-09-26T10:13:49.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "1", + "operation": "pause" + }, + { + "jobId": "6", + "time": "2014-09-26T10:13:49.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "1", + "operation": "start" + }, + { + "jobId": "6", + "time": "2014-09-26T10:25:23.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "1", + "operation": "finish" + }, + { + "jobId": "7", + "time": "2014-09-26T10:25:24.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect potential NC", + "productId": "1", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Paolo" + } + ] + }, + { + "jobId": "7", + "time": "2014-09-26T10:32:42.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect potential NC", + "productId": "1", + "operation": "finish" + }, + { + "jobId": "9", + "time": "2014-09-26T10:32:42.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Go to station", + "productId": "1", + "operation": "start" + }, + { + "jobId": "9", + "time": "2014-09-26T10:58:32.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Go to station", + "productId": "1", + "operation": "finish" + }, + { + "jobId": "10", + "time": "2014-09-26T10:58:33.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Discuss potential NC", + "productId": "1", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Marcelo" + } + ] + }, + { + "jobId": "10", + "time": "2014-09-26T11:05:12.000+02:00", + "performedBy": "Marcelo", + "type": "pm", + "assignment": "Discuss potential NC", + "productId": "1", + "operation": "finish" + }, + { + "jobId": "11", + "time": "2014-09-26T11:05:12.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Organise drilling rework", + "productId": "1", + "operation": "start", + "prerequisites": [ + { + "type": "Discuss potential NC" + } + ] + }, + { + "jobId": "11", + "time": "2014-09-26T13:25:18.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Organise drilling rework", + "productId": "1", + "operation": "finish" + }, + { + "jobId": "8", + "time": "2014-09-26T13:27:58.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Drilling rework", + "productId": "1", + "operation": "start", + "prerequisites": [ + { + "type": "Organise drilling rework" + } + ] + }, + { + "jobId": "53", + "time": "2014-09-26T14:13:39.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "69", + "time": "2014-09-26T14:13:39.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "4", + "operation": "start" + }, + { + "jobId": "69", + "time": "2014-09-26T14:19:39.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "70", + "time": "2014-09-26T14:19:56.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "4", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Paolo" + } + ] + }, + { + "jobId": "70", + "time": "2014-09-26T17:13:39.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "71", + "time": "2014-09-26T17:13:39.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "4", + "operation": "start" + }, + { + "jobId": "71", + "time": "2014-09-26T17:29:39.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "51", + "time": "2014-09-26T17:29:39.000+02:00", + "performedBy": "Francesco", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "4", + "operation": "finish" + }, + { + "jobId": "8", + "time": "2014-09-26T17:45:21.000+02:00", + "performedBy": "Fredrico", + "type": "worker", + "assignment": "Drilling rework", + "productId": "1", + "operation": "finish" + }, + { + "jobId": "999", + "time": "2014-09-26T18:00:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "endOfDay" + }, + { + "jobId": "999", + "time": "2014-09-29T08:30:00.000+02:00", + "performedBy": "global", + "type": "global", + "assignment": "", + "productId": "", + "operation": "startOfDay" + }, + { + "jobId": "3", + "time": "2014-09-29T09:01:23.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "1", + "operation": "resume" + }, + { + "jobId": "3", + "time": "2014-09-29T12:11:34.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Assemble Coffeemaker", + "productId": "1", + "operation": "finish" + }, + { + "jobId": "12", + "time": "2014-09-29T12:11:34.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "1", + "operation": "start" + }, + { + "jobId": "12", + "time": "2014-09-29T12:12:34.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Go to station", + "productId": "1", + "operation": "finish" + }, + { + "jobId": "13", + "time": "2014-09-29T12:12:35.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "1", + "operation": "start", + "prerequisites": [ + { + "type": "Go to station", + "agentId": "Paolo" + } + ] + }, + { + "jobId": "13", + "time": "2014-09-29T14:01:32.000+02:00", + "performedBy": "Paolo", + "type": "rao", + "assignment": "Inspect finished Coffeemaker", + "productId": "1", + "operation": "finish" + }, + { + "jobId": "14", + "time": "2014-09-29T14:01:32.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "1", + "operation": "start" + }, + { + "jobId": "14", + "time": "2014-09-29T15:34:10.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Transport to delivery", + "productId": "1", + "operation": "finish" + }, + { + "jobId": "1", + "time": "2014-09-29T15:34:11.000+02:00", + "performedBy": "Biagio", + "type": "worker", + "assignment": "Produce Coffeemaker", + "productId": "1", + "operation": "finish" + } + ]; + +} + +// extend the eve.Agent prototype +EventGenerator.prototype = Object.create(eve.Agent.prototype); +EventGenerator.prototype.constructor = EventGenerator; + +// define RPC functions, preferably in a separated object to clearly distinct +// exposed functions from local functions. +EventGenerator.prototype.rpcFunctions = {}; + +EventGenerator.prototype.rpcFunctions.loadEvents = function() { + return this.events.length - 1; +} + +EventGenerator.prototype.rpcFunctions.nextEvent = function() { + this.rpc.request("agentGenerator",{method:'receiveEvent', params:this.events[this.eventCounter]}).done(); + this.eventCounter += 1; +} + +if (typeof window === 'undefined') { + module.exports = EventGenerator; +} \ No newline at end of file diff --git a/showcase/projects/midas/arumAgents/genericAgent.js b/showcase/projects/midas/arumAgents/genericAgent.js new file mode 100644 index 00000000..74b3efc3 --- /dev/null +++ b/showcase/projects/midas/arumAgents/genericAgent.js @@ -0,0 +1,85 @@ +'use strict'; + +if (typeof window === 'undefined') { + var eve = require('evejs'); +} + +function GenericAgent(id, type) { + // execute super constructor + eve.Agent.call(this, id); + + this.id = id; + this.rpc = this.loadModule('rpc', this.rpcFunctions); + this.connect(eve.system.transports.getAll()); + this.type = type; + this.jobs = new JobManager(this); + this.timelineDataset = timelineItems; + + timelineGroups.add({id:id, content:type + ": " + id, className: 'timelineGroup ' + type}); + + this.availableSubgroups = [0,1,2,3,4,5,6,7,8,9,10]; + this.freeSubgroups = {}; + for (var i = 0; i < this.availableSubgroups.length; i++) { + this.freeSubgroups[this.availableSubgroups[i]] = true; + } + this.usedSubgroups = {}; +} + +// extend the eve.Agent prototype +GenericAgent.prototype = Object.create(eve.Agent.prototype); +GenericAgent.prototype.constructor = GenericAgent; + +// define RPC functions, preferably in a separated object to clearly distinct +// exposed functions from local functions. +GenericAgent.prototype.rpcFunctions = {}; + +GenericAgent.prototype.allocateSubgroup = function(type) { + for (var i = 0; i < this.availableSubgroups.length; i++) { + if (this.freeSubgroups[this.availableSubgroups[i]] == true) { + this.usedSubgroups[type] = i; + this.freeSubgroups[this.availableSubgroups[i]] = false; + break; + } + } +}; + +GenericAgent.prototype.freeSubgroup = function(type) { + this.freeSubgroups[this.usedSubgroups[type]] = true; + delete this.usedSubgroups[type]; +}; + +GenericAgent.prototype.newAssignment = function(id, type, time, prerequisites) { + this.allocateSubgroup(type); + this.jobs.add(id, type, time, prerequisites); +}; + +/** + * @param id + * @param time + * @param type + */ +GenericAgent.prototype.finishAssignment = function(id, type, time) { + this.jobs.finish(id, type, time); +}; + + +GenericAgent.prototype.updateAssignment = function(id, type, time, operation) { + this.jobs.update(id, type, time, operation); +}; + +GenericAgent.prototype.rpcFunctions.newEvent = function(params) { + // handle events + if (params.operation == 'start') { + this.newAssignment(params.jobId, params.assignment, params.time, params.prerequisites) + } + else if (params.operation == 'finish') { + this.finishAssignment(params.jobId, params.assignment, params.time); + } + else if (params.operation == 'pause' || params.operation == 'resume') { + this.updateAssignment(params.jobId,params.assignment,params.time, params.operation); + } +}; + +if (typeof window === 'undefined') { + module.exports = GenericAgent; +} \ No newline at end of file diff --git a/showcase/projects/midas/arumAgents/job.js b/showcase/projects/midas/arumAgents/job.js new file mode 100644 index 00000000..f04e0fc8 --- /dev/null +++ b/showcase/projects/midas/arumAgents/job.js @@ -0,0 +1,116 @@ +'use strict'; + +function uuid() { + return (Math.random()*1e15).toString(32) + "-" + (Math.random()*1e15).toString(32); +} + +/** + * This is a local assignment, this keeps track on how long an assignment takes THIS worker. + * + * @param id + * @param type + * @param timeStart + * @constructor + */ +function Job(id, type, timeStart, agentId, prerequisites) { + this.id = id; + this.type = type; + this.agentId = agentId; + + this.timeStart = timeStart; + this.timeResumed = timeStart; + this.timePaused = 0; + this.elapsedTime = 0; + this.elapsedTimeWithPause = 0; + this.endOfDayPause = false; + + this.paused = false; + this.finished = false; + + this.duration = new DurationData(); + this.prediction = new DurationStats(); + this.startupTime = new DurationData(); + this.predictedStartupTime = new DurationStats(); + + this.prerequisites = prerequisites; +} + +Job.prototype.prerequisiteFinished = function(params) { + var uuid = params.uuid; + for (var i = 0; i < this.prerequisites.length; i++) { + var prereq = this.prerequisites[i]; + if (prereq.uuid == uuid) { + prereq.times.setData(params.duration); + break; + } + } +}; + +Job.prototype.watchingPrerequisite = function(preliminaryStats, uuid) { + for (var i = 0; i < this.prerequisites.length; i++) { + var prereq = this.prerequisites[i]; + if (prereq.uuid == uuid) { + prereq.stats.setData(preliminaryStats); + this.predictedStartupTime.useHighest(preliminaryStats); + break; + } + } +}; + +Job.prototype.finalizePrerequisites = function() { + for (var i = 0; i < this.prerequisites.length; i++) { + this.startupTime.useHighest(this.prerequisites[i].times); + } +}; + +Job.prototype.finish = function(time) { + this.finished = true; + this.elapsedTime += new Date(time).getTime() - new Date(this.timeResumed).getTime(); + this.elapsedTimeWithPause += new Date(time).getTime() - new Date(this.timeResumed).getTime(); + this.finalizePrerequisites(); + + this.duration.calculateDuration(time, this.timeStart, this.elapsedTime, this.elapsedTimeWithPause, this.startupTime); +}; + +Job.prototype.pause = function(time, endOfDay) { + // if this is the endOfDay AND the job is paused, count the pause time and set the endOfDay pause to true + if (endOfDay == true && this.paused == true) { + this.elapsedTimeWithPause += new Date(time).getTime() - new Date(this.timePaused).getTime(); + this.endOfDayPause = true; + } + // if this is the endOfDay AND the job is NOT paused, pause the job, increment the timers + else if (endOfDay == true && this.paused == false) { + this.elapsedTimeWithPause += new Date(time).getTime() - new Date(this.timeResumed).getTime(); + this.elapsedTime += new Date(time).getTime() - new Date(this.timeResumed).getTime(); + this.endOfDayPause = true; + } + else if (this.paused == false) { + this.elapsedTimeWithPause += new Date(time).getTime() - new Date(this.timeResumed).getTime(); + this.elapsedTime += new Date(time).getTime() - new Date(this.timeResumed).getTime(); + this.timePaused = time; + this.paused = true; + } +}; + +Job.prototype.resume = function(time, startOfDay) { + // if the job was paused because of the endOfDay, resume it and set the timeResumed to now + if (this.endOfDayPause == true && startOfDay == true && this.paused == false) { + this.timeResumed = time; + this.endOfDayPause = false; + } + // if the job was paused before the endOfDay, keep it paused, but set the paused time to now. + else if (this.endOfDayPause == true && startOfDay == true && this.paused == true) { + this.timePaused = time; + this.endOfDayPause = false; + } + // if this is NOT the start of day and the job was paused, resume job, increment + else if (startOfDay == false && this.paused == true) { + this.elapsedTimeWithPause += new Date(time).getTime() - new Date(this.timePaused).getTime(); + this.timeResumed = time; + this.paused = false; + } + + +}; + + diff --git a/showcase/projects/midas/arumAgents/jobAgent.js b/showcase/projects/midas/arumAgents/jobAgent.js new file mode 100644 index 00000000..b382791d --- /dev/null +++ b/showcase/projects/midas/arumAgents/jobAgent.js @@ -0,0 +1,434 @@ +'use strict'; + + + +function JobAgent(id) { + // execute super constructor + eve.Agent.call(this, id); + this.rpc = this.loadModule('rpc', this.rpcFunctions); + this.connect(eve.system.transports.getAll()); + + this.id = id; + this.type = this.id.replace('job_',''); + this.globalStats = new DurationStats(); + this.globalStats.getFakeStats(id); + + this.agentStats = {}; + + this.allJobs = {}; // used to quickly look up a job ID + this.openJobs = {}; // used to check if there is a watcher to + this.closedJobs = {};// keeps a list of agentIds containing jobs, used for stats collection + // optimization would be nice with running averages, but N samples are needed, wait for demo data. + + this.watchers = {}; +} + +// extend the eve.Agent prototype +JobAgent.prototype = Object.create(eve.Agent.prototype); +JobAgent.prototype.constructor = JobAgent; + +// define RPC functions, preferably in a separated object to clearly distinct +// exposed functions from local functions. +JobAgent.prototype.rpcFunctions = {}; + + +JobAgent.prototype.expandPrerequisites = function(prerequisites) { + var expanded = []; + if (prerequisites !== undefined) { + for (var i = 0; i < prerequisites.length; i++) { + var prereq = prerequisites[i]; + if (typeof prereq == 'string') { + expanded.push({ + jobId: prereq, + uuid: uuid(), + times : new DurationData(), + stats: new DurationStats() + }); + } + else if (typeof prereq == 'object' && prereq.type !== undefined) { //&& prereq.agentId !== undefined not needed since the same items will be added when only type exists + prereq.uuid = uuid(); + prereq.times = new DurationData(); + prereq.stats = new DurationStats(); + expanded.push(prereq); + } + else { + console.log('ERROR: cannot use the prerequisites! Not in array of strings or array of objects with correct fields format.'); + throw new Error('ERROR: cannot use the prerequisites! Not in array of strings or array of objects with correct fields format.'); + } + } + } + return expanded; +}; +/** + * Create new Job for agent + * @param params + */ +JobAgent.prototype.rpcFunctions.add = function(params) { + var agentId = params.agentId; + var jobId = params.jobId; + + // create stats if not yet exits + if (this.agentStats[agentId] === undefined) { + this.agentStats[agentId] = new DurationStats(); + } + + // create open job + if (this.openJobs[agentId] === undefined) { + this.openJobs[agentId] = {}; + } + if (this.openJobs[agentId][jobId] !== undefined) { + console.log('cannot start new job, jobId:', jobId, ' already exists!'); + throw new Error('cannot start new job, jobId:' + jobId + ' already exists!'); + } + var prerequisites = this.expandPrerequisites(params.prerequisites); + this.openJobs[agentId][jobId] = new Job(jobId, this.id, params.time, agentId, prerequisites); + this.allJobs[jobId] = this.openJobs[agentId][jobId]; + this.addWatchers(jobId, prerequisites); + + + // return prediction + var statsData; + if (this.agentStats[agentId].duration.mean == 0) { + statsData = this.globalStats.getData(); + } + else { + statsData = this.agentStats[agentId].getData(); + } + return statsData; +}; + +/** + * finish the job of an agent + * @param params + */ +JobAgent.prototype.rpcFunctions.finish = function(params) { + var agentId = params.agentId; + var jobId = params.jobId; + var job = this.openJobs[agentId][jobId]; + + // finish job + job.finish(params.time); + // notify watchers that a job is finished. + if (this.watchers[jobId] !== undefined) { + for (var i = 0; i < this.watchers[jobId].length; i++) { + var val = this.watchers[jobId][i]; + var address = val.address; + var parentJobId = val.parentJobId; + var uuid = val.uuid; + this.rpc.request(address, {method:'watchedJobFinished', params:{ + uuid: uuid, + parentJobId: parentJobId, + duration: job.duration.getData() + }}).done(); + } + } + // cleanup watchers + delete this.watchers[jobId]; + + // move from open to closed jobs. + if (this.closedJobs[agentId] === undefined) { + this.closedJobs[agentId] = {}; + } + if (this.closedJobs[agentId][jobId] !== undefined) { + console.log('cannot close job, jobId:', jobId, ' already exists!'); + throw new Error('cannot close job, jobId:' + jobId + ' already exists!'); + } + this.closedJobs[agentId][jobId] = this.openJobs[agentId][jobId]; + + delete this.openJobs[agentId][jobId]; + + this.updateStats(); + + return { + elapsedTime: this.closedJobs[agentId][jobId].elapsedTime, + elapsedTimeWithPause: this.closedJobs[agentId][jobId].elapsedTimeWithPause, + duration: this.closedJobs[agentId][jobId].duration.getData(), + prediction: this.globalStats.getData() + }; +}; + +/** + * update the job of an agent + * @param params + */ +JobAgent.prototype.rpcFunctions.update = function(params) { + var agentId = params.agentId; + var jobId = params.jobId; + var job = this.openJobs[agentId][jobId]; + var operation = params.operation; + + switch (operation) { + case 'pause': + job.pause(params.time, false); + break; + case 'endOfDay': + job.pause(params.time, true); + break; + case 'startOfDay': + job.resume(params.time, true); + break; + case 'resume': + job.resume(params.time, false); + break; + } + return {jobId: jobId, type: this.type, elapsedTime: job.elapsedTime, elapsedTimeWithPause: job.elapsedTimeWithPause}; +}; + +/** + * return agent stats + * @param params + * @returns {*} + */ +JobAgent.prototype.rpcFunctions.getAgentStats = function(params) { + return this.agentStats[params.agentId]; +}; + + +/** + * return global stats + * @param params + * @returns {{mean: number, std: number}|*} + */ +JobAgent.prototype.rpcFunctions.getGlobalStats = function(params) { + return this.globalStats; +}; + +JobAgent.prototype.rpcFunctions.watchedJobFinished = function(params) { + var jobId = params.parentJobId; + this.allJobs[jobId].prerequisiteFinished(params); +}; + + +/** + * + * @param params + * @param sender + * @returns {*} + */ +JobAgent.prototype.rpcFunctions.addWatcherOnJobId = function(params, sender) { + var jobId = params.jobId; + var uuid = params.uuid; + var parentJobId = params.parentJobId; + var job = this.allJobs[jobId]; + + // if the job is already finished, call the finished callback + if (job.finished == true) { + this.rpc.request(sender, {method:'watchedJobFinished', params:{ + uuid: uuid, + parentJobId: parentJobId, + duration: job.duration.getData() // we need the pure json data, not the class + }}).done(); + } + else { + // we will create a watcher on a job which will alert the watcher when the job is finished with all the times. + if (this.watchers[jobId] === undefined) { + this.watchers[jobId] = []; + } + this.watchers[jobId].push({address: params.address, uuid: uuid}); + } + + // return the best prediction we have + if (this.agentStats[job.agentId].mean == 0) { + return this.globalStats.getData(); // we need the pure json data, not the class + } + return this.agentStats[job.agentId].getData(); // we need the pure json data, not the class +}; + + +/** + * + * @param params + * @param sender + * @returns {*} + */ +JobAgent.prototype.rpcFunctions.addWatcherByAgentID = function(params, sender) { + var agentId = params.agentId; + var parentJobId = params.parentJobId; + var jobId = null; + var uuid = params.uuid; + var returnStats; + + // see which statistics collection we will need to return. + if (this.agentStats[agentId].duration.mean == 0) { + returnStats = this.globalStats; + } + else { + returnStats = this.agentStats[agentId]; + } + // see if we have an open job with that agent of this type + if (this.openJobs[agentId] !== undefined) { + for (var jId in this.openJobs[agentId]) { + if (this.openJobs[agentId].hasOwnProperty(jId)) { + jobId = jId; + break; + } + } + } + // there is no open job from supplied agent of this type. return the mean of the return stats + if (jobId === null) { + this.rpc.request(params.address, {method:'watchedJobFinished', params:{ + uuid: uuid, + parentJobId: parentJobId, + duration: returnStats.getMeanData(), // we need the pure json data, not the class + oldData: true + }}).done(); + } + else { + params.jobId = jobId; + this.rpcFunctions.addWatcherOnJobId.call(this, params, sender); + } + + // return the best prediction we have + return returnStats.getData(); // we need the pure json data, not the class +}; + +/** + * + * @param params + * @param sender + * @returns {*} + */ +JobAgent.prototype.rpcFunctions.addWatcherByType = function(params, sender) { + // since we cannot watch a global type, we return the global stats at that point. + this.rpc.request(params.address, {method:'watchedJobFinished', params:{ + uuid: params.uuid, + parentJobId: params.parentJobId, + duration: this.globalStats.getMeanData(), // we need the pure json data, not the class + oldData: true + }}).done(); + return this.globalStats.getData(); // we need the pure json data, not the class +}; + + +/** + * + * @param parentJobId | ID from the job that wants to WATCH other jobs + * @param prerequisites + */ +JobAgent.prototype.addWatchers = function(parentJobId, prerequisites) { + for (var i = 0; i < prerequisites.length; i++) { + var prereq = prerequisites[i]; + var params = { + uuid: prereq.uuid, + address: this.id, // this is the callback address + parentJobId: parentJobId// this is the job that wants to watch the other one. + }; + var me = this; + if (prereq.jobId !== undefined) { + // we now have a parentJobId to watch + // we first need to find the type of job this belongs to. + this.rpc.request('JobAgentGenerator', {method: 'returnJobAddress', params: {jobId: prereq.jobId}}) + // now that we have an address, set a watcher on the job id + .done(function (address) { + if (address != 'doesNotExist') { + params.jobId = prereq.jobId; // this is the job we want to watch + me.rpc.request(address, {method: 'addWatcherOnJobId', params: params}) + .done(function (preliminaryStats) { + me.allJobs[parentJobId].watchingPrerequisite(preliminaryStats, prereq.uuid); + }) + } + else { + console.log('ERROR: watch job does not exist.'); + throw new Error('ERROR: watch job does not exist.'); + } + }); + } + else if (prereq.agentId !== undefined && prereq.type !== undefined) { + // we now have an agentId and a jobType to watch. + params.agentId = prereq.agentId; // this is the job we want to watch + this.rpc.request(prereq.type, {method: 'addWatcherByAgentID', params: params}) + .done(function (preliminaryStats) { + me.allJobs[parentJobId].watchingPrerequisite(preliminaryStats, prereq.uuid); + }) + } + else if (prereq.type !== undefined) { + this.rpc.request(prereq.type, {method: 'addWatcherByType', params: params}) + .done(function (preliminaryStats) { + me.allJobs[parentJobId].watchingPrerequisite(preliminaryStats, prereq.uuid); + }) + } + } +}; + +JobAgent.prototype.updatePredictedStartup = function(jobId, prediction) { + var jobPrediction = this.allJobs[jobId].predictedStartupTime; + jobPrediction.mean = Math.max(jobPrediction.mean, prediction.mean); + jobPrediction.std = Math.sqrt(Math.pow(jobPrediction.std,2) + Math.pow(prediction.std,2)); + + this.allJobs[jobId].prerequisitesCount += 1; +}; + + +/** + * Update all statistics + * + */ +JobAgent.prototype.updateStats = function() { + this.globalStats.clearStats(); + + var count = 0; + for (var agentId in this.closedJobs) { + if (this.closedJobs.hasOwnProperty(agentId)) { + var collection = this.closedJobs[agentId]; + // could be optimised with rolling average for efficient memory management + this.agentStats[agentId].setData(this.updateStatsIn(collection)); + this.globalStats.sumStats(this.agentStats[agentId]); + count += 1; + } + } + this.globalStats.averageStats(count); +}; + + +/** + * + * @param collection + * @returns {{duration: *, durationWithPause: *, durationWithStartup: *, durationWithBoth: *}} + */ +JobAgent.prototype.updateStatsIn = function(collection) { + var stats = {}; + for (var i = 0; i < this.globalStats.fields.length; i++) { + var field = this.globalStats.fields[i]; + stats[field] = this.collectStatsIn(collection, field); + } + return stats; +}; + + +JobAgent.prototype.collectStatsIn = function(collection, field) { + var total = 0; + var mean = 0; + var std = 0; + var minVal = 1e16; + var maxVal = 0; + var count = 0; + + for (var jobId in collection) { + if (collection.hasOwnProperty(jobId)) { + var value = collection[jobId].duration[field]; + maxVal = value > maxVal ? value : maxVal; + minVal = value < minVal ? value : minVal; + + total += collection[jobId].duration[field]; + count += 1; + } + } + if (count > 0) { + mean = total / count; + for (var jobId in collection) { + if (collection.hasOwnProperty(jobId)) { + std += Math.pow(collection[jobId].duration[field] - mean,2); + } + } + + std = Math.sqrt(std/count); + return {mean: mean, std: std, min: minVal, max: maxVal}; + } + else { + return {mean: 0, std: 0, min: 0, max: 0}; + } +}; + +JobAgent.prototype.hasJob = function(params) { + return this.allJobs[params.jobId] !== undefined; +}; diff --git a/showcase/projects/midas/arumAgents/jobAgentGenerator.js b/showcase/projects/midas/arumAgents/jobAgentGenerator.js new file mode 100644 index 00000000..8d3659f8 --- /dev/null +++ b/showcase/projects/midas/arumAgents/jobAgentGenerator.js @@ -0,0 +1,169 @@ +'use strict'; + +function JobAgentGenerator(id) { + // execute super constructor + eve.Agent.call(this, id); + this.rpc = this.loadModule('rpc', this.rpcFunctions); + this.connect(eve.system.transports.getAll()); +} + +// extend the eve.Agent prototype +JobAgentGenerator.prototype = Object.create(eve.Agent.prototype); +JobAgentGenerator.prototype.constructor = AgentGenerator; + +// define RPC functions, preferably in a separated object to clearly distinct +// exposed functions from local functions. +JobAgentGenerator.prototype.rpcFunctions = {}; + + +JobAgentGenerator.prototype.rpcFunctions.createJob = function(params) { + var jobAgentName = params.type; + if (jobList[jobAgentName] === undefined) { + jobList[jobAgentName] = new JobAgent(jobAgentName); + + graph2dGroups.add([ + { + id: jobAgentName+'_pred_duration_std_lower', + content: "prediction", + className: 'prediction_std', + options: {drawPoints:false} + }, + { + id: jobAgentName+'_pred_durationWithPause_std_lower', + content: "predWithPause", + className: 'prediction_std', + options: {drawPoints:false} + }, + { + id: jobAgentName+'_pred_durationWithStartup_std_lower', + content: "predWithStartup", + className: 'prediction_std', + options: {drawPoints:false} + }, + { + id: jobAgentName+'_pred_durationWithBoth_std_lower', + content: "predWithBoth", + className: 'prediction_std', + options: {drawPoints:false} + }, + { + id: jobAgentName+'_pred_duration_std_higher', + content: "prediction", + className: 'prediction_std', + options: {drawPoints:false} + }, + { + id: jobAgentName+'_pred_durationWithPause_std_higher', + content: "predWithPause", + className: 'prediction_std', + options: {drawPoints:false} + }, + { + id: jobAgentName+'_pred_durationWithStartup_std_higher', + content: "predWithStartup", + className: 'prediction_std', + options: {drawPoints:false} + }, + { + id: jobAgentName+'_pred_durationWithBoth_std_higher', + content: "predWithBoth", + className: 'prediction_std', + options: {drawPoints:false} + },{ + id: jobAgentName+'_pred_duration_original', + content: "prediction", + className: 'prediction_original' + }, + { + id: jobAgentName+'_pred_durationWithPause_original', + content: "predWithPause", + className: 'prediction_original' + }, + { + id: jobAgentName+'_pred_durationWithStartup_original', + content: "predWithStartup", + className: 'prediction_original' + }, + { + id: jobAgentName+'_pred_durationWithBoth_original', + content: "predWithBoth", + className: 'prediction_original' + }, + { + id: jobAgentName+'_pred_duration', + content: "prediction", + className: 'prediction' + }, + { + id: jobAgentName+'_pred_durationWithPause', + content: "predWithPause", + className: 'prediction' + }, + { + id: jobAgentName+'_pred_durationWithStartup', + content: "predWithStartup", + className: 'prediction' + }, + { + id: jobAgentName+'_pred_durationWithBoth', + content: "predWithBoth", + className: 'prediction' + }, + { + id: jobAgentName+'_duration', + content: "duration", + className: 'duration' + }, + { + id: jobAgentName+'_durationWithPause', + content: "durationWithPause", + className: 'duration' + }, + { + id: jobAgentName+'_durationWithStartup', + content: "durationWithStartup", + className: 'duration' + }, + { + id: jobAgentName+'_durationWithBoth', + content: "durationWithBoth", + className: 'duration' + } + ]); + var visibilityUpdate = {}; + //visibilityUpdate[jobAgentName+'_pred'] = false; + //visibilityUpdate[jobAgentName+'_predWithPause'] = false; + //visibilityUpdate[jobAgentName+'_predWithStartup'] = false; + //visibilityUpdate[jobAgentName+'_predWithBoth'] = false; + //visibilityUpdate[jobAgentName+'_duration'] = false; + //visibilityUpdate[jobAgentName+'_durationWithPause'] = false; + //visibilityUpdate[jobAgentName+'_durationWithStartup'] = false; + //visibilityUpdate[jobAgentName+'_durationWithBoth'] = false; + graph2d.setOptions({groups:{visible:visibilityUpdate}}); + refreshJobs(); + } +}; + +JobAgentGenerator.prototype.rpcFunctions.returnJobAddress = function(params) { + var instanceId = params.instanceId; + var hasJob = false; + for (var jobAgentName in jobList) { + if (jobList.hasOwnProperty(jobAgentName)) { + hasJob = jobList[jobAgentName].hasJob(instanceId); + if (hasJob == true) { + return jobAgentName; + } + } + } + return "doesNotExist"; +}; + +JobAgentGenerator.prototype.getAllJobNames = function() { + var list = []; + for (var jobAgentName in jobList) { + if (jobList.hasOwnProperty(jobAgentName)) { + list.push(jobAgentName); + } + } + return list; +}; diff --git a/showcase/projects/midas/arumAgents/jobManager.js b/showcase/projects/midas/arumAgents/jobManager.js new file mode 100644 index 00000000..7626c5ce --- /dev/null +++ b/showcase/projects/midas/arumAgents/jobManager.js @@ -0,0 +1,428 @@ +'use strict'; + +function JobManager(agent) { + this.agent = agent; + this.jobs = { + id:{}, + type:{ + open:{}, + closed:{} + } + }; + this.openJobs = {}; +} + +JobManager.prototype.add = function(id, type, time, prerequisites) { + var me = this; + + // create job agent. This agent will keep track of the global job stats. Jobs per type. + this.agent.rpc.request('jobAgentGenerator',{method:'createJob', params:{type:type}}).done(); + + this.jobs.id[id] = { + type: type, + startTime: time, + prediction: null, + predictionCounter: 0, + elapsedTime: 0, + elapsedTimeWithPause: 0, + pauseCount: 0, + endOfDay: false, + paused: false, + pauseAreaID: "" + }; + + // assign agent to job. + this.agent.rpc.request(type, {method:'add', params:{ + agentId: this.agent.id, + time:time, + jobId: id, + prerequisites: prerequisites + }}) + .done(function (prediction) { + if (prediction.duration.mean != 0) { + me.agent.timelineDataset.update({ + id: id + "_predMean0", + start: time, + end: new Date(time).getTime() + prediction.duration.mean, + group: me.agent.id, + type: 'range', + content: "", + subgroup: me.agent.usedSubgroups[type], + className: 'prediction' + }); + } + me.jobs.id[id].prediction = prediction; + }); + + if (this.jobs.type.open[type] === undefined) {this.jobs.type.open[type] = {}} + this.jobs.type.open[type][id] = time; + this.openJobs[id] = this.jobs.id[id]; + + + var addQuery = [{id:id, start:time, content:"started: "+ type, group:this.agent.id, subgroup: this.agent.usedSubgroups[type]}]; + this.agent.timelineDataset.add(addQuery); + this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}}).done(); +}; + +JobManager.prototype.finish = function(id, type, time) { + var me = this; + // finish the job. + this.agent.rpc.request(type, {method:'finish', params:{ + agentId: this.agent.id, + time: time, + jobId: id + }}) + .done(function (reply) { + var prediction = reply.prediction; + var originalPrediction = me.jobs.id[id].prediction; + me.jobs.id[id].elapsedTime = reply.elapsedTime; + me.jobs.id[id].elapsedTimeWithPause = reply.elapsedTimeWithPause; + me.updateDataSetsFinish(id, type, time, me.jobs.id[id].prediction); + + graph2dDataset.push({x: time, y: reply.duration.duration/3600000 ,group: type + '_duration', type: type}); + graph2dDataset.push({x: time, y: reply.duration.durationWithPause/3600000 ,group: type + '_durationWithPause', type: type}); + graph2dDataset.push({x: time, y: reply.duration.durationWithStartup/3600000 ,group: type + '_durationWithStartup', type: type}); + graph2dDataset.push({x: time, y: reply.duration.durationWithBoth/3600000 ,group: type + '_durationWithBoth', type: type}); + graph2dDataset.push({x: time, y: prediction.duration.mean/3600000 ,group: type + '_pred_duration', type: type}); + graph2dDataset.push({x: time, y: prediction.durationWithPause.mean/3600000 ,group: type + '_pred_durationWithPause', type: type}); + graph2dDataset.push({x: time, y: prediction.durationWithStartup.mean/3600000 ,group: type + '_pred_durationWithStartup', type: type}); + graph2dDataset.push({x: time, y: prediction.durationWithBoth.mean/3600000 ,group: type + '_pred_durationWithBoth', type: type}); + graph2dDataset.push({x: time, y: (prediction.duration.mean + prediction.duration.std)/3600000 ,group: type + '_pred_duration_std_higher', type: type}); + graph2dDataset.push({x: time, y: (prediction.durationWithPause.mean + prediction.durationWithPause.std)/3600000 ,group: type + '_pred_durationWithPause_std_higher', type: type}); + graph2dDataset.push({x: time, y: (prediction.durationWithStartup.mean + prediction.durationWithStartup.std)/3600000 ,group: type + '_pred_durationWithStartup_std_higher', type: type}); + graph2dDataset.push({x: time, y: (prediction.durationWithBoth.mean + prediction.durationWithBoth.std)/3600000 ,group: type + '_pred_durationWithBoth_std_higher', type: type}); + graph2dDataset.push({x: time, y: (prediction.duration.mean - prediction.duration.std)/3600000 ,group: type + '_pred_duration_std_lower', type: type}); + graph2dDataset.push({x: time, y: (prediction.durationWithPause.mean - prediction.durationWithPause.std)/3600000 ,group: type + '_pred_durationWithPause_std_lower', type: type}); + graph2dDataset.push({x: time, y: (prediction.durationWithStartup.mean - prediction.durationWithStartup.std)/3600000 ,group: type + '_pred_durationWithStartup_std_lower', type: type}); + graph2dDataset.push({x: time, y: (prediction.durationWithBoth.mean - prediction.durationWithBoth.std)/3600000 ,group: type + '_pred_durationWithBoth_std_lower', type: type}); + graph2dDataset.push({x: time, y: originalPrediction.duration.mean/3600000 ,group: type + '_pred_duration_original', type: type}); + graph2dDataset.push({x: time, y: originalPrediction.durationWithPause.mean/3600000 ,group: type + '_pred_durationWithPause_original', type: type}); + graph2dDataset.push({x: time, y: originalPrediction.durationWithStartup.mean/3600000 ,group: type + '_pred_durationWithStartup_original', type: type}); + graph2dDataset.push({x: time, y: originalPrediction.durationWithBoth.mean/3600000 ,group: type + '_pred_durationWithBoth_original', type: type}); + }); + + delete this.jobs.type.open[type][id]; + delete this.openJobs[id]; + if (this.jobs.type.closed[type] === undefined) {this.jobs.type.closed[type] = {}} + this.jobs.type.closed[type][id] = time; +}; + +JobManager.prototype.update = function(id, type, time, operation) { + var me = this; + var eventId = uuid(); + if (operation == 'endOfDay' || operation == 'startOfDay') { + for (var jobId in this.openJobs) { + if (this.openJobs.hasOwnProperty(jobId)) { + var type = this.openJobs[jobId].type; + this.agent.rpc.request(type, {method:'update', params:{ + agentId: this.agent.id, + time: time, + jobId: jobId, + operation: operation + }}) + .done(function (reply) { + me.jobs.id[reply.jobId].elapsedTime = reply.elapsedTime; + me.jobs.id[reply.jobId].elapsedTimeWithPause = reply.elapsedTimeWithPause; + me.updateDataSetsOperation(reply.jobId, reply.type, time, operation, eventId); + }); + + } + } + } + else { + this.agent.rpc.request(type, {method:'update', params:{ + agentId: this.agent.id, + time: time, + jobId: id, + operation: operation + }}) + .done(function (reply) { + me.jobs.id[id].elapsedTime = reply.elapsedTime; + me.jobs.id[id].elapsedTimeWithPause = reply.elapsedTimeWithPause; + me.updateDataSetsOperation(id, type, time, operation, eventId); + }); + } +}; + + +JobManager.prototype.updateDataSetsOperation = function(id, type, time, operation, eventId) { + switch (operation) { + case 'pause': + this.updateDataSetsPause(id, type, time, operation, this.jobs.id[id].prediction, eventId); + break; + case 'endOfDay': + this.updateDataSetsPause(id, type, time, operation, this.jobs.id[id].prediction, eventId); + break; + case 'startOfDay': + this.updateDataSetsResume(id, type, time, operation, this.jobs.id[id].prediction, eventId); + break; + case 'resume': + this.updateDataSetsResume(id, type, time, operation, this.jobs.id[id].prediction, eventId); + break; + } +}; + +JobManager.prototype.updateDataSetsFinish = function(id, type, time, prediction) { + var updateQuery = []; + var field = 'duration'; + var elapsedTime = this.jobs.id[id].elapsedTime; + + // gather statistic indicator data + if (this.jobs.id[id].pauseCount > 0) { + field = 'durationWithPause'; + elapsedTime = this.jobs.id[id].elapsedTimeWithPause; + } + + // generate indicator + var predictedTimeLeft = 0; + if (prediction[field].mean != 0) { + predictedTimeLeft = prediction[field].mean - elapsedTime; + this.updatePredictionOnFinish(id, type, time, prediction[field], elapsedTime); + } + + updateQuery.push({ + id: id, + end: time, + content: type, + type: 'range', + className: 'finished', + style: 'background-color: ' + this.generateColors(predictedTimeLeft, elapsedTime) + ' !important;' + }); + + this.agent.freeSubgroup(type); + this.agent.timelineDataset.update(updateQuery); + this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}}).done(); +}; + +JobManager.prototype.updateDataSetsPause = function(id, type, time, operation, prediction, eventId) { + var updateQuery = []; + var image = ''; + var flagId = id + "_pauseNotifier" + eventId; + var title = 'Calling RAO for possible NC'; + + // this causes there to be only one flag for the end of day as well as a moon icon + if (operation == 'endOfDay') { + // don't end-of-day jobs twice + if (this.jobs.id[id].endOfDay == true) { + return; + } + this.jobs.id[id].endOfDay = true; + image = ''; + flagId = id + "endOfDayNotifier" + eventId; + title = "End of working day" + } + else { + this.jobs.id[id].pauseCount += 1; + this.jobs.id[id].pauseAreaID = this.agent.id + "_" + "_pauseArea_" + eventId; + var shadedPauseArea = { + id: this.jobs.id[id].pauseAreaID, + start: time, + end: time, + content: '', + group: this.agent.id, + subgroup: this.agent.usedSubgroups[type], + className: 'pausedArea', + title: 'Job paused.' + }; + updateQuery.push(shadedPauseArea); + } + + var field = 'duration'; + var elapsedTime = this.jobs.id[id].elapsedTime; + if (this.jobs.id[id].pauseCount > 0) { + field = 'durationWithPause'; + elapsedTime = this.jobs.id[id].elapsedTimeWithPause; + } + + updateQuery.push({id: id, end: time, content: type, type: 'range'}); + var imageUpdate = { + id: flagId, + start: time, + end: time, + content: image, + group: this.agent.id, + subgroup: this.agent.usedSubgroups[type], + className: 'pause', + title: title + }; + + + if (this.agent.id == "Paolo") { + imageUpdate.title = "Going to inspect possible NC."; + } + updateQuery.push(imageUpdate); + + var predictedTimeLeft = prediction[field].mean - elapsedTime; + var predictionExists = prediction[field].mean != 0; + + // update the predicted line if the job is not ALREADY pauseds + if (predictedTimeLeft > 0 && predictionExists == true && this.jobs.id[id].paused != true) { + updateQuery.push({ + id: id + "_predMean" + this.jobs.id[id].predictionCounter, + end: time, + group: this.agent.id, + className: 'prediction' + }) + } + + // set the status to paused if needed + if (operation != 'endOfDay') { + this.jobs.id[id].paused = true; + } + this.agent.timelineDataset.update(updateQuery); + this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}}) +}; + +JobManager.prototype.updateDataSetsResume = function(id, type, time, operation, prediction, eventId) { + var updateQuery = []; + var image = ''; + var flagId = id + "_resumeNotifier" + eventId; + + // this causes there to be only one flag for the start of day as well as a sun icon + if (operation == 'startOfDay') { + // don't start-of-day jobs twice + if (this.jobs.id[id].endOfDay == false) { + return; + } + this.jobs.id[id].endOfDay = false; + image = ''; + flagId = id + "startOfDayNotifier_" + eventId; + } + else { + updateQuery.push({id: this.jobs.id[id].pauseAreaID, end: time, content: '', type: 'range'}); + this.jobs.id[id].pauseAreaID = ""; + this.jobs.id[id].pauseCount += 1; + } + + var field = 'duration'; + var elapsedTime = this.jobs.id[id].elapsedTime; + if (this.jobs.id[id].pauseCount > 0) { + field = 'durationWithPause'; + elapsedTime = this.jobs.id[id].elapsedTimeWithPause; + } + + updateQuery.push({id: id, end: time, content: type, type: 'range'}); + updateQuery.push({ + id: flagId, + start: time, + end: time, + content: image, + group: this.agent.id, + subgroup: this.agent.usedSubgroups[type], + className: 'pause', + title: 'Resuming job' + }); + + var predictedTimeLeft = prediction[field].mean - elapsedTime; + // no not increase the prediction line at the start of the day if the job is PAUSED + if (predictedTimeLeft > 0 && !(operation == 'startOfDay' && this.jobs.id[id].paused == true)) { + this.jobs.id[id].predictionCounter += 1; + updateQuery.push({ + id: id + "_predMean" + this.jobs.id[id].predictionCounter, + start: time, + end: new Date(time).getTime() + predictedTimeLeft, + group: this.agent.id, + content: "", + subgroup: this.agent.usedSubgroups[type], + type: 'range', + className: 'prediction' + }); + } + + // resume if needed + if (operation != 'startOfDay'){ + this.jobs.id[id].paused = false; + } + this.agent.timelineDataset.update(updateQuery); + this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}}) +}; + +JobManager.prototype.updatePredictionOnFinish = function(id,type,time,prediction,elapsedTime) { + if (prediction.mean != 0) { + var predictedTimeLeft = prediction.mean - elapsedTime; + if (predictedTimeLeft > 0) { + this.agent.timelineDataset.remove({id: id + "_predMean" + this.jobs.id[id].predictionCounter}) + } + } +}; + +JobManager.prototype.updateJobs = function(time, skipId) { + var updateQuery = []; + for (var jobId in this.openJobs) { + if (this.openJobs.hasOwnProperty(jobId) && jobId != skipId) { + var type = this.openJobs[jobId].type; + var prediction = this.openJobs[jobId].prediction; + var predictedTimeLeft = 0; + // never been paused before + if (this.jobs.id[jobId].pauseCount == 0) { + if (prediction !== null && prediction.duration !== null) { + predictedTimeLeft = prediction.duration.mean - this.jobs.id[jobId].elapsedTime; + } + } + else { + if (prediction !== null && prediction.durationWithPause !== null) { + predictedTimeLeft = prediction.durationWithPause.mean - this.jobs.id[jobId].elapsedTimeWithPause; + } + } + updateQuery.push({id: jobId, end: time, content: type, type: 'range'}); + if (this.openJobs[jobId].paused == true) { + updateQuery.push({id: this.openJobs[jobId].pauseAreaID, end: time}); + } + } + } + + this.agent.timelineDataset.update(updateQuery); +}; + + +JobManager.prototype.generateColors = function(predictedTime, elapsedTime) { + var ratio = (elapsedTime + predictedTime) / elapsedTime; + if (ratio > 1) { + ratio = Math.min(2,ratio) - 1; // 2 -- 1 + var rgb = HSVToRGB(94/360,ratio*0.6 + 0.2,1); + } + else { + ratio = Math.max(0.5,ratio) - 0.5; // 1 -- 0.5 + var rgb = HSVToRGB(40/360,(1-(2*ratio))*0.6 + 0.1 ,1); + } + return "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")"; +}; + +function HSVToRGB(h, s, v) { + var r, g, b; + + var i = Math.floor(h * 6); + var f = h * 6 - i; + var p = v * (1 - s); + var q = v * (1 - f * s); + var t = v * (1 - (1 - f) * s); + + switch (i % 6) { + case 0: r = v, g = t, b = p; break; + case 1: r = q, g = v, b = p; break; + case 2: r = p, g = v, b = t; break; + case 3: r = p, g = q, b = v; break; + case 4: r = t, g = p, b = v; break; + case 5: r = v, g = p, b = q; break; + } + + return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) }; +} +function RGBToHSV (red,green,blue) { + red=red/255; green=green/255; blue=blue/255; + var minRGB = Math.min(red,Math.min(green,blue)); + var maxRGB = Math.max(red,Math.max(green,blue)); + + // Black-gray-white + if (minRGB == maxRGB) { + return {h:0,s:0,v:minRGB}; + } + + // Colors other than black-gray-white: + var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red); + var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5); + var hue = 60*(h - d/(maxRGB - minRGB))/360; + var saturation = (maxRGB - minRGB)/maxRGB; + var value = maxRGB; + return {h:hue,s:saturation,v:value}; +} \ No newline at end of file diff --git a/showcase/projects/midas/css/global.css b/showcase/projects/midas/css/global.css new file mode 100644 index 00000000..6f297980 --- /dev/null +++ b/showcase/projects/midas/css/global.css @@ -0,0 +1,301 @@ +body { + font-family : Verdana, "Bitstream Vera Sans", "DejaVu Sans", Tahoma, Geneva, Arial, Sans-serif; + font-size: 13px; + min-width:980px; +} + +div.group { + background-color: rgba(0, 0, 0, 0.005); +} + +/*.vis.timeline .item.background {*/ +/*background-color: rgba(40, 148, 255, 0.20) !important;*/ +/*z-index:9999 !important;*/ +/*}*/ +.vis.timeline .item.range.prediction { + border: 1px solid rgba(0, 164, 255, 0.50) !important; + background-color: rgba(28, 183, 255, 0.10) !important; + box-shadow: rgba(0, 83, 128, 0.15) 0px 0px 10px !important; + z-index:-1 !important; + height:34px !important; +} +.vis.timeline .item.background.night { + background-color: rgba(60, 60, 60, 0.3) !important; + z-index:9999 !important; +} +.vis.timeline .labelset .timelineGroup .inner { + /*height:100px !important;*/ + width:140px !important; +} + +.vis.timeline .labelset .timelineGroup.worker{ + background-color: #ffffff; +} +.vis.timeline .labelset .timelineGroup.rao{ + background-color: #f5f5f5; +} +.vis.timeline .labelset .timelineGroup.pm{ + background-color: #dfeaf2; +} +.vis.timeline .labelset .timelineGroup.mt{ + background-color: #b8d6e6; +} + +.vis.timeline .item.range { + height:34px !important; +} + +.vis.timeline .item.range.pause{ + height:34px !important; + width:26px !important; + box-shadow: rgba(0,0,0,0.2) 0px 0px 10px !important; + padding:3px !important; + z-index:999999; + background-color: #ffffff !important +} + +.vis.timeline .item.range.pausedArea{ + height:34px !important; + padding:3px !important; + z-index:999950; + border-width: 0px !important; + background: url('../images/shaded.png'); + background-color: rgba(0,0,0,0) !important; +} + +.vis.timeline .item { + border-radius: 0px !important; + border-color: #7d807d !important; + background-color: #ffffff !important; + /*background-color: #c4e0ff !important;*/ + +} +.vis.timeline .item.box { + height:22px !important; + border-radius: 0px !important; + border-color: #7d807d !important; + background-color: #ffffff !important; + box-shadow: rgba(0, 0, 0, 0.52) 0px 0px 12px 0px; +} + +img.icon{ + width:16px; + height:16px; +} + +#multiselect { + position:relative; + top:1px; + width:200px; + height:452px; +} + +table.wrapper { + position:relative; + height:500px; + width:100%; +} + +/*table.wrapper td {*/ + /*border:1px solid #ff0000;*/ +/*}*/ + +#selectTD { + width:200px; +} + +div.typeButtons { + height:50px; + width:100%; + overflow:hidden; +} + +div.typeButton { + display:inline-block; + width:150px; + height:24px; + border:2px solid #ffffff; + border-radius: 40px; + text-align:center; + font-size:12px; + color: #848484; + padding-top:8px; + background-color: #dedede; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor:pointer; + margin:5px; + box-shadow: rgba(50,50,50,0.3) 0px 0px 3px; +} + +div.typeButton.selected { + background-color: #9aff4d; + color: #000000; +} + +div.toggleButton { + background-image: url("../images/toggleOff.png"); + background-repeat: no-repeat; + background-position: 2px 2px; + display:inline-block; + width:138px; + height:24px; + font-size:12px; + border:1px solid #d0d0d0; + text-align:left; + padding-top:10px; + padding-left:60px; + padding-right:0px; + border-radius: 40px; + background-color: #ffffff; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor:pointer; +} + +div.toggleButton.square{ + height:28px; + border-radius: 0px; +} + +div.toggleButton.selected { + background-image: url("../images/toggleOn.png"); + background-color: #ffffff; +} + +div.graph2dContent{ + height:450px; + width:100%; +} + +#graphWrapper { + display:none; + border: 1px solid #dddddd; + padding:5px; +} + +#timelineWrapper { + display:block; + border: 1px solid #dddddd; + padding:5px; +} + +.prediction_original { + fill: rgb(123, 199, 255); + fill-opacity:0; + stroke-width:2px; + stroke: rgb(123, 199, 255); +} + +.prediction { + fill: #77da2e; + fill-opacity:0; + stroke-width:2px; + stroke: #77da2e; +} + +.duration { + fill: rgb(170, 34, 206); + fill-opacity:0; + stroke-width:2px; + stroke: rgb(170, 34, 206); +} + +.fill { + fill-opacity:0.1; + stroke: none; +} + + +.bar { + fill-opacity:0.5; + stroke-width:1px; +} + +.point { + stroke-width:2px; + fill-opacity:1.0; +} + + +.legendBackground { + stroke-width:1px; + fill-opacity:0.9; + fill: #ffffff; + stroke: #c2c2c2; +} + + +.outline { + stroke-width:1px; + fill-opacity:1; + fill: #ffffff; + stroke: #e5e5e5; +} + +.iconFill { + fill-opacity:0.3; + stroke: none; +} + +div.descriptionContainer { + float:left; + height:30px; + min-width:160px; + padding-left:5px; + padding-right:5px; + line-height: 30px; +} + +div.iconContainer { + float:left; +} + +div.legendElementContainer { + display:inline-block; + height:30px; + border-style:solid; + border-width:1px; + border-color: #d0d0d0; + background-color: #ffffff; + margin-right:6px; + padding:4px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor:pointer; +} + +svg.legendIcon { + width:30px; + height:30px; +} + +div.externalLegend { + position:relative; + top:1px; + width: 700px; +} + +.differencePositive { + fill: #afff33; + stroke-width:2px; + stroke: #96db22; +} + +.differenceNegative { + fill: #ff9f00; + stroke-width:2px; + stroke: #e58f00; +} \ No newline at end of file diff --git a/showcase/projects/midas/css/vis.css b/showcase/projects/midas/css/vis.css new file mode 100644 index 00000000..2bbe58fb --- /dev/null +++ b/showcase/projects/midas/css/vis.css @@ -0,0 +1,745 @@ +.vis .overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + /* Must be displayed above for example selected Timeline items */ + z-index: 10; +} + +.vis-active { + box-shadow: 0 0 10px #86d5f8; +} + +.vis.timeline { +} + + +.vis.timeline.root { + position: relative; + border: 1px solid #bfbfbf; + + overflow: hidden; + padding: 0; + margin: 0; + + box-sizing: border-box; +} + +.vis.timeline .vispanel { + position: absolute; + + padding: 0; + margin: 0; + + box-sizing: border-box; +} + +.vis.timeline .vispanel.center, +.vis.timeline .vispanel.left, +.vis.timeline .vispanel.right, +.vis.timeline .vispanel.top, +.vis.timeline .vispanel.bottom { + border: 1px #bfbfbf; +} + +.vis.timeline .vispanel.center, +.vis.timeline .vispanel.left, +.vis.timeline .vispanel.right { + border-top-style: solid; + border-bottom-style: solid; + overflow: hidden; +} + +.vis.timeline .vispanel.center, +.vis.timeline .vispanel.top, +.vis.timeline .vispanel.bottom { + border-left-style: solid; + border-right-style: solid; +} + +.vis.timeline .background { + overflow: hidden; +} + +.vis.timeline .vispanel > .content { + position: relative; +} + +.vis.timeline .vispanel .shadow { + position: absolute; + width: 100%; + height: 1px; + box-shadow: 0 0 10px rgba(0,0,0,0.8); + /* TODO: find a nice way to ensure shadows are drawn on top of items + z-index: 1; + */ +} + +.vis.timeline .vispanel .shadow.top { + top: -1px; + left: 0; +} + +.vis.timeline .vispanel .shadow.bottom { + bottom: -1px; + left: 0; +} + +.vis.timeline .labelset { + position: relative; + width: 100%; + + overflow: hidden; + + box-sizing: border-box; +} + +.vis.timeline .labelset .vlabel { + position: relative; + left: 0; + top: 0; + width: 100%; + color: #4d4d4d; + + box-sizing: border-box; +} + +.vis.timeline .labelset .vlabel { + border-bottom: 1px solid #bfbfbf; +} + +.vis.timeline .labelset .vlabel:last-child { + border-bottom: none; +} + +.vis.timeline .labelset .vlabel .inner { + display: inline-block; + padding: 5px; +} + +.vis.timeline .labelset .vlabel .inner.hidden { + padding: 0; +} + + +.vis.timeline .itemset { + position: relative; + padding: 0; + margin: 0; + + box-sizing: border-box; +} + +.vis.timeline .itemset .background, +.vis.timeline .itemset .foreground { + position: absolute; + width: 100%; + height: 100%; + overflow: visible; +} + +.vis.timeline .axis { + position: absolute; + width: 100%; + height: 0; + left: 0; + z-index: 1; +} + +.vis.timeline .foreground .group { + position: relative; + box-sizing: border-box; + border-bottom: 1px solid #bfbfbf; +} + +.vis.timeline .foreground .group:last-child { + border-bottom: none; +} + + +.vis.timeline .item { + position: absolute; + color: #1A1A1A; + border-color: #97B0F8; + border-width: 1px; + background-color: #D5DDF6; + display: inline-block; + padding: 5px; +} + +.vis.timeline .item.selected { + border-color: #FFC200; + background-color: #FFF785; + + /* z-index must be higher than the z-index of custom time bar and current time bar */ + z-index: 2; +} + +.vis.timeline .editable .item.selected { + cursor: move; +} + +.vis.timeline .item.point.selected { + background-color: #FFF785; +} + +.vis.timeline .item.box { + text-align: center; + border-style: solid; + border-radius: 2px; +} + +.vis.timeline .item.point { + background: none; +} + +.vis.timeline .item.dot { + position: absolute; + padding: 0; + border-width: 4px; + border-style: solid; + border-radius: 4px; +} + +.vis.timeline .item.range { + border-style: solid; + border-radius: 2px; + box-sizing: border-box; +} + +.vis.timeline .item.background { + overflow: hidden; + border: none; + background-color: rgba(213, 221, 246, 0.4); + box-sizing: border-box; + padding: 0; + margin: 0; +} + +.vis.timeline .item.range .content { + position: relative; + display: inline-block; + overflow: hidden; + max-width: 100%; +} + +.vis.timeline .item.background .content { + position: absolute; + display: inline-block; + overflow: hidden; + max-width: 100%; + margin: 5px; +} + +.vis.timeline .item.line { + padding: 0; + position: absolute; + width: 0; + border-left-width: 1px; + border-left-style: solid; +} + +.vis.timeline .item .content { + white-space: nowrap; + overflow: hidden; +} + +.vis.timeline .item .delete { + background: url('img/timeline/delete.png') no-repeat top center; + position: absolute; + width: 24px; + height: 24px; + top: 0; + right: -24px; + cursor: pointer; +} + +.vis.timeline .item.range .drag-left { + position: absolute; + width: 24px; + height: 100%; + top: 0; + left: -4px; + + cursor: w-resize; +} + +.vis.timeline .item.range .drag-right { + position: absolute; + width: 24px; + height: 100%; + top: 0; + right: -4px; + + cursor: e-resize; +} + +.vis.timeline .timeaxis { + position: relative; + overflow: hidden; +} + +.vis.timeline .timeaxis.foreground { + top: 0; + left: 0; + width: 100%; +} + +.vis.timeline .timeaxis.background { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.vis.timeline .timeaxis .text { + position: absolute; + color: #4d4d4d; + padding: 3px; + white-space: nowrap; +} + +.vis.timeline .timeaxis .text.measure { + position: absolute; + padding-left: 0; + padding-right: 0; + margin-left: 0; + margin-right: 0; + visibility: hidden; +} + +.vis.timeline .timeaxis .grid.vertical { + position: absolute; + width: 0; + border-right: 1px solid; +} + +.vis.timeline .timeaxis .grid.minor { + border-color: #e5e5e5; +} + +.vis.timeline .timeaxis .grid.major { + border-color: #bfbfbf; +} + +.vis.timeline .currenttime { + background-color: #FF7F6E; + width: 2px; + z-index: 1; +} +.vis.timeline .customtime { + background-color: #6E94FF; + width: 2px; + cursor: move; + z-index: 1; +} +.vis.timeline.root { + /* + -webkit-transition: height .4s ease-in-out; + transition: height .4s ease-in-out; + */ +} + +.vis.timeline .vispanel { + /* + -webkit-transition: height .4s ease-in-out, top .4s ease-in-out; + transition: height .4s ease-in-out, top .4s ease-in-out; + */ +} + +.vis.timeline .axis { + /* + -webkit-transition: top .4s ease-in-out; + transition: top .4s ease-in-out; + */ +} + +/* TODO: get animation working nicely + +.vis.timeline .item { + -webkit-transition: top .4s ease-in-out; + transition: top .4s ease-in-out; +} + +.vis.timeline .item.line { + -webkit-transition: height .4s ease-in-out, top .4s ease-in-out; + transition: height .4s ease-in-out, top .4s ease-in-out; +} +/**/ + +.vis.timeline .vispanel.background.horizontal .grid.horizontal { + position: absolute; + width: 100%; + height: 0; + border-bottom: 1px solid; +} + +.vis.timeline .vispanel.background.horizontal .grid.minor { + border-color: #e5e5e5; +} + +.vis.timeline .vispanel.background.horizontal .grid.major { + border-color: #bfbfbf; +} + + +.vis.timeline .dataaxis .yAxis.major { + width: 100%; + position: absolute; + color: #4d4d4d; + white-space: nowrap; +} + +.vis.timeline .dataaxis .yAxis.major.measure{ + padding: 0px 0px 0px 0px; + margin: 0px 0px 0px 0px; + visibility: hidden; + width: auto; +} + + +.vis.timeline .dataaxis .yAxis.minor{ + position: absolute; + width: 100%; + color: #bebebe; + white-space: nowrap; +} + +.vis.timeline .dataaxis .yAxis.minor.measure{ + padding: 0px 0px 0px 0px; + margin: 0px 0px 0px 0px; + visibility: hidden; + width: auto; +} + + +.vis.timeline .legend { + background-color: rgba(247, 252, 255, 0.65); + padding: 5px; + border-color: #b3b3b3; + border-style:solid; + border-width: 1px; + box-shadow: 2px 2px 10px rgba(154, 154, 154, 0.55); +} + +.vis.timeline .legendText { + /*font-size: 10px;*/ + white-space: nowrap; + display: inline-block +} +.vis.timeline .graphGroup0 { + fill:#4f81bd; + fill-opacity:0; + stroke-width:2px; + stroke: #4f81bd; +} + +.vis.timeline .graphGroup1 { + fill:#f79646; + fill-opacity:0; + stroke-width:2px; + stroke: #f79646; +} + +.vis.timeline .graphGroup2 { + fill: #8c51cf; + fill-opacity:0; + stroke-width:2px; + stroke: #8c51cf; +} + +.vis.timeline .graphGroup3 { + fill: #75c841; + fill-opacity:0; + stroke-width:2px; + stroke: #75c841; +} + +.vis.timeline .graphGroup4 { + fill: #ff0100; + fill-opacity:0; + stroke-width:2px; + stroke: #ff0100; +} + +.vis.timeline .graphGroup5 { + fill: #37d8e6; + fill-opacity:0; + stroke-width:2px; + stroke: #37d8e6; +} + +.vis.timeline .graphGroup6 { + fill: #042662; + fill-opacity:0; + stroke-width:2px; + stroke: #042662; +} + +.vis.timeline .graphGroup7 { + fill:#00ff26; + fill-opacity:0; + stroke-width:2px; + stroke: #00ff26; +} + +.vis.timeline .graphGroup8 { + fill:#ff00ff; + fill-opacity:0; + stroke-width:2px; + stroke: #ff00ff; +} + +.vis.timeline .graphGroup9 { + fill: #8f3938; + fill-opacity:0; + stroke-width:2px; + stroke: #8f3938; +} + +.vis.timeline .fill { + fill-opacity:0.1; + stroke: none; +} + + +.vis.timeline .bar { + fill-opacity:0.5; + stroke-width:1px; +} + +.vis.timeline .point { + stroke-width:2px; + fill-opacity:1.0; +} + + +.vis.timeline .legendBackground { + stroke-width:1px; + fill-opacity:0.9; + fill: #ffffff; + stroke: #c2c2c2; +} + + +.vis.timeline .outline { + stroke-width:1px; + fill-opacity:1; + fill: #ffffff; + stroke: #e5e5e5; +} + +.vis.timeline .iconFill { + fill-opacity:0.3; + stroke: none; +} + + + +div.network-manipulationDiv { + border-width: 0; + border-bottom: 1px; + border-style:solid; + border-color: #d6d9d8; + background: #ffffff; /* Old browsers */ + background: -moz-linear-gradient(top, #ffffff 0%, #fcfcfc 48%, #fafafa 50%, #fcfcfc 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(48%,#fcfcfc), color-stop(50%,#fafafa), color-stop(100%,#fcfcfc)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* IE10+ */ + background: linear-gradient(to bottom, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#fcfcfc',GradientType=0 ); /* IE6-9 */ + + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 30px; +} + +div.network-manipulation-editMode { + position:absolute; + left: 0; + top: 0; + height: 30px; + margin-top:20px; +} + +div.network-manipulation-closeDiv { + position:absolute; + right: 0; + top: 0; + width: 30px; + height: 30px; + + background-position: 20px 3px; + background-repeat: no-repeat; + background-image: url("img/network/cross.png"); + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +div.network-manipulation-closeDiv:hover { + opacity: 0.6; +} + +span.network-manipulationUI { + font-family: verdana; + font-size: 12px; + -moz-border-radius: 15px; + border-radius: 15px; + display:inline-block; + background-position: 0px 0px; + background-repeat:no-repeat; + height:24px; + margin: -14px 0px 0px 10px; + vertical-align:middle; + cursor: pointer; + padding: 0px 8px 0px 8px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +span.network-manipulationUI:hover { + box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.20); +} + +span.network-manipulationUI:active { + box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.50); +} + +span.network-manipulationUI.back { + background-image: url("img/network/backIcon.png"); +} + +span.network-manipulationUI.none:hover { + box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0); + cursor: default; +} +span.network-manipulationUI.none:active { + box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0); +} +span.network-manipulationUI.none { + padding: 0; +} +span.network-manipulationUI.notification{ + margin: 2px; + font-weight: bold; +} + +span.network-manipulationUI.add { + background-image: url("img/network/addNodeIcon.png"); +} + +span.network-manipulationUI.edit { + background-image: url("img/network/editIcon.png"); +} + +span.network-manipulationUI.edit.editmode { + background-color: #fcfcfc; + border-style:solid; + border-width:1px; + border-color: #cccccc; +} + +span.network-manipulationUI.connect { + background-image: url("img/network/connectIcon.png"); +} + +span.network-manipulationUI.delete { + background-image: url("img/network/deleteIcon.png"); +} +/* top right bottom left */ +span.network-manipulationLabel { + margin: 0px 0px 0px 23px; + line-height: 25px; +} +div.network-seperatorLine { + display:inline-block; + width:1px; + height:20px; + background-color: #bdbdbd; + margin: 5px 7px 0px 15px; +} + +div.network-navigation_wrapper { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; +} +div.network-navigation { + width:34px; + height:34px; + -moz-border-radius: 17px; + border-radius: 17px; + position:absolute; + display:inline-block; + background-position: 2px 2px; + background-repeat:no-repeat; + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +div.network-navigation:hover { + box-shadow: 0px 0px 3px 3px rgba(56, 207, 21, 0.30); +} + +div.network-navigation:active { + box-shadow: 0px 0px 1px 3px rgba(56, 207, 21, 0.95); +} + +div.network-navigation.up { + background-image: url("img/network/upArrow.png"); + bottom:50px; + left:55px; +} +div.network-navigation.down { + background-image: url("img/network/downArrow.png"); + bottom:10px; + left:55px; +} +div.network-navigation.left { + background-image: url("img/network/leftArrow.png"); + bottom:10px; + left:15px; +} +div.network-navigation.right { + background-image: url("img/network/rightArrow.png"); + bottom:10px; + left:95px; +} +div.network-navigation.zoomIn { + background-image: url("img/network/plus.png"); + bottom:10px; + right:15px; +} +div.network-navigation.zoomOut { + background-image: url("img/network/minus.png"); + bottom:10px; + right:55px; +} +div.network-navigation.zoomExtends { + background-image: url("img/network/zoomExtends.png"); + bottom:50px; + right:15px; +} \ No newline at end of file diff --git a/showcase/projects/midas/images/arum.png b/showcase/projects/midas/images/arum.png new file mode 100644 index 00000000..2f6a223a Binary files /dev/null and b/showcase/projects/midas/images/arum.png differ diff --git a/showcase/projects/midas/images/arum_small.png b/showcase/projects/midas/images/arum_small.png new file mode 100644 index 00000000..fa252bde Binary files /dev/null and b/showcase/projects/midas/images/arum_small.png differ diff --git a/showcase/projects/midas/images/control_pause.png b/showcase/projects/midas/images/control_pause.png new file mode 100644 index 00000000..2d9ce9c4 Binary files /dev/null and b/showcase/projects/midas/images/control_pause.png differ diff --git a/showcase/projects/midas/images/control_pause_blue.png b/showcase/projects/midas/images/control_pause_blue.png new file mode 100644 index 00000000..ec61099b Binary files /dev/null and b/showcase/projects/midas/images/control_pause_blue.png differ diff --git a/showcase/projects/midas/images/control_play.png b/showcase/projects/midas/images/control_play.png new file mode 100644 index 00000000..0846555d Binary files /dev/null and b/showcase/projects/midas/images/control_play.png differ diff --git a/showcase/projects/midas/images/control_play_blue.png b/showcase/projects/midas/images/control_play_blue.png new file mode 100644 index 00000000..f8c8ec68 Binary files /dev/null and b/showcase/projects/midas/images/control_play_blue.png differ diff --git a/showcase/projects/midas/images/midas_big.png b/showcase/projects/midas/images/midas_big.png new file mode 100644 index 00000000..73217ab4 Binary files /dev/null and b/showcase/projects/midas/images/midas_big.png differ diff --git a/showcase/projects/midas/images/midas_small.png b/showcase/projects/midas/images/midas_small.png new file mode 100644 index 00000000..23561af2 Binary files /dev/null and b/showcase/projects/midas/images/midas_small.png differ diff --git a/showcase/projects/midas/images/moon.png b/showcase/projects/midas/images/moon.png new file mode 100644 index 00000000..764c74ba Binary files /dev/null and b/showcase/projects/midas/images/moon.png differ diff --git a/showcase/projects/midas/images/shaded.png b/showcase/projects/midas/images/shaded.png new file mode 100644 index 00000000..cb55bd36 Binary files /dev/null and b/showcase/projects/midas/images/shaded.png differ diff --git a/showcase/projects/midas/images/sun.png b/showcase/projects/midas/images/sun.png new file mode 100644 index 00000000..4a9158e4 Binary files /dev/null and b/showcase/projects/midas/images/sun.png differ diff --git a/showcase/projects/midas/images/toggleOff.png b/showcase/projects/midas/images/toggleOff.png new file mode 100644 index 00000000..b1476509 Binary files /dev/null and b/showcase/projects/midas/images/toggleOff.png differ diff --git a/showcase/projects/midas/images/toggleOn.png b/showcase/projects/midas/images/toggleOn.png new file mode 100644 index 00000000..b8c5d9a5 Binary files /dev/null and b/showcase/projects/midas/images/toggleOn.png differ diff --git a/showcase/projects/midas/index.html b/showcase/projects/midas/index.html new file mode 100644 index 00000000..94a49212 --- /dev/null +++ b/showcase/projects/midas/index.html @@ -0,0 +1,86 @@ + + + + ARUM - MIDAS + + + + + + + + + + + + + + + + + + + + + + + +
+
Show by Timeline
+
Show by Job
+
+ + eventNo:
-1
+ + +
+
+
+ +
+ + + + + + + + + + + + + + +
+
Prediction
+
  +
Ideal
+
Ideal + Pause
+
Ideal + Prerequisites
+
Total Time
+
+ + +
+
+
+
+
Switch View
+
+
+
+
+ +

+

+ + diff --git a/showcase/projects/midas/js/evejs.js b/showcase/projects/midas/js/evejs.js new file mode 100644 index 00000000..1a53cc7b --- /dev/null +++ b/showcase/projects/midas/js/evejs.js @@ -0,0 +1,31161 @@ +!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.eve=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o} Returns a promise resolving with the response message + */ +RequestModule.prototype.request = function (to, message) { + var me = this; + return new Promise(function (resolve, reject) { + // put the data in an envelope with id + var id = uuid(); + var envelope = { + type: 'request', + id: id, + message: message + }; + + // add the request to the list with requests in progress + me.queue[id] = { + resolve: resolve, + reject: reject, + timeout: setTimeout(function () { + delete me.queue[id]; + reject(new Error('Timeout')); + }, me.timeout) + }; + + me.agent.send(to, envelope) + .catch(function (err) { + reject(err); + }); + }); +}; + +/** + * Get a map with mixin functions + * @return {{_receive: function, request: function}} + * Returns mixin function, which can be used to extend the agent. + */ +RequestModule.prototype.mixin = function () { + return { + _receive: this.receive.bind(this), + request: this.request.bind(this) + } +}; + +module.exports = RequestModule; + +},{"../util":23,"promise":114,"uuid-v4":125}],9:[function(_dereq_,module,exports){ +'use strict'; + +var Promise = _dereq_('promise'); + +/** + * An abstract Transport connection + * @param {Transport} transport + * @param {string} id + * @param {function} receive + * @constructor + * @abstract + */ +function Connection (transport, id, receive) { + throw new Error('Cannot create an abstract Connection'); +} + +Connection.prototype.ready = Promise.reject(new Error('Cannot get abstract property ready')); + +/** + * Send a message to an agent. + * @param {string} to + * @param {*} message + * @return {Promise} returns a promise which resolves when the message has been sent + */ +Connection.prototype.send = function (to, message) { + throw new Error('Cannot call abstract function send'); +}; + +/** + * Close the connection, disconnect from the transport. + */ +Connection.prototype.close = function () { + throw new Error('Cannot call abstract function "close"'); +}; + +module.exports = Connection; + +},{"promise":114}],10:[function(_dereq_,module,exports){ +'use strict'; + +/** + * Abstract prototype of a transport + * @param {Object} [config] + * @constructor + */ +function Transport(config) { + this.id = config && config.id || null; + this['default'] = config && config['default'] || false; +} + +Transport.prototype.type = null; + +/** + * Connect an agent + * @param {String} id + * @param {Function} receive Invoked as receive(from, message) + * @return {Connection} Returns a connection + */ +Transport.prototype.connect = function(id, receive) { + throw new Error('Cannot invoke abstract function "connect"'); +}; + +/** + * Close the transport + */ +Transport.prototype.close = function() { + throw new Error('Cannot invoke abstract function "close"'); +}; + +module.exports = Transport; + +},{}],11:[function(_dereq_,module,exports){ +'use strict'; + +var Promise = _dereq_('promise'); +var Connection = _dereq_('../Connection'); + +/** + * A local connection. + * @param {AMQPTransport} transport + * @param {string | number} id + * @param {function} receive + * @constructor + */ +function AMQPConnection(transport, id, receive) { + this.transport = transport; + this.id = id; + + // ready state + this.ready = this.transport._connect(id, receive); +} + +/** + * Send a message to an agent. + * @param {string} to + * @param {*} message + * @return {Promise} returns a promise which resolves when the message has been sent + */ +AMQPConnection.prototype.send = function (to, message) { + var me = this; + return new Promise(function (resolve, reject) { + var msg = { + body: { + from: me.id, + to: to, + message: message + } + }; + var options = { + //immediate: true + }; + + me.transport.exchange.publish(to, msg, options, function () { + // FIXME: callback is not called. + //console.log('sent', arguments) + }); + + resolve(); + }); +}; + +/** + * Close the connection + */ +AMQPConnection.prototype.close = function () { + this.transport._close(this.id); +}; + +module.exports = AMQPConnection; + +},{"../Connection":9,"promise":114}],12:[function(_dereq_,module,exports){ +'use strict'; + +var Promise = _dereq_('promise'); +var Transport = _dereq_('./../Transport'); +var AMQPConnection = _dereq_('./AMQPConnection'); + +/** + * Use AMQP as transport + * @param {Object} config Config can contain the following properties: + * - `id: string` + * - `url: string` + * - `host: string` + * The config must contain either `url` or `host`. + * For example: {url: 'amqp://localhost'} or + * {host: 'dev.rabbitmq.com'} + * @constructor + */ +function AMQPTransport(config) { + this.id = config.id || null; + this.networkId = config.url || config.host || null; + this['default'] = config['default'] || false; + + this.config = config; + this.connection = null; + this.exchange = null; + + this.subscriptions = []; +} + +AMQPTransport.prototype = new Transport(); + +AMQPTransport.prototype.type = 'amqp'; + +/** + * Connect an agent + * @param {String} id + * @param {Function} receive Invoked as receive(from, message) + * @return {AMQPConnection} Returns a connection. + */ +AMQPTransport.prototype.connect = function(id, receive) { + return new AMQPConnection(this, id, receive); +}; + +/** + * Get an AMQP connection. If there is not yet a connection, a connection will + * be made. + * @param {Function} callback Invoked as callback(connection) + * @private + */ +AMQPTransport.prototype._getConnection = function(callback) { + var me = this; + + if (this.connection) { + // connection is available + callback(this.connection); + } + else { + if (this._onConnected) { + // connection is being opened but not yet ready + this._onConnected.push(callback); + } + else { + // no connection, create one + this._onConnected = [callback]; + + var amqp = _dereq_('amqp'); // lazy load the amqp library + var connection = amqp.createConnection(this.config); + connection.on('ready', function () { + var exchange = connection.exchange('', {confirm: true}, function () { + var _onConnected = me._onConnected; + delete me._onConnected; + + me.connection = connection; + me.exchange = exchange; + + _onConnected.forEach(function (callback) { + callback(me.connection); + }); + }); + }); + } + } +}; + +/** + * Open a connection + * @param {string} id + * @param {Function} receive Invoked as receive(from, message) + */ +AMQPTransport.prototype._connect = function(id, receive) { + var me = this; + + return new Promise(function (resolve, reject) { + function subscribe(connection) { + var queue = connection.queue(id, {}, function() { + queue + .subscribe(function(message) { + var body = message.body; + receive(body.from, body.message); + }) + .addCallback(function (ok) { + // register this subscription + me.subscriptions.push({ + id: id, + consumerTag: ok.consumerTag + }); + + resolve(me); + }); + }); + } + + me._getConnection(subscribe); + }); +}; + +/** + * Close a connection an agent by its id + * @param {String} id + */ +AMQPTransport.prototype._close = function(id) { + var i = 0; + while (i < this.subscriptions.length) { + var subscription = this.subscriptions[i]; + if (subscription.id == id) { + // remove this entry + this.subscriptions.splice(i, 1); + } + else { + i++; + } + } + + if (this.subscriptions.length == 0) { + // fully disconnect if there are no subscribers left + this.exchange.destroy(); + this.connection.disconnect(); + + this.connection = null; + this.exchange = null; + } +}; + +/** + * Close the transport. + */ +AMQPTransport.prototype.close = function() { + this.connection.destroy(); + this.connection = null; +}; + +module.exports = AMQPTransport; + +},{"./../Transport":10,"./AMQPConnection":11,"amqp":24,"promise":114}],13:[function(_dereq_,module,exports){ +'use strict'; + +var Promise = _dereq_('promise'); +var Connection = _dereq_('../Connection'); + +/** + * A local connection. + * @param {DistribusTransport} transport + * @param {string | number} id + * @param {function} receive + * @constructor + */ +function DistribusConnection(transport, id, receive) { + this.transport = transport; + this.id = id; + + // create a peer + var peer = this.transport.host.create(id); + peer.on('message', receive); + + // ready state + this.ready = Promise.resolve(this); +} + +/** + * Send a message to an agent. + * @param {string} to + * @param {*} message + * @return {Promise} returns a promise which resolves when the message has been sent + */ +DistribusConnection.prototype.send = function (to, message) { + return this.transport.host.send(this.id, to, message); +}; + +/** + * Close the connection + */ +DistribusConnection.prototype.close = function () { + this.transport.host.remove(this.id); +}; + +module.exports = DistribusConnection; + +},{"../Connection":9,"promise":114}],14:[function(_dereq_,module,exports){ +'use strict'; + +var distribus = _dereq_('distribus'); +var Transport = _dereq_('./../Transport'); +var DistribusConnection = _dereq_('./DistribusConnection'); + +/** + * Use distribus as transport + * @param {Object} config Config can contain the following properties: + * - `id: string`. Optional + * - `host: distribus.Host`. Optional + * If `host` is not provided, + * a new local distribus Host is created. + * @constructor + */ +function DistribusTransport(config) { + this.id = config && config.id || null; + this['default'] = config && config['default'] || false; + this.host = config && config.host || new distribus.Host(config); + + this.networkId = this.host.networkId; // FIXME: networkId can change when host connects to another host. +} + +DistribusTransport.prototype = new Transport(); + +DistribusTransport.prototype.type = 'distribus'; + +/** + * Connect an agent + * @param {String} id + * @param {Function} receive Invoked as receive(from, message) + * @return {DistribusConnection} Returns a connection. + */ +DistribusTransport.prototype.connect = function(id, receive) { + return new DistribusConnection(this, id, receive); +}; + +/** + * Close the transport. + */ +DistribusTransport.prototype.close = function() { + this.host.close(); + this.host = null; +}; + +module.exports = DistribusTransport; + +},{"./../Transport":10,"./DistribusConnection":13,"distribus":67}],15:[function(_dereq_,module,exports){ +'use strict'; + +var Promise = _dereq_('promise'); +var Connection = _dereq_('../Connection'); + +/** + * A HTTP connection. + * @param {HTTPTransport} transport + * @param {string | number} id + * @param {function} receive + * @constructor + */ +function HTTPConnection(transport, id, receive) { + this.transport = transport; + this.id = id; + + // register the agents receive function + if (this.id in this.transport.agents) { + throw new Error('Agent with id ' + id + ' already exists'); + } + this.transport.agents[this.id] = receive; + + // ready state + this.ready = Promise.resolve(this); +} + +/** + * Send a message to an agent. + * @param {string} to + * @param {*} message + */ +HTTPConnection.prototype.send = function (to, message) { + var fromURL = this.transport.url.replace(':id', this.id); + + var isURL = to.indexOf('://') !== -1; + var toURL; + if (isURL) { + toURL = to; + } + else { + if (this.transport.remoteUrl !== undefined) { + toURL = this.transport.remoteUrl.replace(':id', to); + } + else { + console.log('ERROR: no remote URL specified. Cannot send over HTTP.', to); + } + } + + return this.transport.send(fromURL, toURL, message); +}; + +/** + * Close the connection + */ +HTTPConnection.prototype.close = function () { + delete this.transport.agents[this.id]; +}; + +module.exports = HTTPConnection; + +},{"../Connection":9,"promise":114}],16:[function(_dereq_,module,exports){ +'use strict'; + +var http = _dereq_('http'); +var Promise = _dereq_('promise'); +var Transport = _dereq_('./../Transport'); +var HTTPConnection = _dereq_('./HTTPConnection'); +var uuid = _dereq_('uuid-v4'); + +/** + * HTTP Transport layer: + * + * Supported Options: + * + * {Number} config.port Port to listen on. + * {String} config.path Path, with or without leading and trailing slash (/) + * {Boolean} config.localShortcut If the agentId exists locally, use local transport. (local) + * + * Address: http://127.0.0.1:PORTNUMBER/PATH + */ +function HTTPTransport(config) { + this.id = config && config.id || null; + this.networkId = null; + + this.agents = {}; + this.outstandingRequests = {}; // these are received messages that are expecting a response + this.outstandingMessages = {}; + + this.url = config && config.url || "http://127.0.0.1:3000/agents/:id"; + this.remoteUrl = config && config.remoteUrl; + this.localShortcut = (config && config.localShortcut === false) ? false : true; + + this.httpTimeout = config && config.httpTimeout || 2000; // 1 second - timeout to send message + this.httpResponseTimeout = config && config.httpResponseTimeout || 200; // 0.5 second - timeout to expect reply after delivering request + this.regexHosts = /[http]{4}s?:\/\/([a-z\-\.A-Z0-9]*):?([0-9]*)(\/[a-z\/:A-Z0-9._\-% \\\(\)\*\+\.\^\$]*)/; + this.urlHostData = this.regexHosts.exec(this.url); + + this.regexPath = this.getRegEx(this.urlHostData[3]); + this.port = config && config.port || this.urlHostData[2] || 3000; + this.path = this.urlHostData[3].replace(':id', ''); +} + +HTTPTransport.prototype = new Transport(); +HTTPTransport.prototype.type = 'http'; + +HTTPTransport.prototype.getRegEx = function(url) { + return new RegExp(url.replace(/[\\\(\)\*\+\.\^\$]/g,function(match) {return '\\' + match;}).replace(':id','([:a-zA-Z_0-9]*)')); +}; + + +function askAgent(url,method,params,callback, async) { + if (async === undefined) { + async = true; + } + // create post request + var POSTrequest = JSON.stringify({"id":0, "method": method, "params": params}); + + // create XMLHttpRequest object to send the POST request + var http = new XMLHttpRequest(); + + // insert the callback function. This is called when the message has been delivered and a response has been received + http.onreadystatechange = function () { + if (http.readyState == 4 && http.status == 200) { + if (callback === undefined || callback === null) { + } + else { + // launch callback function + callback(JSON.parse(http.responseText)); + } + } + else if (http.readyState == 4 && http.status != 200) { + console.log("Make sure that the Node server has started."); + } + }; + + // open an asynchronous POST connection + http.open("POST", url, async); + // include header so the receiving code knows its a JSON object + http.setRequestHeader("Content-type", "application/json"); + // send + http.send(POSTrequest); +} + + +/** + * Connect an agent + * @param {String} id + * @param {Function} receive Invoked as receive(from, message) + * @return {HTTPConnection} Returns a connection. + */ +HTTPTransport.prototype.connect = function(id, receive) { + if (this.server === undefined) { + this.initiateServer(); + } + this.outstandingRequests[id] = {}; + this.outstandingMessages[id] = {}; + return new HTTPConnection(this, id, receive); +}; + +/** + * Send a message to an agent + * @param {String} from Id of sender + * @param {String} to Id of addressed peer + * @param {String} message + */ +HTTPTransport.prototype.send = function(from, to, message) { + var me = this; + return new Promise(function (resolve,reject) { + var hostData = me.regexHosts.exec(to); + var fromRegexpCheck = me.regexPath.exec(from); + var fromAgentId = fromRegexpCheck[1]; + var outstandingMessageID = uuid(); + + // check for local shortcut possibility + if (me.localShortcut == true) { + var toRegexpCheck = me.regexPath.exec(to); + var toAgentId = toRegexpCheck[1]; + var toPath = hostData[3].replace(toAgentId,""); + + // check if the "to" address is on the same URL, port and path as the "from" + if ((hostData[1] == '127.0.0.1' && hostData[2] == me.urlHostData[2] && toPath == me.path) || + (me.urlHostData[1] == hostData[1] && hostData[2] == me.urlHostData[2] && toPath == me.path)) { + // by definition true but check anyway + if (me.agents[toAgentId] !== undefined) { + me.agents[toAgentId](fromAgentId, message); + resolve(); + return; + } + } + } + + // stringify the message. If the message is an object, it can have an ID so it may be part of a req/rep. + if (typeof message == 'object') { + + // check if the send is a reply to an outstanding request and if so, deliver + var outstanding = me.outstandingRequests[fromAgentId]; + if (outstanding[message.id] !== undefined) { + var callback = outstanding[message.id]; + callback.response.end(JSON.stringify(message)); + clearTimeout(callback.timeout); + delete outstanding[message.id]; + resolve(); + return; + } + // stringify the message. + message = JSON.stringify(message) + } + + // all post options + var options = { + host: hostData[1], + port: hostData[2], + path: hostData[3], + method: 'POST', + headers: { + 'x-eve-senderurl' : from, // used to get senderID + 'Content-type' : 'text/plain' + } + }; + var request = http.request(options, function(res) { + res.setEncoding('utf8'); + // message was delivered, clear the cannot deliver timeout. + clearTimeout(me.outstandingMessages[fromAgentId][outstandingMessageID].timeout); + // listen to incoming data + res.on('data', function (response) { + var parsedResponse; + try {parsedResponse = JSON.parse(response);} catch (err) {parsedResponse = response;} + if (typeof parsedResponse == 'object') { + if (parsedResponse.__httpError__ !== undefined) { + reject(new Error(parsedResponse.__httpError__)); + return; + } + } + me.agents[fromAgentId](to, parsedResponse); + resolve(); + }); + }); + + me.outstandingMessages[fromAgentId][outstandingMessageID] = { + timeout: setTimeout(function () { + request.abort(); + reject(new Error("Cannot connect to " + to)) + }, me.httpTimeout), + reject: reject + }; + + request.on('error', function(e) { + reject(e); + }); + + // write data to request body + request.write(message); + request.end(); + }); +}; + + +/** + * This is the HTTP equivalent of receiveMessage. + * + * @param request + * @param response + */ +HTTPTransport.prototype.processRequest = function(request, response) { + var url = request.url; + + // define headers + var headers = {}; + headers['Access-Control-Allow-Origin'] = '*'; + headers['Access-Control-Allow-Credentials'] = true; + headers['Content-Type'] = 'text/plain'; + + var regexpCheck = this.regexPath.exec(url); + if (regexpCheck !== null) { + var agentId = regexpCheck[1]; + var senderId = 'unknown'; + if (request.headers['x-eve-senderurl'] !== undefined) { + senderId = request.headers['x-eve-senderurl']; + } + var body = ''; + request.on('data', function (data) { + body += data; + if (body.length > 1e6) { // 1e6 == 1MB + request.connection.destroy(); // FLOOD ATTACK OR FAULTY CLIENT, NUKE REQUEST + } + }); + + + var me = this; + request.on('end', function () { + var expectReply = false; + var message; + try {message = JSON.parse(body);} catch (err) {message = body;} + + // check if JSON RPC + expectReply = message.jsonrpc && message.jsonrpc == '2.0' || expectReply; + // check if type == 'request' + expectReply = message.type && message.type == 'request' || expectReply; + + response.writeHead(200, headers); + // construct callback + var callback = me.agents[agentId]; + if (callback === undefined) { + var error = new Error('Agent: "' + agentId + '" does not exist.'); + response.end(JSON.stringify({__httpError__:error.message || error.toString()})); + } + else { + if (expectReply == true) { + me.outstandingRequests[agentId][message.id] = { + response: response, + timeout: setTimeout(function () { + response.end("timeout"); + delete me.outstandingRequests[agentId][message.id]; + }, me.httpResponseTimeout) + }; + callback(senderId, message); + } + else { + // if we're not expecting a response, we first close the connection, then receive the message + response.end(''); + if (callback !== undefined) { + callback(senderId, message); + } + } + } + }); + } +}; + +/** + * Configure a HTTP server listener + */ +HTTPTransport.prototype.initiateServer = function() { + if (this.server === undefined) { + var me = this; + this.server = http.createServer(function (request, response) { + if (request.method == 'OPTIONS') { + var headers = {}; + headers['Access-Control-Allow-Origin'] = '*'; + headers['Access-Control-Allow-Methods'] = 'POST, OPTIONS'; + headers['Access-Control-Allow-Credentials'] = true; + headers['Access-Control-Max-Age'] = '86400'; // 24 hours + headers['Access-Control-Allow-Headers'] = 'X-Requested-With, Access-Control-Allow-Origin, X-HTTP-Method-Override, Content-Type, Authorization, Accept'; + // respond to the request + response.writeHead(200, headers); + response.end(); + } + else if (request.method == 'POST') { + me.processRequest(request, response); + } + }); + + this.server.on('error', function(err) { + if (err.code == 'EADDRINUSE') { + throw new Error('ERROR: Could not start HTTP server. Port ' + me.port + ' is occupied.'); + } + else { + throw new Error(err); + } + }); + + // Listen on port (default: 3000), IP defaults to 127.0.0.1 + this.server.listen(this.port, function() { + // Put a friendly message on the terminal + console.log('Server listening at ', me.url); + }); + + + } + else { + this.server.close(); + this.server = undefined; + this.initiateServer(); + } +}; + + +/** + * Close the HTTP server + */ +HTTPTransport.prototype.close = function() { + // close all open connections + for (var agentId in this.outstandingRequests) { + if (this.outstandingRequests.hasOwnProperty(agentId)) { + var agentRequests = this.outstandingRequests[agentId]; + for (var messageId in agentRequests) { + if (agentRequests.hasOwnProperty(messageId)) { + var openMessage = agentRequests[messageId]; + var error = new Error('Server shutting down.'); + openMessage.response.end(JSON.stringify({__httpError__:error.message || error.toString()})); + } + } + } + } + // close server + if (this.server) { + this.server.close(); + } + this.server = null; +}; + + +module.exports = HTTPTransport; + + +},{"./../Transport":10,"./HTTPConnection":15,"http":138,"promise":114,"uuid-v4":125}],17:[function(_dereq_,module,exports){ +'use strict'; + +var Promise = _dereq_('promise'); +var Connection = _dereq_('../Connection'); + +/** + * A local connection. + * @param {LocalTransport} transport + * @param {string | number} id + * @param {function} receive + * @constructor + */ +function LocalConnection(transport, id, receive) { + this.transport = transport; + this.id = id; + + // register the agents receive function + if (this.id in this.transport.agents) { + throw new Error('Agent with id ' + id + ' already exists'); + } + this.transport.agents[this.id] = receive; + + // ready state + this.ready = Promise.resolve(this); +} + +/** + * Send a message to an agent. + * @param {string} to + * @param {*} message + * @return {Promise} returns a promise which resolves when the message has been sent + */ +LocalConnection.prototype.send = function (to, message) { + var callback = this.transport.agents[to]; + if (!callback) { + throw new Error('Agent with id ' + to + ' not found'); + } + + // invoke the agents receiver as callback(from, message) + callback(this.id, message); + + return Promise.resolve(); +}; + +/** + * Close the connection + */ +LocalConnection.prototype.close = function () { + delete this.transport.agents[this.id]; +}; + +module.exports = LocalConnection; + +},{"../Connection":9,"promise":114}],18:[function(_dereq_,module,exports){ +'use strict'; + +var Transport = _dereq_('./../Transport'); +var LocalConnection = _dereq_('./LocalConnection'); + +/** + * Create a local transport. + * @param {Object} config Config can contain the following properties: + * - `id: string`. Optional + * @constructor + */ +function LocalTransport(config) { + this.id = config && config.id || null; + this.networkId = this.id || null; + this['default'] = config && config['default'] || false; + this.agents = {}; +} + +LocalTransport.prototype = new Transport(); + +LocalTransport.prototype.type = 'local'; + +/** + * Connect an agent + * @param {String} id + * @param {Function} receive Invoked as receive(from, message) + * @return {LocalConnection} Returns a promise which resolves when + * connected. + */ +LocalTransport.prototype.connect = function(id, receive) { + return new LocalConnection(this, id, receive); +}; + +/** + * Close the transport. Removes all agent connections. + */ +LocalTransport.prototype.close = function() { + this.agents = {}; +}; + +module.exports = LocalTransport; + +},{"./../Transport":10,"./LocalConnection":17}],19:[function(_dereq_,module,exports){ +'use strict'; + +var Promise = _dereq_('promise'); +var Connection = _dereq_('../Connection'); + +/** + * A connection. The connection is ready when the property .ready resolves. + * @param {PubNubTransport} transport + * @param {string | number} id + * @param {function} receive + * @constructor + */ +function PubNubConnection(transport, id, receive) { + this.id = id; + this.transport = transport; + + // ready state + var me = this; + this.ready = new Promise(function (resolve, reject) { + transport.pubnub.subscribe({ + channel: id, + message: function (message) { + receive(message.from, message.message); + }, + connect: function () { + resolve(me); + } + }); + }); +} + +/** + * Send a message to an agent. + * @param {string} to + * @param {*} message + * @return {Promise} returns a promise which resolves when the message has been sent + */ +PubNubConnection.prototype.send = function (to, message) { + var me = this; + return new Promise(function (resolve, reject) { + me.transport.pubnub.publish({ + channel: to, + message: { + from: me.id, + to: to, + message: message + }, + callback: resolve, + error: reject + }); + }); +}; + +/** + * Close the connection + */ +PubNubConnection.prototype.close = function () { + this.transport.pubnub.unsubscribe({ + channel: this.id + }); +}; + +module.exports = PubNubConnection; + +},{"../Connection":9,"promise":114}],20:[function(_dereq_,module,exports){ +'use strict'; + +var Transport = _dereq_('./../Transport'); +var PubNubConnection = _dereq_('./PubNubConnection'); + +/** + * Use pubnub as transport + * @param {Object} config Config can contain the following properties: + * - `id: string`. Optional + * - `publish_key: string`. Required + * - `subscribe_key: string`. Required + * @constructor + */ +function PubNubTransport(config) { + this.id = config.id || null; + this.networkId = config.publish_key || null; + this['default'] = config['default'] || false; + this.pubnub = PUBNUB().init(config); +} + +PubNubTransport.prototype = new Transport(); + +PubNubTransport.prototype.type = 'pubnub'; + +/** + * Connect an agent + * @param {String} id + * @param {Function} receive Invoked as receive(from, message) + * @return {PubNubConnection} Returns a connection + */ +PubNubTransport.prototype.connect = function(id, receive) { + return new PubNubConnection(this, id, receive) +}; + +/** + * Close the transport. + */ +PubNubTransport.prototype.close = function() { + // FIXME: how to correctly close a pubnub connection? + this.pubnub = null; +}; + +/** + * Load the PubNub library + * @returns {Object} PUBNUB + */ +function PUBNUB() { + if (typeof window !== 'undefined') { + // browser + if (typeof window['PUBNUB'] === 'undefined') { + throw new Error('Please load pubnub first in the browser'); + } + return window['PUBNUB']; + } + else { + // node.js + return _dereq_('pubnub'); + } +} + +module.exports = PubNubTransport; + +},{"./../Transport":10,"./PubNubConnection":19,"pubnub":123}],21:[function(_dereq_,module,exports){ +'use strict'; + +var uuid = _dereq_('uuid-v4'); +var Promise = _dereq_('promise'); +var WebSocket = (typeof window !== 'undefined' && typeof window.WebSocket !== 'undefined') ? + window.WebSocket : + _dereq_('ws'); + +var util = _dereq_('../../util'); +var Connection = _dereq_('../Connection'); + +/** + * A websocket connection. + * @param {WebSocketTransport} transport + * @param {string | number | null} url The url of the agent. The url must match + * the url of the WebSocket server. + * If url is null, a UUID id is generated as url. + * @param {function} receive + * @constructor + */ +function WebSocketConnection(transport, url, receive) { + this.transport = transport; + this.url = url ? util.normalizeURL(url) : uuid(); + this.receive = receive; + + this.sockets = {}; + this.closed = false; + this.reconnectTimers = {}; + + // ready state + this.ready = Promise.resolve(this); +} + +/** + * Send a message to an agent. + * @param {string} to The WebSocket url of the receiver + * @param {*} message + * @return {Promise} Returns a promise which resolves when the message is sent, + * and rejects when sending the message failed + */ +WebSocketConnection.prototype.send = function (to, message) { + //console.log('send', this.url, to, message); // TODO: cleanup + + // deliver locally when possible + if (this.transport.localShortcut) { + var agent = this.transport.agents[to]; + if (agent) { + try { + agent.receive(this.url, message); + return Promise.resolve(); + } + catch (err) { + return Promise.reject(err); + } + } + } + + // get or create a connection + var conn = this.sockets[to]; + if (conn) { + try { + if (conn.readyState == conn.CONNECTING) { + // the connection is still opening + return new Promise(function (resolve, reject) { + conn.onopen.callback.push(function () { + conn.send(JSON.stringify(message)); + resolve(); + }) + }); + } + else if (conn.readyState == conn.OPEN) { + conn.send(JSON.stringify(message)); + return Promise.resolve(); + } + else { + // remove the connection + conn = null; + } + } + catch (err) { + return Promise.reject(err); + } + } + + if (!conn) { + // try to open a connection + var me = this; + return new Promise(function (resolve, reject) { + me._connect(to, function (conn) { + conn.send(JSON.stringify(message)); + resolve(); + }, function (err) { + reject(new Error('Failed to connect to agent "' + to + '"')); + }); + }) + } +}; + +/** + * Open a websocket connection to an other agent. No messages are sent. + * @param {string} to Url of the remote agent. + * @returns {Promise.} + * Returns a promise which resolves when the connection is + * established and rejects in case of an error. + */ +WebSocketConnection.prototype.connect = function (to) { + var me = this; + return new Promise(function (resolve, reject) { + me._connect(to, function () { + resolve(me); + }, reject); + }); +}; + +/** + * Open a websocket connection + * @param {String} to Url of the remote agent + * @param {function} [callback] + * @param {function} [errback] + * @param {boolean} [doReconnect=false] + * @returns {WebSocket} + * @private + */ +WebSocketConnection.prototype._connect = function (to, callback, errback, doReconnect) { + var me = this; + var conn = new WebSocket(to + '?id=' + this.url); + + // register the new socket + me.sockets[to] = conn; + + conn.onopen = function () { + // Change doReconnect to true as soon as we have had an open connection + doReconnect = true; + + conn.onopen.callbacks.forEach(function (cb) { + cb(conn); + }); + conn.onopen.callbacks = []; + }; + conn.onopen.callbacks = callback ? [callback] : []; + + conn.onmessage = function (event) { + me.receive(to, JSON.parse(event.data)); + }; + + conn.onclose = function () { + delete me.sockets[to]; + if (doReconnect) { + me._reconnect(to); + } + }; + + conn.onerror = function (err) { + delete me.sockets[to]; + if (errback) { + errback(err); + } + }; + + return conn; +}; + +/** + * Auto reconnect a broken connection + * @param {String} to Url of the remote agent + * @private + */ +WebSocketConnection.prototype._reconnect = function (to) { + var me = this; + var doReconnect = true; + if (me.closed == false && me.reconnectTimers[to] == null) { + me.reconnectTimers[to] = setTimeout(function () { + delete me.reconnectTimers[to]; + me._connect(to, null, null, doReconnect); + }, me.transport.reconnectDelay); + } +}; + +/** + * Register a websocket connection + * @param {String} from Url of the remote agent + * @param {WebSocket} conn WebSocket connection + * @returns {WebSocket} Returns the websocket itself + * @private + */ +WebSocketConnection.prototype._onConnection = function (from, conn) { + var me = this; + + conn.onmessage = function (event) { + me.receive(from, JSON.parse(event.data)); + }; + + conn.onclose = function () { + // remove this connection from the sockets list + delete me.sockets[from]; + }; + + conn.onerror = function (err) { + // TODO: what to do with errors? + delete me.sockets[from]; + }; + + if (this.sockets[from]) { + // there is already a connection open with remote agent + // TODO: what to do with overwriting existing sockets? + this.sockets[from].close(); + } + + // register new connection + this.sockets[from] = conn; + + return conn; +}; + +/** + * Get a list with all open sockets + * @return {String[]} Returns all open sockets + */ +WebSocketConnection.prototype.list = function () { + return Object.keys(this.sockets); +}; + +/** + * Close the connection. All open sockets will be closed and the agent will + * be unregistered from the WebSocketTransport. + */ +WebSocketConnection.prototype.close = function () { + this.closed = true; + + // close all connections + for (var id in this.sockets) { + if (this.sockets.hasOwnProperty(id)) { + this.sockets[id].close(); + } + } + this.sockets = {}; + + delete this.transport.agents[this.url]; +}; + +module.exports = WebSocketConnection; + +},{"../../util":23,"../Connection":9,"promise":114,"uuid-v4":125,"ws":126}],22:[function(_dereq_,module,exports){ +'use strict'; + +var urlModule = _dereq_('url'); +var uuid = _dereq_('uuid-v4'); +var Promise = _dereq_('promise'); +var WebSocketServer = _dereq_('ws').Server; + +var util = _dereq_('../../util'); +var Transport = _dereq_('../Transport'); +var WebSocketConnection = _dereq_('./WebSocketConnection'); + +/** + * Create a web socket transport. + * @param {Object} config Config can contain the following properties: + * - `id: string`. Optional + * - `default: boolean`. Optional + * - `url: string`. Optional. If provided, + * A WebSocket server is started on given + * url. + * - `localShortcut: boolean`. Optional. If true + * (default), messages to local agents are not + * send via WebSocket but delivered immediately + * - `reconnectDelay: number` Optional. Delay in + * milliseconds for reconnecting a broken + * connection. 10000 ms by default. Connections + * are only automatically reconnected after + * there has been an established connection. + * @constructor + */ +function WebSocketTransport(config) { + this.id = config && config.id || null; + this.networkId = this.id || null; + this['default'] = config && config['default'] || false; + this.localShortcut = (config && config.localShortcut === false) ? false : true; + this.reconnectDelay = config && config.reconnectDelay || 10000; + + this.url = config && config.url || null; + this.server = null; + + if (this.url != null) { + var urlParts = urlModule.parse(this.url); + + if (urlParts.protocol != 'ws:') throw new Error('Invalid protocol, "ws:" expected'); + if (this.url.indexOf(':id') == -1) throw new Error('":id" placeholder missing in url'); + + this.address = urlParts.protocol + '//' + urlParts.host; // the url without path, for example 'ws://localhost:3000' + this.ready = this._initServer(this.url); + } + else { + this.address = null; + this.ready = Promise.resolve(this); + } + + this.agents = {}; // WebSocketConnections of all registered agents. The keys are the urls of the agents +} + +WebSocketTransport.prototype = new Transport(); + +WebSocketTransport.prototype.type = 'ws'; + +/** + * Build an url for given id. Example: + * var url = getUrl('agent1'); // 'ws://localhost:3000/agents/agent1' + * @param {String} id + * @return {String} Returns the url, or returns null when no url placeholder + * is defined. + */ +WebSocketTransport.prototype.getUrl = function (id) { + return this.url ? this.url.replace(':id', id) : null; +}; + +/** + * Initialize a server on given url + * @param {String} url For example 'http://localhost:3000' + * @return {Promise} Returns a promise which resolves when the server is up + * and running + * @private + */ +WebSocketTransport.prototype._initServer = function (url) { + var urlParts = urlModule.parse(url); + var port = urlParts.port || 80; + + var me = this; + return new Promise(function (resolve, reject) { + me.server = new WebSocketServer({port: port}, function () { + resolve(me); + }); + + me.server.on('connection', me._onConnection.bind(me)); + + me.server.on('error', function (err) { + reject(err) + }); + }) +}; + +/** + * Handle a new connection. The connection is added to the addressed agent. + * @param {WebSocket} conn + * @private + */ +WebSocketTransport.prototype._onConnection = function (conn) { + var url = conn.upgradeReq.url; + var urlParts = urlModule.parse(url, true); + var toPath = urlParts.pathname; + var to = util.normalizeURL(this.address + toPath); + + // read sender id from query parameters or generate a random uuid + var queryParams = urlParts.query; + var from = queryParams.id || uuid(); + // TODO: make a config option to allow/disallow anonymous connections? + //console.log('onConnection, to=', to, ', from=', from, ', agents:', Object.keys(this.agents)); // TODO: cleanup + + var agent = this.agents[to]; + if (agent) { + agent._onConnection(from, conn); + } + else { + // reject the connection + // conn.send('Error: Agent with id "' + to + '" not found'); // TODO: can we send back a message before closing? + conn.close(); + } +}; + +/** + * Connect an agent + * @param {string} id The id or url of the agent. In case of an + * url, this url should match the url of the + * WebSocket server. + * @param {Function} receive Invoked as receive(from, message) + * @return {WebSocketConnection} Returns a promise which resolves when + * connected. + */ +WebSocketTransport.prototype.connect = function(id, receive) { + var isURL = (id.indexOf('://') !== -1); + + // FIXME: it's confusing right now what the final url will be based on the provided id... + var url = isURL ? id : (this.getUrl(id) || id); + if (url) url = util.normalizeURL(url); + + // register the agents receive function + if (this.agents[url]) { + throw new Error('Agent with id ' + this.id + ' already exists'); + } + + var conn = new WebSocketConnection(this, url, receive); + this.agents[conn.url] = conn; // use conn.url, url can be changed when it was null + + return conn; +}; + +/** + * Close the transport. Removes all agent connections. + */ +WebSocketTransport.prototype.close = function() { + // close all connections + for (var id in this.agents) { + if (this.agents.hasOwnProperty(id)) { + this.agents[id].close(); + } + } + this.agents = {}; + + // close the server + if (this.server) { + this.server.close(); + } +}; + +module.exports = WebSocketTransport; + +},{"../../util":23,"../Transport":10,"./WebSocketConnection":21,"promise":114,"url":157,"uuid-v4":125,"ws":126}],23:[function(_dereq_,module,exports){ +'use strict'; + +/** + * Test whether the provided value is a Promise. + * A value is marked as a Promise when it is an object containing functions + * `then` and `catch`. + * @param {*} value + * @return {boolean} Returns true when `value` is a Promise + */ +exports.isPromise = function (value) { + return value && + typeof value['then'] === 'function' && + typeof value['catch'] === 'function' +}; + +/** + * Splits an url like "protocol://domain/path" + * @param {string} url + * @return {{protocol: string, domain: string, path: string} | null} + * Returns an object with properties protocol, domain, and path + * when there is a match. Returns null if no valid url. + * + */ +exports.parseUrl = function (url) { + // match an url like "protocol://domain/path" + var match = /^([A-z]+):\/\/([^\/]+)(\/(.*)$|$)/.exec(url); + if (match) { + return { + protocol: match[1], + domain: match[2], + path: match[4] + } + } + + return null; +}; + +/** + * Normalize a url. Removes trailing slash + * @param {string} url + * @return {string} Returns the normalized url + */ +exports.normalizeURL = function (url) { + if (url[url.length - 1] == '/') { + return url.substring(0, url.length - 1); + } + else { + return url; + } +}; + +},{}],24:[function(_dereq_,module,exports){ +'use strict'; +var Connection = _dereq_('./lib/connection'); + +module.exports = { + Connection: Connection, + createConnection: function (options, implOptions, readyCallback) { + var c = new Connection(options, implOptions, readyCallback); + c.connect(); + return c; + } +}; + +},{"./lib/connection":28}],25:[function(_dereq_,module,exports){ +// Copyright (c) 2008, Fair Oaks Labs, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this list +// of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * Neither the name of Fair Oaks Labs, Inc. nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// Modified from original JSPack +exports.jspack = function (bigEndian) { + this.bigEndian = bigEndian; +} + +exports.jspack.prototype._DeArray = function (a, p, l) { + return [a.slice(p, p + l)]; +}; + +exports.jspack.prototype._EnArray = function (a, p, l, v) { + for (var i = 0; i < l; ++i) { + a[p + i] = v[i] ? v[i] : 0; + } +}; + +exports.jspack.prototype._DeChar = function (a, p) { + return String.fromCharCode(a[p]); +}; + +exports.jspack.prototype._EnChar = function (a, p, v) { + a[p] = v.charCodeAt(0); +}; + +exports.jspack.prototype._DeInt = function (a, p) { + var lsb = bigEndian ? format.len - 1 : 0; + var nsb = bigEndian ? -1 : 1; + var stp = lsb + nsb * format.len, + rv; + var ret = 0; + + var i = lsb; + var f = 1; + while (i != stp) { + rv += a[p + i] * f; + i += nsb; + f *= 256; + } + + if (format.signed) { + if ((rv & Math.pow(2, format.len * 8 - 1)) != 0) { + rv -= Math.pow(2, format.len * 8); + } + } + + return rv; +}; + +exports.jspack.prototype._EnInt = function (a, p, v) { + var lsb = bigEndian ? format.len - 1 : 0; + var nsb = bigEndian ? -1 : 1; + var stp = lsb + nsb * format.len; + + v = v < format.min ? format.min : ((v > format.max) ? format.max : v); + + var i = lsb; + while (i != stp) { + a[p + i] = v & 0xff; + i += nsb; + v >>= 8; + } +}; + +exports.jspack.prototype._DeString = function (a, p, l) { + var rv = new Array(1); + + for (i = 0; i < l; i++) { + rv[i] = String.fromCharCode(a[p + i]); + } + + return rv.join(''); +}; + +exports.jspack.prototype._EnString = function (a, p, l, v) { + for (var t, i = 0; i < l; ++i) { + t = v.charCodeAt(i); + if (!t) t = 0; + + a[p + i] = t; + } +}; + +exports.jspack.prototype._De754 = function (a, p) { + var s, e, m, i, d, bits, bit, len, bias, max; + + bit = format.bit; + len = format.len * 8 - format.bit - 1; + max = (1 << len) - 1; + bias = max >> 1; + + i = bigEndian ? 0 : format.len - 1; + d = bigEndian ? 1 : -1;; + s = a[p + i]; + i = i + d; + + bits = -7; + + e = s & ((1 << -bits) - 1); + s >>= -bits; + + for (bits += len; bits > 0; bits -= 8) { + e = e * 256 + a[p + i]; + i += d; + } + + m = e & ((1 << -bits) - 1); + e >>= -bits; + + for (bits += bit; bits > 0; bits -= 8) { + m = m * 256 + a[p + i]; + i += d; + } + + switch (e) { + case 0: + // Zero, or denormalized number + e = 1 - bias; + break; + + case max: + // NaN, or +/-Infinity + return m ? NaN : ((s ? -1 : 1) * Infinity); + + default: + // Normalized number + m = m + Math.pow(2, bit); + e = e - bias; + break; + } + + return (s ? -1 : 1) * m * Math.pow(2, e - bit); +}; + +exports.jspack.prototype._En754 = function (a, p, v) { + var s, e, m, i, d, c, bit, len, bias, max; + + bit = format.bit; + len = format.len * 8 - format.bit - 1; + max = (1 << len) - 1; + bias = max >> 1; + + s = v < 0 ? 1 : 0; + v = Math.abs(v); + + if (isNaN(v) || (v == Infinity)) { + m = isNaN(v) ? 1 : 0; + e = max; + } else { + e = Math.floor(Math.log(v) / Math.LN2); // Calculate log2 of the value + c = Math.pow(2, -e); + if (v * c < 1) { + e--; + c = c * 2; + } + + // Round by adding 1/2 the significand's LSD + if (e + bias >= 1) { + v += format.rt / c; // Normalized: bit significand digits + } else { + v += format.rt * Math.pow(2, 1 - bias); // Denormalized: <= bit significand digits + } + if (v * c >= 2) { + e++; + c = c / 2; // Rounding can increment the exponent + } + + if (e + bias >= max) { // overflow + m = 0; + e = max; + } else if (e + bias >= 1) { // normalized + m = (v * c - 1) * Math.pow(2, bit); // do not reorder this expression + e = e + bias; + } else { + // Denormalized - also catches the '0' case, somewhat by chance + m = v * Math.pow(2, bias - 1) * Math.pow(2, bit); + e = 0; + } + } + + i = bigEndian ? format.len - 1 : 0; + d = bigEndian ? -1 : 1;; + + while (bit >= 8) { + a[p + i] = m & 0xff; + i += d; + m /= 256; + bit -= 8; + } + + e = (e << bit) | m; + for (len += bit; len > 0; len -= 8) { + a[p + i] = e & 0xff; + i += d; + e /= 256; + } + + a[p + i - d] |= s * 128; +}; + +// Unpack a series of n formatements of size s from array a at offset p with fxn +exports.jspack.prototype._UnpackSeries = function (n, s, a, p) { + var fxn = format.de; + + var ret = []; + for (var i = 0; i < n; i++) { + ret.push(fxn(a, p + i * s)); + } + + return ret; +}; + +// Pack a series of n formatements of size s from array v at offset i to array a at offset p with fxn +exports.jspack.prototype._PackSeries = function (n, s, a, p, v, i) { + var fxn = format.en; + + for (o = 0; o < n; o++) { + fxn(a, p + o * s, v[i + o]); + } +}; + +// Unpack the octet array a, beginning at offset p, according to the fmt string +exports.jspack.prototype.Unpack = function (fmt, a, p) { + bigEndian = fmt.charAt(0) != '<'; + + if (p == undefined || p == null) p = 0; + + var re = new RegExp(this._sPattern, 'g'); + + var ret = []; + + for (var m; m = re.exec(fmt); /* */ ) { + var n; + if (m[1] == undefined || m[1] == '') n = 1; + else n = parseInt(m[1]); + + var s = this._lenLut[m[2]]; + + if ((p + n * s) > a.length) return undefined; + + switch (m[2]) { + case 'A': + case 's': + rv.push(this._formatLut[m[2]].de(a, p, n)); + break; + case 'c': + case 'b': + case 'B': + case 'h': + case 'H': + case 'i': + case 'I': + case 'l': + case 'L': + case 'f': + case 'd': + format = this._formatLut[m[2]]; + ret.push(this._UnpackSeries(n, s, a, p)); + break; + } + + p += n * s; + } + + return Array.prototype.concat.apply([], ret); +}; + +// Pack the supplied values into the octet array a, beginning at offset p, according to the fmt string +exports.jspack.prototype.PackTo = function (fmt, a, p, values) { + bigEndian = (fmt.charAt(0) != '<'); + + var re = new RegExp(this._sPattern, 'g'); + + for (var m, i = 0; m = re.exec(fmt); /* */ ) { + var n; + if (m[1] == undefined || m[1] == '') n = 1; + else n = parseInt(m[1]); + + var s = this._lenLut[m[2]]; + + if ((p + n * s) > a.length) return false; + + switch (m[2]) { + case 'A': + case 's': + if ((i + 1) > values.length) return false; + + this._formatLut[m[2]].en(a, p, n, values[i]); + + i += 1; + break; + + case 'c': + case 'b': + case 'B': + case 'h': + case 'H': + case 'i': + case 'I': + case 'l': + case 'L': + case 'f': + case 'd': + format = this._formatLut[m[2]]; + + if (i + n > values.length) return false; + + this._PackSeries(n, s, a, p, values, i); + + i += n; + break; + + case 'x': + for (var j = 0; j < n; j++) { + a[p + j] = 0; + } + break; + } + + p += n * s; + } + + return a; +}; + +// Pack the supplied values into a new octet array, according to the fmt string +exports.jspack.prototype.Pack = function (fmt, values) { + return this.PackTo(fmt, new Array(this.CalcLength(fmt)), 0, values); +}; + +// Determine the number of bytes represented by the format string +exports.jspack.prototype.CalcLength = function (fmt) { + var re = new RegExp(this._sPattern, 'g'); + var sz = 0; + + while (match = re.exec(fmt)) { + var n; + if (match[1] == undefined || match[1] == '') n = 1; + else n = parseInt(match[1]); + + sz += n * this._lenLut[match[2]]; + } + + return sz; +}; + +// Regular expression for counting digits +exports.jspack.prototype._sPattern = '(\\d+)?([AxcbBhHsfdiIlL])'; + +// Byte widths for associated formats +exports.jspack.prototype._lenLut = { + 'A': 1, + 'x': 1, + 'c': 1, + 'b': 1, + 'B': 1, + 'h': 2, + 'H': 2, + 's': 1, + 'f': 4, + 'd': 8, + 'i': 4, + 'I': 4, + 'l': 4, + 'L': 4 +}; + +exports.jspack.prototype._formatLut = { + 'A': { + en: exports.jspack.prototype._EnArray, + de: exports.jspack.prototype._DeArray + }, + 's': { + en: exports.jspack.prototype._EnString, + de: exports.jspack.prototype._DeString + }, + 'c': { + en: exports.jspack.prototype._EnChar, + de: exports.jspack.prototype._DeChar + }, + 'b': { + en: exports.jspack.prototype._EnInt, + de: exports.jspack.prototype._DeInt, + len: 1, + signed: true, + min: -Math.pow(2, 7), + max: Math.pow(2, 7) - 1 + }, + 'B': { + en: exports.jspack.prototype._EnInt, + de: exports.jspack.prototype._DeInt, + len: 1, + signed: false, + min: 0, + max: Math.pow(2, 8) - 1 + }, + 'h': { + en: exports.jspack.prototype._EnInt, + de: exports.jspack.prototype._DeInt, + len: 2, + signed: true, + min: -Math.pow(2, 15), + max: Math.pow(2, 15) - 1 + }, + 'H': { + en: exports.jspack.prototype._EnInt, + de: exports.jspack.prototype._DeInt, + len: 2, + signed: false, + min: 0, + max: Math.pow(2, 16) - 1 + }, + 'i': { + en: exports.jspack.prototype._EnInt, + de: exports.jspack.prototype._DeInt, + len: 4, + signed: true, + min: -Math.pow(2, 31), + max: Math.pow(2, 31) - 1 + }, + 'I': { + en: exports.jspack.prototype._EnInt, + de: exports.jspack.prototype._DeInt, + len: 4, + signed: false, + min: 0, + max: Math.pow(2, 32) - 1 + }, + 'l': { + en: exports.jspack.prototype._EnInt, + de: exports.jspack.prototype._DeInt, + len: 4, + signed: true, + min: -Math.pow(2, 31), + max: Math.pow(2, 31) - 1 + }, + 'L': { + en: exports.jspack.prototype._EnInt, + de: exports.jspack.prototype._DeInt, + len: 4, + signed: false, + min: 0, + max: Math.pow(2, 32) - 1 + }, + 'f': { + en: exports.jspack.prototype._En754, + de: exports.jspack.prototype._De754, + len: 4, + bit: 23, + rt: Math.pow(2, -24) - Math.pow(2, -77) + }, + 'd': { + en: exports.jspack.prototype._En754, + de: exports.jspack.prototype._De754, + len: 8, + bit: 52, + rt: 0 + } +}; + +},{}],26:[function(_dereq_,module,exports){ +exports.constants = [ + [1, "frameMethod"], + [2, "frameHeader"], + [3, "frameBody"], + [8, "frameHeartbeat"], + [200, "replySuccess"], + [206, "frameEnd"], + [311, "contentTooLarge"], + [313, "noConsumers"], + [320, "connectionForced"], + [402, "invalidPath"], + [403, "accessRefused"], + [404, "notFound"], + [405, "resourceLocked"], + [406, "preconditionFailed"], + [501, "frameError"], + [502, "syntaxError"], + [503, "commandInvalid"], + [504, "channelError"], + [505, "unexpectedFrame"], + [506, "resourceError"], + [530, "notAllowed"], + [540, "notImplemented"], + [541, "internalError"], + [4096, "frameMinSize"] +]; +exports.classes = [{ + "name": "connection", + "index": 10, + "fields": [], + "methods": [{ + "name": "start", + "index": 10, + "fields": [{ + "name": "versionMajor", + "domain": "octet" + }, { + "name": "versionMinor", + "domain": "octet" + }, { + "name": "serverProperties", + "domain": "table" + }, { + "name": "mechanisms", + "domain": "longstr" + }, { + "name": "locales", + "domain": "longstr" + }] + }, { + "name": "startOk", + "index": 11, + "fields": [{ + "name": "clientProperties", + "domain": "table" + }, { + "name": "mechanism", + "domain": "shortstr" + }, { + "name": "response", + "domain": "longstr" + }, { + "name": "locale", + "domain": "shortstr" + }] + }, { + "name": "secure", + "index": 20, + "fields": [{ + "name": "challenge", + "domain": "longstr" + }] + }, { + "name": "secureOk", + "index": 21, + "fields": [{ + "name": "response", + "domain": "longstr" + }] + }, { + "name": "tune", + "index": 30, + "fields": [{ + "name": "channelMax", + "domain": "short" + }, { + "name": "frameMax", + "domain": "long" + }, { + "name": "heartbeat", + "domain": "short" + }] + }, { + "name": "tuneOk", + "index": 31, + "fields": [{ + "name": "channelMax", + "domain": "short" + }, { + "name": "frameMax", + "domain": "long" + }, { + "name": "heartbeat", + "domain": "short" + }] + }, { + "name": "open", + "index": 40, + "fields": [{ + "name": "virtualHost", + "domain": "shortstr" + }, { + "name": "reserved1", + "domain": "shortstr" + }, { + "name": "reserved2", + "domain": "bit" + }] + }, { + "name": "openOk", + "index": 41, + "fields": [{ + "name": "reserved1", + "domain": "shortstr" + }] + }, { + "name": "close", + "index": 50, + "fields": [{ + "name": "replyCode", + "domain": "short" + }, { + "name": "replyText", + "domain": "shortstr" + }, { + "name": "classId", + "domain": "short" + }, { + "name": "methodId", + "domain": "short" + }] + }, { + "name": "closeOk", + "index": 51, + "fields": [] + }] +}, { + "name": "channel", + "index": 20, + "fields": [], + "methods": [{ + "name": "open", + "index": 10, + "fields": [{ + "name": "reserved1", + "domain": "shortstr" + }] + }, { + "name": "openOk", + "index": 11, + "fields": [{ + "name": "reserved1", + "domain": "longstr" + }] + }, { + "name": "flow", + "index": 20, + "fields": [{ + "name": "active", + "domain": "bit" + }] + }, { + "name": "flowOk", + "index": 21, + "fields": [{ + "name": "active", + "domain": "bit" + }] + }, { + "name": "close", + "index": 40, + "fields": [{ + "name": "replyCode", + "domain": "short" + }, { + "name": "replyText", + "domain": "shortstr" + }, { + "name": "classId", + "domain": "short" + }, { + "name": "methodId", + "domain": "short" + }] + }, { + "name": "closeOk", + "index": 41, + "fields": [] + }] +}, { + "name": "exchange", + "index": 40, + "fields": [], + "methods": [{ + "name": "declare", + "index": 10, + "fields": [{ + "name": "reserved1", + "domain": "short" + }, { + "name": "exchange", + "domain": "shortstr" + }, { + "name": "type", + "domain": "shortstr" + }, { + "name": "passive", + "domain": "bit" + }, { + "name": "durable", + "domain": "bit" + }, { + "name": "autoDelete", + "domain": "bit" + }, { + "name": "reserved2", + "domain": "bit" + }, { + "name": "reserved3", + "domain": "bit" + }, { + "name": "noWait", + "domain": "bit" + }, { + "name": "arguments", + "domain": "table" + }] + }, { + "name": "declareOk", + "index": 11, + "fields": [] + }, { + "name": "delete", + "index": 20, + "fields": [{ + "name": "reserved1", + "domain": "short" + }, { + "name": "exchange", + "domain": "shortstr" + }, { + "name": "ifUnused", + "domain": "bit" + }, { + "name": "noWait", + "domain": "bit" + }] + }, { + "name": "deleteOk", + "index": 21, + "fields": [] + }, { + "name": "bind", + "index": 30, + "fields": [{ + "name": "reserved1", + "domain": "short" + }, { + "name": "destination", + "domain": "shortstr" + }, { + "name": "source", + "domain": "shortstr" + }, { + "name": "routingKey", + "domain": "shortstr" + }, { + "name": "noWait", + "domain": "bit" + }, { + "name": "arguments", + "domain": "table" + }] + }, { + "name": "bindOk", + "index": 31, + "fields": [] + }, { + "name": "unbind", + "index": 40, + "fields": [{ + "name": "reserved1", + "domain": "short" + }, { + "name": "destination", + "domain": "shortstr" + }, { + "name": "source", + "domain": "shortstr" + }, { + "name": "routingKey", + "domain": "shortstr" + }, { + "name": "noWait", + "domain": "bit" + }, { + "name": "arguments", + "domain": "table" + }] + }, { + "name": "unbindOk", + "index": 51, + "fields": [] + }] +}, { + "name": "queue", + "index": 50, + "fields": [], + "methods": [{ + "name": "declare", + "index": 10, + "fields": [{ + "name": "reserved1", + "domain": "short" + }, { + "name": "queue", + "domain": "shortstr" + }, { + "name": "passive", + "domain": "bit" + }, { + "name": "durable", + "domain": "bit" + }, { + "name": "exclusive", + "domain": "bit" + }, { + "name": "autoDelete", + "domain": "bit" + }, { + "name": "noWait", + "domain": "bit" + }, { + "name": "arguments", + "domain": "table" + }] + }, { + "name": "declareOk", + "index": 11, + "fields": [{ + "name": "queue", + "domain": "shortstr" + }, { + "name": "messageCount", + "domain": "long" + }, { + "name": "consumerCount", + "domain": "long" + }] + }, { + "name": "bind", + "index": 20, + "fields": [{ + "name": "reserved1", + "domain": "short" + }, { + "name": "queue", + "domain": "shortstr" + }, { + "name": "exchange", + "domain": "shortstr" + }, { + "name": "routingKey", + "domain": "shortstr" + }, { + "name": "noWait", + "domain": "bit" + }, { + "name": "arguments", + "domain": "table" + }] + }, { + "name": "bindOk", + "index": 21, + "fields": [] + }, { + "name": "unbind", + "index": 50, + "fields": [{ + "name": "reserved1", + "domain": "short" + }, { + "name": "queue", + "domain": "shortstr" + }, { + "name": "exchange", + "domain": "shortstr" + }, { + "name": "routingKey", + "domain": "shortstr" + }, { + "name": "arguments", + "domain": "table" + }] + }, { + "name": "unbindOk", + "index": 51, + "fields": [] + }, { + "name": "purge", + "index": 30, + "fields": [{ + "name": "reserved1", + "domain": "short" + }, { + "name": "queue", + "domain": "shortstr" + }, { + "name": "noWait", + "domain": "bit" + }] + }, { + "name": "purgeOk", + "index": 31, + "fields": [{ + "name": "messageCount", + "domain": "long" + }] + }, { + "name": "delete", + "index": 40, + "fields": [{ + "name": "reserved1", + "domain": "short" + }, { + "name": "queue", + "domain": "shortstr" + }, { + "name": "ifUnused", + "domain": "bit" + }, { + "name": "ifEmpty", + "domain": "bit" + }, { + "name": "noWait", + "domain": "bit" + }] + }, { + "name": "deleteOk", + "index": 41, + "fields": [{ + "name": "messageCount", + "domain": "long" + }] + }] +}, { + "name": "basic", + "index": 60, + "fields": [{ + "name": "contentType", + "domain": "shortstr" + }, { + "name": "contentEncoding", + "domain": "shortstr" + }, { + "name": "headers", + "domain": "table" + }, { + "name": "deliveryMode", + "domain": "octet" + }, { + "name": "priority", + "domain": "octet" + }, { + "name": "correlationId", + "domain": "shortstr" + }, { + "name": "replyTo", + "domain": "shortstr" + }, { + "name": "expiration", + "domain": "shortstr" + }, { + "name": "messageId", + "domain": "shortstr" + }, { + "name": "timestamp", + "domain": "timestamp" + }, { + "name": "type", + "domain": "shortstr" + }, { + "name": "userId", + "domain": "shortstr" + }, { + "name": "appId", + "domain": "shortstr" + }, { + "name": "reserved", + "domain": "shortstr" + }], + "methods": [{ + "name": "qos", + "index": 10, + "fields": [{ + "name": "prefetchSize", + "domain": "long" + }, { + "name": "prefetchCount", + "domain": "short" + }, { + "name": "global", + "domain": "bit" + }] + }, { + "name": "qosOk", + "index": 11, + "fields": [] + }, { + "name": "consume", + "index": 20, + "fields": [{ + "name": "reserved1", + "domain": "short" + }, { + "name": "queue", + "domain": "shortstr" + }, { + "name": "consumerTag", + "domain": "shortstr" + }, { + "name": "noLocal", + "domain": "bit" + }, { + "name": "noAck", + "domain": "bit" + }, { + "name": "exclusive", + "domain": "bit" + }, { + "name": "noWait", + "domain": "bit" + }, { + "name": "arguments", + "domain": "table" + }] + }, { + "name": "consumeOk", + "index": 21, + "fields": [{ + "name": "consumerTag", + "domain": "shortstr" + }] + }, { + "name": "cancel", + "index": 30, + "fields": [{ + "name": "consumerTag", + "domain": "shortstr" + }, { + "name": "noWait", + "domain": "bit" + }] + }, { + "name": "cancelOk", + "index": 31, + "fields": [{ + "name": "consumerTag", + "domain": "shortstr" + }] + }, { + "name": "publish", + "index": 40, + "fields": [{ + "name": "reserved1", + "domain": "short" + }, { + "name": "exchange", + "domain": "shortstr" + }, { + "name": "routingKey", + "domain": "shortstr" + }, { + "name": "mandatory", + "domain": "bit" + }, { + "name": "immediate", + "domain": "bit" + }] + }, { + "name": "return", + "index": 50, + "fields": [{ + "name": "replyCode", + "domain": "short" + }, { + "name": "replyText", + "domain": "shortstr" + }, { + "name": "exchange", + "domain": "shortstr" + }, { + "name": "routingKey", + "domain": "shortstr" + }] + }, { + "name": "deliver", + "index": 60, + "fields": [{ + "name": "consumerTag", + "domain": "shortstr" + }, { + "name": "deliveryTag", + "domain": "longlong" + }, { + "name": "redelivered", + "domain": "bit" + }, { + "name": "exchange", + "domain": "shortstr" + }, { + "name": "routingKey", + "domain": "shortstr" + }] + }, { + "name": "get", + "index": 70, + "fields": [{ + "name": "reserved1", + "domain": "short" + }, { + "name": "queue", + "domain": "shortstr" + }, { + "name": "noAck", + "domain": "bit" + }] + }, { + "name": "getOk", + "index": 71, + "fields": [{ + "name": "deliveryTag", + "domain": "longlong" + }, { + "name": "redelivered", + "domain": "bit" + }, { + "name": "exchange", + "domain": "shortstr" + }, { + "name": "routingKey", + "domain": "shortstr" + }, { + "name": "messageCount", + "domain": "long" + }] + }, { + "name": "getEmpty", + "index": 72, + "fields": [{ + "name": "reserved1", + "domain": "shortstr" + }] + }, { + "name": "ack", + "index": 80, + "fields": [{ + "name": "deliveryTag", + "domain": "longlong" + }, { + "name": "multiple", + "domain": "bit" + }] + }, { + "name": "reject", + "index": 90, + "fields": [{ + "name": "deliveryTag", + "domain": "longlong" + }, { + "name": "requeue", + "domain": "bit" + }] + }, { + "name": "recoverAsync", + "index": 100, + "fields": [{ + "name": "requeue", + "domain": "bit" + }] + }, { + "name": "recover", + "index": 110, + "fields": [{ + "name": "requeue", + "domain": "bit" + }] + }, { + "name": "recoverOk", + "index": 111, + "fields": [] + }] +}, { + "name": "tx", + "index": 90, + "fields": [], + "methods": [{ + "name": "select", + "index": 10, + "fields": [] + }, { + "name": "selectOk", + "index": 11, + "fields": [] + }, { + "name": "commit", + "index": 20, + "fields": [] + }, { + "name": "commitOk", + "index": 21, + "fields": [] + }, { + "name": "rollback", + "index": 30, + "fields": [] + }, { + "name": "rollbackOk", + "index": 31, + "fields": [] + }] +}, { + "name": "confirm", + "index": 85, + "fields": [], + "methods": [{ + "name": "select", + "index": 10, + "fields": [{ + "name": "noWait", + "domain": "bit" + }] + }, { + "name": "selectOk", + "index": 11, + "fields": [] + }] +}]; +},{}],27:[function(_dereq_,module,exports){ +'use strict'; +var events = _dereq_('events'); +var util = _dereq_('util'); +var fs = _dereq_('fs'); +var Promise = _dereq_('./promise').Promise; +var definitions = _dereq_('./definitions'); +var methods = definitions.methods; + +// This class is not exposed to the user. Queue and Exchange are subclasses +// of Channel. This just provides a task queue. +var Channel = module.exports = function Channel (connection, channel) { + events.EventEmitter.call(this); + + // Unlimited listeners. Helps when e.g. publishing high-volume messages, + // 10 is far too low. + this.setMaxListeners(0); + + this.channel = channel; + this.connection = connection; + this._tasks = []; + + this.reconnect(); +}; +util.inherits(Channel, events.EventEmitter); + +Channel.prototype.closeOK = function() { + this.connection._sendMethod(this.channel, methods.channelCloseOk, {reserved1: ""}); +}; + +Channel.prototype.reconnect = function () { + this.connection._sendMethod(this.channel, methods.channelOpen, {reserved1: ""}); +}; + +Channel.prototype._taskPush = function (reply, cb) { + var promise = new Promise(); + this._tasks.push({ + promise: promise, + reply: reply, + sent: false, + cb: cb + }); + this._tasksFlush(); + return promise; +}; + +Channel.prototype._tasksFlush = function () { + if (this.state != 'open') return; + + for (var i = 0; i < this._tasks.length; i++) { + var task = this._tasks[i]; + if (task.sent) continue; + task.cb(); + task.sent = true; + if (!task.reply) { + // if we don't expect a reply, just delete it now + this._tasks.splice(i, 1); + i = i-1; + } + } +}; + +Channel.prototype._handleTaskReply = function (channel, method, args) { + var task, i; + + for (i = 0; i < this._tasks.length; i++) { + if (this._tasks[i].reply == method) { + task = this._tasks[i]; + this._tasks.splice(i, 1); + task.promise.emitSuccess(args); + this._tasksFlush(); + return true; + } + } + + return false; +}; + +Channel.prototype._onChannelMethod = function(channel, method, args) { + switch (method) { + case methods.channelCloseOk: + delete this.connection.channels[this.channel]; + this.state = 'closed'; + // TODO should this be falling through? + default: + this._onMethod(channel, method, args); + } +}; + +Channel.prototype.close = function(reason) { + this.state = 'closing'; + this.connection._sendMethod(this.channel, methods.channelClose, + {'replyText': reason ? reason : 'Goodbye from node', + 'replyCode': 200, + 'classId': 0, + 'methodId': 0}); +}; + +},{"./definitions":31,"./promise":35,"events":137,"fs":127,"util":159}],28:[function(_dereq_,module,exports){ +(function (process,Buffer){ +'use strict'; +var net = _dereq_('net'); +var tls = _dereq_('tls'); +var fs = _dereq_('fs'); +var URL = _dereq_('url'); +var _ = _dereq_('lodash'); +var debug = _dereq_('./debug'); +var EventEmitter = _dereq_('events').EventEmitter; +var util = _dereq_('util'); +var serializer = _dereq_('./serializer'); +var definitions = _dereq_('./definitions'); +var methods = definitions.methods; +var methodTable = definitions.methodTable; +var classes = definitions.classes; +var Exchange = _dereq_('./exchange'); +var Queue = _dereq_('./queue'); +var AMQPParser = _dereq_('./parser'); +var nodeAMQPVersion = _dereq_('../package').version; + +var maxFrameBuffer = 131072; // 128k, same as rabbitmq (which was + // copying qpid) + +var defaultPorts = { 'amqp': 5672, 'amqps': 5671 }; + +var defaultOptions = { + host: 'localhost', + port: defaultPorts['amqp'], + login: 'guest', + password: 'guest', + authMechanism: 'AMQPLAIN', + vhost: '/', + connectionTimeout: 10000, + ssl: { + enabled: false + } +}; + +var defaultSslOptions = { + port: defaultPorts['amqps'], + ssl: { + rejectUnauthorized: true + } +}; + +var defaultImplOptions = { + defaultExchangeName: '', + reconnect: true, + reconnectBackoffStrategy: 'linear', + reconnectExponentialLimit: 120000, + reconnectBackoffTime: 1000 +}; + +var defaultClientProperties = { + version: nodeAMQPVersion, + platform: 'node-' + process.version, + product: 'node-amqp' +}; + +var Connection = module.exports = function Connection (connectionArgs, options, readyCallback) { + EventEmitter.call(this); + this.setOptions(connectionArgs); + this.setImplOptions(options); + + if (typeof readyCallback === 'function') { + this._readyCallback = readyCallback; + } + + this.connectionAttemptScheduled = false; + this._defaultExchange = null; + this.channelCounter = 0; + this._sendBuffer = new Buffer(maxFrameBuffer); +}; +util.inherits(Connection, EventEmitter); + + + +Connection.prototype.setOptions = function (options) { + var urlo = (options && options.url) ? this._parseURLOptions(options.url) : {}; + var sslo = (options && options.ssl && options.ssl.enabled) ? defaultSslOptions : {}; + this.options = _.extend({}, defaultOptions, sslo, urlo, options || {}); + this.options.clientProperties = _.extend({}, defaultClientProperties, (options && options.clientProperties) || {}); +}; + +Connection.prototype.setImplOptions = function (options) { + this.implOptions = _.extend({}, defaultImplOptions, options || {}); +}; + +Connection.prototype.connect = function () { + // If this is our first connection, add listeners. + if (!this.socket) this.addAllListeners(); + + this._createSocket(); + this._startHandshake(); +}; + +Connection.prototype.reconnect = function () { + // Suspend activity on channels + for (var channel in this.channels) { + this.channels[channel].state = 'closed'; + } + debug && debug("Connection lost, reconnecting..."); + // Terminate socket activity + if (this.socket) this.socket.end(); + this.connect(); +}; + +Connection.prototype.disconnect = function () { + debug && debug("Sending disconnect request to server"); + this._sendMethod(0, methods.connectionClose, { + 'replyText': 'client disconnect', + 'replyCode': 200, + 'classId': 0, + 'methodId': 0 + }); +}; + +Connection.prototype.addAllListeners = function() { + var self = this; + var connectEvent = this.options.ssl.enabled ? 'secureConnect' : 'connect'; + + + self.addListener(connectEvent, function() { + // In the case where this is a reconnection, do not trample on the existing + // channels. + // For your reference, channel 0 is the control channel. + self.channels = self.channels || {0:self}; + self.queues = self.queues || {}; + self.exchanges = self.exchanges || {}; + + self.parser = new AMQPParser('0-9-1', 'client'); + + self.parser.onMethod = function (channel, method, args) { + self._onMethod(channel, method, args); + }; + + self.parser.onContent = function (channel, data) { + debug && debug(channel + " > content " + data.length); + if (self.channels[channel] && self.channels[channel]._onContent) { + self.channels[channel]._onContent(channel, data); + } else { + debug && debug("unhandled content: " + data); + } + }; + + self.parser.onContentHeader = function (channel, classInfo, weight, properties, size) { + debug && debug(channel + " > content header " + JSON.stringify([classInfo.name, weight, properties, size])); + if (self.channels[channel] && self.channels[channel]._onContentHeader) { + self.channels[channel]._onContentHeader(channel, classInfo, weight, properties, size); + } else { + debug && debug("unhandled content header"); + } + }; + + self.parser.onHeartBeat = function () { + self.emit("heartbeat"); + debug && debug("heartbeat"); + }; + + self.parser.onError = function (e) { + self.emit("error", e); + self.emit("close"); + }; + + // Remove readyEmitted flag so we can detect an auth error. + self.readyEmitted = false; + }); + + self.addListener('data', function (data) { + if(self.parser != null){ + try { + self.parser.execute(data); + } catch (exception) { + self.emit('error', exception); + return; + } + } + self._inboundHeartbeatTimerReset(); + }); + + var backoffTime = null; + self.addListener('error', function backoff(e) { + if (self._inboundHeartbeatTimer !== null) { + clearTimeout(self._inboundHeartbeatTimer); + self._inboundHeartbeatTimer = null; + } + if (self._outboundHeartbeatTimer !== null) { + clearTimeout(self._outboundHeartbeatTimer); + self._outboundHeartbeatTimer = null; + } + + if (!self.connectionAttemptScheduled) { + // Set to true, as we are presently in the process of scheduling one. + self.connectionAttemptScheduled = true; + + // Kill the socket, if it hasn't been killed already. + self.socket.end(); + + // Reset parser state + self.parser = null; + + // In order for our reconnection to be seamless, we have to notify the + // channels that they are no longer connected so that nobody attempts + // to send messages which would be doomed to fail. + for (var channel in self.channels) { + if (channel !== 0) { + self.channels[channel].state = 'closed'; + } + } + // Queues are channels (so we have already marked them as closed), but + // queues have special needs, since the subscriptions will no longer + // be known to the server when we reconnect. Mark the subscriptions as + // closed so that we can resubscribe them once we are reconnected. + for (var queue in self.queues) { + for (var index in self.queues[queue].consumerTagOptions) { + self.queues[queue].consumerTagOptions[index]['state'] = 'closed'; + } + } + + // Begin reconnection attempts + if (self.implOptions.reconnect) { + // Don't thrash, use a backoff strategy. + if (backoffTime === null) { + // This is the first time we've failed since a successful connection, + // so use the configured backoff time without any modification. + backoffTime = self.implOptions.reconnectBackoffTime; + } else if (self.implOptions.reconnectBackoffStrategy === 'exponential') { + // If you've configured exponential backoff, we'll double the + // backoff time each subsequent attempt until success. + backoffTime *= 2; + // limit the maxium timeout, to avoid potentially unlimited stalls + if(backoffTime > self.implOptions.reconnectExponentialLimit){ + backoffTime = self.implOptions.reconnectExponentialLimit; + } + + } else if (self.implOptions.reconnectBackoffStrategy === 'linear') { + // Linear strategy is the default. In this case, we will retry at a + // constant interval, so there's no need to change the backoff time + // between attempts. + } else { + // TODO should we warn people if they picked a nonexistent strategy? + } + + setTimeout(function () { + // Set to false, so that if we fail in the reconnect attempt, we can + // schedule another one. + self.connectionAttemptScheduled = false; + self.reconnect(); + }, backoffTime); + } else { + self.removeListener('error', backoff); + } + } + }); + + self.addListener('ready', function () { + // Reset the backoff time since we have successfully connected. + backoffTime = null; + + if (self.implOptions.reconnect) { + // Reconnect any channels which were open. + _.each(self.channels, function(channel, index) { + // FIXME why is the index "0" instead of 0? + if (index !== "0") channel.reconnect(); + }); + } + + // Set 'ready' flag for auth failure detection. + this.readyEmitted = true; + + // Restart the heartbeat to the server + self._outboundHeartbeatTimerReset(); + }); + + // Apparently, it is not possible to determine if an authentication error + // has occurred, but when the connection closes then we can HINT that a + // possible authentication error has occured. Although this may be a bug + // in the spec, handling it as a possible error is considerably better than + // failing silently. + self.addListener('end', function (){ + if (!this.readyEmitted){ + this.emit('error', new Error( + 'Connection ended: possibly due to an authentication failure.' + )); + } + }); +}; + +Connection.prototype.heartbeat = function () { + if(this.socket.writable) this.write(new Buffer([8,0,0,0,0,0,0,206])); +}; + +// connection.exchange('my-exchange', { type: 'topic' }); +// Options +// - type 'fanout', 'direct', or 'topic' (default) +// - passive (boolean) +// - durable (boolean) +// - autoDelete (boolean, default true) +Connection.prototype.exchange = function (name, options, openCallback) { + if (name === undefined) name = this.implOptions.defaultExchangeName; + + if (!options) options = {}; + if (name !== '' && options.type === undefined) options.type = 'topic'; + + try{ + var channel = this.generateChannelId(); + }catch(exception){ + this.emit("error", exception); + return; + } + var exchange = new Exchange(this, channel, name, options, openCallback); + this.channels[channel] = exchange; + this.exchanges[name] = exchange; + return exchange; +}; + +// remove an exchange when it's closed (called from Exchange) +Connection.prototype.exchangeClosed = function (name) { + if (this.exchanges[name]) delete this.exchanges[name]; +}; + +// Options +// - passive (boolean) +// - durable (boolean) +// - exclusive (boolean) +// - autoDelete (boolean, default true) +Connection.prototype.queue = function (name /* options, openCallback */) { + var options, callback; + if (typeof arguments[1] == 'object') { + options = arguments[1]; + callback = arguments[2]; + } else { + callback = arguments[1]; + } + + try{ + var channel = this.generateChannelId(); + }catch(exception){ + this.emit("error", exception); + return; + } + + var q = new Queue(this, channel, name, options, callback); + this.channels[channel] = q; + return q; +}; + +// remove a queue when it's closed (called from Queue) +Connection.prototype.queueClosed = function (name) { + if (this.queues[name]) delete this.queues[name]; +}; + +// Publishes a message to the default exchange. +Connection.prototype.publish = function (routingKey, body, options, callback) { + if (!this._defaultExchange) this._defaultExchange = this.exchange(); + return this._defaultExchange.publish(routingKey, body, options, callback); +}; + +Connection.prototype._bodyToBuffer = function (body) { + // Handles 3 cases + // - body is utf8 string + // - body is instance of Buffer + // - body is an object and its JSON representation is sent + // Does not handle the case for streaming bodies. + // Returns buffer. + if (typeof(body) == 'string') { + return [null, new Buffer(body, 'utf8')]; + } else if (body instanceof Buffer) { + return [null, body]; + } else { + var jsonBody = JSON.stringify(body); + + debug && debug('sending json: ' + jsonBody); + + var props = {contentType: 'application/json'}; + return [props, new Buffer(jsonBody, 'utf8')]; + } +}; + +Connection.prototype._inboundHeartbeatTimerReset = function () { + if (this._inboundHeartbeatTimer !== null) { + clearTimeout(this._inboundHeartbeatTimer); + this._inboundHeartbeatTimer = null; + } + if (this.options.heartbeat) { + var self = this; + var gracePeriod = 2 * this.options.heartbeat; + this._inboundHeartbeatTimer = setTimeout(function () { + if(self.socket.readable) + self.emit('error', new Error('no heartbeat or data in last ' + gracePeriod + ' seconds')); + }, gracePeriod * 1000); + } +}; + +Connection.prototype._outboundHeartbeatTimerReset = function () { + if (this._outboundHeartbeatTimer !== null) { + clearTimeout(this._outboundHeartbeatTimer); + this._outboundHeartbeatTimer = null; + } + if (this.socket.writable && this.options.heartbeat) { + var self = this; + this._outboundHeartbeatTimer = setTimeout(function () { + self.heartbeat(); + self._outboundHeartbeatTimerReset(); + }, 1000 * this.options.heartbeat); + } +}; + +Connection.prototype._onMethod = function (channel, method, args) { + debug && debug(channel + " > " + method.name + " " + JSON.stringify(args)); + + // Channel 0 is the control channel. If not zero then delegate to + // one of the channel objects. + + if (channel > 0) { + if (!this.channels[channel]) { + debug && debug("Received message on untracked channel."); + return; + } + if (!this.channels[channel]._onChannelMethod) { + throw new Error('Channel ' + channel + ' has no _onChannelMethod method.'); + } + this.channels[channel]._onChannelMethod(channel, method, args); + return; + } + + // channel 0 + + switch (method) { + // 2. The server responds, after the version string, with the + // 'connectionStart' method (contains various useless information) + case methods.connectionStart: + // We check that they're serving us AMQP 0-9 + if (args.versionMajor !== 0 && args.versionMinor != 9) { + this.socket.end(); + this.emit('error', new Error("Bad server version")); + return; + } + this.serverProperties = args.serverProperties; + // 3. Then we reply with StartOk, containing our useless information. + this._sendMethod(0, methods.connectionStartOk, { + clientProperties: this.options.clientProperties, + mechanism: this.options.authMechanism, + response: { + LOGIN: this.options.login, + PASSWORD: this.options.password + }, + locale: 'en_US' + }); + break; + + // 4. The server responds with a connectionTune request + case methods.connectionTune: + if (args.frameMax) { + debug && debug("tweaking maxFrameBuffer to " + args.frameMax); + maxFrameBuffer = args.frameMax; + } + // 5. We respond with connectionTuneOk + this._sendMethod(0, methods.connectionTuneOk, { + channelMax: 0, + frameMax: maxFrameBuffer, + heartbeat: this.options.heartbeat || 0 + }); + // 6. Then we have to send a connectionOpen request + this._sendMethod(0, methods.connectionOpen, { + virtualHost: this.options.vhost + // , capabilities: '' + // , insist: true + , + reserved1: '', + reserved2: true + }); + break; + + + case methods.connectionOpenOk: + // 7. Finally they respond with connectionOpenOk + // Whew! That's why they call it the Advanced MQP. + if (this._readyCallback) { + this._readyCallback(this); + this._readyCallback = null; + } + this.emit('ready'); + break; + + case methods.connectionClose: + var e = new Error(args.replyText); + e.code = args.replyCode; + if (!this.listeners('close').length) { + console.log('Unhandled connection error: ' + args.replyText); + } + this.socket.destroy(e); + break; + + case methods.connectionCloseOk: + debug && debug("Received close-ok from server, closing socket"); + this.socket.end(); + break; + + default: + throw new Error("Uncaught method '" + method.name + "' with args " + + JSON.stringify(args)); + } +}; + +// Generate connection options from URI string formatted with amqp scheme. +Connection.prototype._parseURLOptions = function(connectionString) { + var opts = {}; + opts.ssl = {}; + var url = URL.parse(connectionString); + var scheme = url.protocol.substring(0, url.protocol.lastIndexOf(':')); + if (scheme != 'amqp' && scheme != 'amqps') { + throw new Error('Connection URI must use amqp or amqps scheme. ' + + 'For example, "amqp://bus.megacorp.internal:5766".'); + } + opts.ssl.enabled = ('amqps' === scheme); + opts.host = url.hostname; + opts.port = url.port || defaultPorts[scheme]; + if (url.auth) { + var auth = url.auth.split(':'); + auth[0] && (opts.login = auth[0]); + auth[1] && (opts.password = auth[1]); + } + if (url.pathname) { + opts.vhost = unescape(url.pathname.substr(1)); + } + return opts; +}; + +/* + * + * Connect helpers + * + */ + +// If you pass a array of hosts, lets choose a random host or the preferred host number, or then next one. +Connection.prototype._chooseHost = function() { + if(Array.isArray(this.options.host)){ + if(this.hosti == null){ + if(typeof this.options.hostPreference == 'number') { + this.hosti = (this.options.hostPreference < this.options.host.length) ? + this.options.hostPreference : this.options.host.length-1; + } else { + this.hosti = parseInt(Math.random() * this.options.host.length, 10); + } + } else { + // If this is already set, it looks like we want to choose another one. + // Add one to hosti but don't overflow it. + this.hosti = (this.hosti + 1) % this.options.host.length; + } + return this.options.host[this.hosti]; + } else { + return this.options.host; + } +}; + +Connection.prototype._createSocket = function() { + var hostName = this._chooseHost(), self = this; + + var options = { + port: this.options.port, + host: hostName + }; + + var resetConnectionTimeout = function () { + debug && debug('connected so resetting connection timeout'); + this.setTimeout(0); + }; + + // Connect socket + if (this.options.ssl.enabled) { + debug && debug('making ssl connection'); + options = _.extend(options, this._getSSLOptions()); + this.socket = tls.connect(options, resetConnectionTimeout); + } else { + debug && debug('making non-ssl connection'); + this.socket = net.connect(options, resetConnectionTimeout); + } + var connTimeout = this.options.connectionTimeout; + if (connTimeout) { + debug && debug('setting connection timeout to ' + connTimeout); + this.socket.setTimeout(connTimeout, function () { + debug && debug('connection timeout'); + this.destroy(); + var e = new Error('connection timeout'); + e.name = 'TimeoutError'; + self.emit('error', e); + }); + } + + // Proxy events. + // Note that if we don't attach a 'data' event, no data will flow. + var events = ['close', 'connect', 'data', 'drain', 'error', 'end', 'secureConnect', 'timeout']; + _.each(events, function(event){ + self.socket.on(event, self.emit.bind(self, event)); + }); + + // Proxy a few methods that we use / previously used. + var methods = ['end', 'destroy', 'write', 'pause', 'resume', 'setEncoding', 'ref', 'unref', 'address']; + _.each(methods, function(method){ + self[method] = function(){ + self.socket[method].apply(self.socket, arguments); + }; + }); + +}; + +Connection.prototype._getSSLOptions = function() { + if (this.sslConnectionOptions) return this.sslConnectionOptions; + this.sslConnectionOptions = {}; + if (this.options.ssl.keyFile) { + this.sslConnectionOptions.key = fs.readFileSync(this.options.ssl.keyFile); + } + if (this.options.ssl.certFile) { + this.sslConnectionOptions.cert = fs.readFileSync(this.options.ssl.certFile); + } + if (this.options.ssl.caFile) { + this.sslConnectionOptions.ca = fs.readFileSync(this.options.ssl.caFile); + } + this.sslConnectionOptions.rejectUnauthorized = this.options.ssl.rejectUnauthorized; + return this.sslConnectionOptions; +}; + +// Time to start the AMQP 7-way connection initialization handshake! +// 1. The client sends the server a version string +Connection.prototype._startHandshake = function() { + debug && debug("Initiating handshake..."); + this.write("AMQP" + String.fromCharCode(0,0,9,1)); +}; + +/* + * + * Parse helpers + * + */ + +Connection.prototype._sendBody = function (channel, body, properties) { + var r = this._bodyToBuffer(body); + var props = r[0], buffer = r[1]; + + properties = _.extend(props || {}, properties); + + this._sendHeader(channel, buffer.length, properties); + + var pos = 0, len = buffer.length; + var metaSize = 8; // headerBytes = 7, frameEndBytes = 1 + var maxBodySize = maxFrameBuffer - metaSize; + + while (len > 0) { + var bodySize = len < maxBodySize ? len : maxBodySize; + var frameSize = bodySize + metaSize; + + var b = new Buffer(frameSize); + b.used = 0; + b[b.used++] = 3; // constants.frameBody + serializer.serializeInt(b, 2, channel); + serializer.serializeInt(b, 4, bodySize); + buffer.copy(b, b.used, pos, pos+bodySize); + b.used += bodySize; + b[b.used++] = 206; // constants.frameEnd; + this.write(b); + + len -= bodySize; + pos += bodySize; + } + return; +}; + +// connection: the connection +// channel: the channel to send this on +// size: size in bytes of the following message +// properties: an object containing any of the following: +// - contentType (default 'application/octet-stream') +// - contentEncoding +// - headers +// - deliveryMode +// - priority (0-9) +// - correlationId +// - replyTo +// - expiration +// - messageId +// - timestamp +// - userId +// - appId +// - clusterId +Connection.prototype._sendHeader = function(channel, size, properties) { + var b = new Buffer(maxFrameBuffer); // FIXME allocating too much. + // use freelist? + b.used = 0; + + var classInfo = classes[60]; // always basic class. + + // 7 OCTET FRAME HEADER + + b[b.used++] = 2; // constants.frameHeader + + serializer.serializeInt(b, 2, channel); + + var lengthStart = b.used; + + serializer.serializeInt(b, 4, 0 /*dummy*/); // length + + var bodyStart = b.used; + + // HEADER'S BODY + + serializer.serializeInt(b, 2, classInfo.index); // class 60 for Basic + serializer.serializeInt(b, 2, 0); // weight, always 0 for rabbitmq + serializer.serializeInt(b, 8, size); // byte size of body + + // properties - first propertyFlags + properties = _.defaults(properties || {}, {contentType: 'application/octet-stream'}); + var propertyFlags = 0; + for (var i = 0; i < classInfo.fields.length; i++) { + if (properties[classInfo.fields[i].name]) propertyFlags |= 1 << (15-i); + } + serializer.serializeInt(b, 2, propertyFlags); + // now the actual properties. + serializer.serializeFields(b, classInfo.fields, properties, false); + + //serializeTable(b, properties); + + var bodyEnd = b.used; + + // Go back to the header and write in the length now that we know it. + b.used = lengthStart; + serializer.serializeInt(b, 4, bodyEnd - bodyStart); + b.used = bodyEnd; + + // 1 OCTET END + + b[b.used++] = 206; // constants.frameEnd; + + var s = new Buffer(b.used); + b.copy(s); + + //debug && debug('header sent: ' + JSON.stringify(s)); + + this.write(s); +}; + +Connection.prototype._sendMethod = function (channel, method, args) { + debug && debug(channel + " < " + method.name + " " + JSON.stringify(args)); + var b = this._sendBuffer; + b.used = 0; + + b[b.used++] = 1; // constants.frameMethod + + serializer.serializeInt(b, 2, channel); + + var lengthIndex = b.used; + + serializer.serializeInt(b, 4, 42); // replace with actual length. + + var startIndex = b.used; + + + serializer.serializeInt(b, 2, method.classIndex); // short, classId + serializer.serializeInt(b, 2, method.methodIndex); // short, methodId + + serializer.serializeFields(b, method.fields, args, true); + + var endIndex = b.used; + + // write in the frame length now that we know it. + b.used = lengthIndex; + serializer.serializeInt(b, 4, endIndex - startIndex); + b.used = endIndex; + + b[b.used++] = 206; // constants.frameEnd; + + var c = new Buffer(b.used); + b.copy(c); + + debug && debug("sending frame: " + c.toJSON()); + + this.write(c); + + this._outboundHeartbeatTimerReset(); +}; + +// tries to find the next available id slot for a channel +Connection.prototype.generateChannelId = function () { + // start from the last used slot id + var channelId = this.channelCounter; + while(true){ + // use values in range of 1..65535 + channelId = channelId % 65535 + 1; + if(!this.channels[channelId]){ + break; + } + // after a full loop throw an Error + if(channelId == this.channelCounter){ + throw new Error("No valid Channel Id values available"); + } + } + this.channelCounter = channelId; + return this.channelCounter; +}; + +}).call(this,_dereq_("C:\\Users\\Alex\\AppData\\Roaming\\npm\\node_modules\\browserify\\node_modules\\insert-module-globals\\node_modules\\process\\browser.js"),_dereq_("buffer").Buffer) +},{"../package":39,"./debug":30,"./definitions":31,"./exchange":32,"./parser":34,"./queue":36,"./serializer":37,"C:\\Users\\Alex\\AppData\\Roaming\\npm\\node_modules\\browserify\\node_modules\\insert-module-globals\\node_modules\\process\\browser.js":144,"buffer":128,"events":137,"fs":127,"lodash":38,"net":127,"tls":127,"url":157,"util":159}],29:[function(_dereq_,module,exports){ +module.exports = { + AMQPTypes: Object.freeze({ + STRING: 'S'.charCodeAt(0) + , INTEGER: 'I'.charCodeAt(0) + , HASH: 'F'.charCodeAt(0) + , TIME: 'T'.charCodeAt(0) + , DECIMAL: 'D'.charCodeAt(0) + , BOOLEAN: 't'.charCodeAt(0) + , SIGNED_8BIT: 'b'.charCodeAt(0) + , SIGNED_16BIT: 's'.charCodeAt(0) + , SIGNED_64BIT: 'l'.charCodeAt(0) + , _32BIT_FLOAT: 'f'.charCodeAt(0) + , _64BIT_FLOAT: 'd'.charCodeAt(0) + , VOID: 'v'.charCodeAt(0) + , BYTE_ARRAY: 'x'.charCodeAt(0) + , ARRAY: 'A'.charCodeAt(0) + , TEN: '10'.charCodeAt(0) + , BOOLEAN_TRUE: '\x01' + , BOOLEAN_FALSE:'\x00' + + }) + , Indicators: Object.freeze({ + FRAME_END: 206 + }) + , FrameType: Object.freeze({ + METHOD: 1 + , HEADER: 2 + , BODY: 3 + , HEARTBEAT: 8 + }) +} + + +},{}],30:[function(_dereq_,module,exports){ +(function (process){ +'use strict'; + +var DEBUG = process.env['NODE_DEBUG_AMQP']; + +// only define debug function in debugging mode +if (DEBUG) { + module.exports = function debug () { + console.error.apply(null, arguments); + }; +} else { + module.exports = null; +} + + +}).call(this,_dereq_("C:\\Users\\Alex\\AppData\\Roaming\\npm\\node_modules\\browserify\\node_modules\\insert-module-globals\\node_modules\\process\\browser.js")) +},{"C:\\Users\\Alex\\AppData\\Roaming\\npm\\node_modules\\browserify\\node_modules\\insert-module-globals\\node_modules\\process\\browser.js":144}],31:[function(_dereq_,module,exports){ +'use strict'; + +var protocol = _dereq_('./amqp-definitions-0-9-1'); + +// a look up table for methods recieved +// indexed on class id, method id +var methodTable = {}; + +// methods keyed on their name +var methods = {}; + +// classes keyed on their index +var classes = {}; + +(function () { // anon scope for init + //debug("initializing amqp methods..."); + + for (var i = 0; i < protocol.classes.length; i++) { + var classInfo = protocol.classes[i]; + classes[classInfo.index] = classInfo; + + for (var j = 0; j < classInfo.methods.length; j++) { + var methodInfo = classInfo.methods[j]; + + var name = classInfo.name + + methodInfo.name[0].toUpperCase() + + methodInfo.name.slice(1); + + //debug(name); + + var method = { + name: name, + fields: methodInfo.fields, + methodIndex: methodInfo.index, + classIndex: classInfo.index + }; + + if (!methodTable[classInfo.index]) methodTable[classInfo.index] = {}; + methodTable[classInfo.index][methodInfo.index] = method; + methods[name] = method; + } + } +})(); + +module.exports = {methods: methods, classes: classes, methodTable: methodTable}; + +},{"./amqp-definitions-0-9-1":26}],32:[function(_dereq_,module,exports){ +'use strict'; +var events = _dereq_('events'); +var util = _dereq_('util'); +var net = _dereq_('net'); +var tls = _dereq_('tls'); +var fs = _dereq_('fs'); +var _ = _dereq_('lodash'); +var methods = _dereq_('./definitions').methods; +var Channel = _dereq_('./channel'); + +var Exchange = module.exports = function Exchange (connection, channel, name, options, openCallback) { + Channel.call(this, connection, channel); + this.name = name; + this.binds = 0; // keep track of queues bound + this.exchangeBinds = 0; // keep track of exchanges bound + this.sourceExchanges = {}; + this.options = _.defaults(options || {}, {autoDelete: true}); + this._openCallback = openCallback; + + this._sequence = null; + this._unAcked = {}; + this._addedExchangeErrorHandler = false; +}; +util.inherits(Exchange, Channel); + +// creates an error handler scoped to the given `exchange` +function createExchangeErrorHandlerFor (exchange) { + return function (err) { + if (!exchange.options.confirm) return; + + for (var id in exchange._unAcked) { + var task = exchange._unAcked[id]; + task.emit('ack error', err); + delete exchange._unAcked[id]; + } + } +}; + +Exchange.prototype._onMethod = function (channel, method, args) { + this.emit(method.name, args); + + if (this._handleTaskReply.apply(this, arguments)) + return true; + + var cb; + + switch (method) { + case methods.channelOpenOk: + // Pre-baked exchanges don't need to be declared + if (/^$|(amq\.)/.test(this.name)) { + this.state = 'open'; + // - issue #33 fix + if (this._openCallback) { + this._openCallback(this); + this._openCallback = null; + } + // -- + this.emit('open'); + + // For if we want to delete a exchange, + // we dont care if all of the options match. + } else if (this.options.noDeclare) { + this.state = 'open'; + + if (this._openCallback) { + this._openCallback(this); + this._openCallback = null; + } + + this.emit('open'); + } else { + this.connection._sendMethod(channel, methods.exchangeDeclare, + { reserved1: 0 + , reserved2: false + , reserved3: false + , exchange: this.name + , type: this.options.type || 'topic' + , passive: !!this.options.passive + , durable: !!this.options.durable + , autoDelete: !!this.options.autoDelete + , internal: !!this.options.internal + , noWait: false + , "arguments":this.options.arguments || {} + }); + this.state = 'declaring'; + } + break; + + case methods.exchangeDeclareOk: + + if (this.options.confirm) { + this.connection._sendMethod(channel, methods.confirmSelect, + { noWait: false }); + } else { + + this.state = 'open'; + this.emit('open'); + if (this._openCallback) { + this._openCallback(this); + this._openCallback = null; + } + } + + break; + + case methods.confirmSelectOk: + this._sequence = 1; + + this.state = 'open'; + this.emit('open'); + if (this._openCallback) { + this._openCallback(this); + this._openCallback = null; + } + break; + + case methods.channelClose: + this.state = "closed"; + this.closeOK(); + this.connection.exchangeClosed(this.name); + var e = new Error(args.replyText); + e.code = args.replyCode; + this.emit('error', e); + this.emit('close'); + break; + + case methods.channelCloseOk: + this.connection.exchangeClosed(this.name); + this.emit('close'); + break; + + + case methods.basicAck: + this.emit('basic-ack', args); + var sequenceNumber = args.deliveryTag.readUInt32BE(4), tag; + + if (sequenceNumber === 0 && args.multiple === true) { + // we must ack everything + for (tag in this._unAcked) { + this._unAcked[tag].emit('ack'); + delete this._unAcked[tag]; + } + } else if (sequenceNumber !== 0 && args.multiple === true) { + // we must ack everything before the delivery tag + for (tag in this._unAcked) { + if (tag <= sequenceNumber) { + this._unAcked[tag].emit('ack'); + delete this._unAcked[tag]; + } + } + } else if (this._unAcked[sequenceNumber] && args.multiple === false) { + // simple single ack + this._unAcked[sequenceNumber].emit('ack'); + delete this._unAcked[sequenceNumber]; + } + break; + + case methods.basicReturn: + this.emit('basic-return', args); + break; + + case methods.exchangeBindOk: + if (this._bindCallback) { + // setting this._bindCallback to null before calling the callback allows for a subsequent bind within the callback + cb = this._bindCallback; + this._bindCallback = null; + cb(this); + } + break; + + case methods.exchangeUnbindOk: + if (this._unbindCallback) { + cb = this._unbindCallback; + this._unbindCallback = null; + cb(this); + } + break; + + default: + throw new Error("Uncaught method '" + method.name + "' with args " + + JSON.stringify(args)); + } + + this._tasksFlush(); +}; + +// exchange.publish('routing.key', 'body'); +// +// the third argument can specify additional options +// - mandatory (boolean, default false) +// - immediate (boolean, default false) +// - contentType (default 'application/octet-stream') +// - contentEncoding +// - headers +// - deliveryMode +// - priority (0-9) +// - correlationId +// - replyTo +// - expiration +// - messageId +// - timestamp +// - userId +// - appId +// - clusterId +// +// the callback is optional and is only used when confirm is turned on for the exchange + +Exchange.prototype.publish = function (routingKey, data, options, callback) { + var self = this; + + options = _.extend({}, options || {}); + options.routingKey = routingKey; + options.exchange = self.name; + options.mandatory = options.mandatory ? true : false; + options.immediate = options.immediate ? true : false; + options.reserved1 = 0; + + var task = this._taskPush(null, function () { + self.connection._sendMethod(self.channel, methods.basicPublish, options); + // This interface is probably not appropriate for streaming large files. + // (Of course it's arguable about whether AMQP is the appropriate + // transport for large files.) The content header wants to know the size + // of the data before sending it - so there's no point in trying to have a + // general streaming interface - streaming messages of unknown size simply + // isn't possible with AMQP. This is all to say, don't send big messages. + // If you need to stream something large, chunk it yourself. + self.connection._sendBody(self.channel, data, options); + }); + + if (self.options.confirm) self._awaitConfirm(task, callback); + return task; +}; + +// registers tasks for confirms +Exchange.prototype._awaitConfirm = function _awaitConfirm (task, callback) { + if (!this._addedExchangeErrorHandler) { + this.on('error', createExchangeErrorHandlerFor(this)); + this._addedExchangeErrorHandler = true; + } + + task.sequence = this._sequence; + this._unAcked[this._sequence] = task; + this._sequence++; + + if ('function' != typeof callback) return; + + task.once('ack error', function (err) { + task.removeAllListeners(); + callback(true, err); + }); + + task.once('ack', function () { + task.removeAllListeners(); + callback(false); + }); +}; + +// do any necessary cleanups eg. after queue destruction +Exchange.prototype.cleanup = function() { + if (this.binds === 0) { // don't keep reference open if unused + this.connection.exchangeClosed(this.name); + } +}; + +Exchange.prototype.destroy = function (ifUnused) { + var self = this; + return this._taskPush(methods.exchangeDeleteOk, function () { + self.connection.exchangeClosed(self.name); + self.connection._sendMethod(self.channel, methods.exchangeDelete, + { reserved1: 0 + , exchange: self.name + , ifUnused: ifUnused ? true : false + , noWait: false + }); + }); +}; + +// E2E Unbind +// support RabbitMQ's exchange-to-exchange binding extension +// http://www.rabbitmq.com/e2e.html +Exchange.prototype.unbind = function (/* exchange, routingKey [, bindCallback] */) { + var self = this; + + // Both arguments are required. The binding to the destination + // exchange/routingKey will be unbound. + + var exchange = arguments[0] + , routingKey = arguments[1] + , callback = arguments[2] + ; + + if (callback) this._unbindCallback = callback; + + return this._taskPush(methods.exchangeUnbindOk, function () { + var source = exchange instanceof Exchange ? exchange.name : exchange; + var destination = self.name; + + if (source in self.connection.exchanges) { + delete self.sourceExchanges[source]; + self.connection.exchanges[source].exchangeBinds--; + } + + self.connection._sendMethod(self.channel, methods.exchangeUnbind, + { reserved1: 0 + , destination: destination + , source: source + , routingKey: routingKey + , noWait: false + , "arguments": {} + }); + }); +}; + +// E2E Bind +// support RabbitMQ's exchange-to-exchange binding extension +// http://www.rabbitmq.com/e2e.html +Exchange.prototype.bind = function (/* exchange, routingKey [, bindCallback] */) { + var self = this; + + // Two arguments are required. The binding to the destination + // exchange/routingKey will be established. + + var exchange = arguments[0] + , routingKey = arguments[1] + , callback = arguments[2] + ; + + if (callback) this._bindCallback = callback; + + var source = exchange instanceof Exchange ? exchange.name : exchange; + var destination = self.name; + + if(source in self.connection.exchanges) { + self.sourceExchanges[source] = self.connection.exchanges[source]; + self.connection.exchanges[source].exchangeBinds++; + } + + self.connection._sendMethod(self.channel, methods.exchangeBind, + { reserved1: 0 + , destination: destination + , source: source + , routingKey: routingKey + , noWait: false + , "arguments": {} + }); + +}; + +// E2E Bind +// support RabbitMQ's exchange-to-exchange binding extension +// http://www.rabbitmq.com/e2e.html +Exchange.prototype.bind_headers = function (/* exchange, routing [, bindCallback] */) { + var self = this; + + // Two arguments are required. The binding to the destination + // exchange/routingKey will be established. + + var exchange = arguments[0] + , routing = arguments[1] + , callback = arguments[2] + ; + + if (callback) this._bindCallback = callback; + + var source = exchange instanceof Exchange ? exchange.name : exchange; + var destination = self.name; + + if (source in self.connection.exchanges) { + self.sourceExchanges[source] = self.connection.exchanges[source]; + self.connection.exchanges[source].exchangeBinds++; + } + + self.connection._sendMethod(self.channel, methods.exchangeBind, + { reserved1: 0 + , destination: destination + , source: source + , routingKey: '' + , noWait: false + , "arguments": routing + }); +}; + +},{"./channel":27,"./definitions":31,"events":137,"fs":127,"lodash":38,"net":127,"tls":127,"util":159}],33:[function(_dereq_,module,exports){ +'use strict'; +var events = _dereq_('events'), + util = _dereq_('util'), + fs = _dereq_('fs'), + protocol, + definitions = _dereq_('./definitions'); + +// Properties: +// - routingKey +// - size +// - deliveryTag +// +// - contentType (default 'application/octet-stream') +// - contentEncoding +// - headers +// - deliveryMode +// - priority (0-9) +// - correlationId +// - replyTo +// - experation +// - messageId +// - timestamp +// - userId +// - appId +// - clusterId +var Message = module.exports = function Message (queue, args) { + var msgProperties = definitions.classes[60].fields; + + events.EventEmitter.call(this); + + this.queue = queue; + + this.deliveryTag = args.deliveryTag; + this.redelivered = args.redelivered; + this.exchange = args.exchange; + this.routingKey = args.routingKey; + this.consumerTag = args.consumerTag; + + for (var i=0, l=msgProperties.length; i= fh.length) { + fh.read = 0; + frameType = fh[fh.read++]; + frameChannel = parseInt(fh, 2); + var frameSize = parseInt(fh, 4); + fh.used = 0; // for reuse + if (frameSize > maxFrameBuffer) { + self.throwError("Oversized frame " + frameSize); + } + frameBuffer = new Buffer(frameSize); + frameBuffer.used = 0; + return frame(data.slice(needed)); + } + else { // need more! + return header; + } + } + + function frame(data) { + var fb = frameBuffer; + var needed = fb.length - fb.used; + var sourceEnd = (fb.length > data.length) ? data.length : fb.length; + data.copy(fb, fb.used, 0, sourceEnd); + fb.used += data.length; + if (data.length > needed) { + return frameEnd(data.slice(needed)); + } + else if (data.length == needed) { + return frameEnd; + } + else { + return frame; + } + } + + function frameEnd(data) { + if (data.length > 0) { + if (data[0] === Indicators.FRAME_END) { + switch (frameType) { + case FrameType.METHOD: + self._parseMethodFrame(frameChannel, frameBuffer); + break; + case FrameType.HEADER: + self._parseHeaderFrame(frameChannel, frameBuffer); + break; + case FrameType.BODY: + if (self.onContent) { + self.onContent(frameChannel, frameBuffer); + } + break; + case FrameType.HEARTBEAT: + debug && debug("heartbeat"); + if (self.onHeartBeat) self.onHeartBeat(); + break; + default: + self.throwError("Unhandled frame type " + frameType); + break; + } + return header(data.slice(1)); + } + else { + self.throwError("Missing frame end marker"); + } + } + else { + return frameEnd; + } + } + + self.parse = header; +} + +// If there's an error in the parser, call the onError handler or throw +AMQPParser.prototype.throwError = function (error) { + if (this.onError) this.onError(error); + else throw new Error(error); +}; + +// Everytime data is recieved on the socket, pass it to this function for +// parsing. +AMQPParser.prototype.execute = function (data) { + // This function only deals with dismantling and buffering the frames. + // It delegates to other functions for parsing the frame-body. + debug && debug('execute: ' + data.toString('hex')); + this.parse = this.parse(data); +}; + + +// parse Network Byte Order integers. size can be 1,2,4,8 +function parseInt (buffer, size) { + switch (size) { + case 1: + return buffer[buffer.read++]; + + case 2: + return (buffer[buffer.read++] << 8) + buffer[buffer.read++]; + + case 4: + return (buffer[buffer.read++] << 24) + (buffer[buffer.read++] << 16) + + (buffer[buffer.read++] << 8) + buffer[buffer.read++]; + + case 8: + return (buffer[buffer.read++] << 56) + (buffer[buffer.read++] << 48) + + (buffer[buffer.read++] << 40) + (buffer[buffer.read++] << 32) + + (buffer[buffer.read++] << 24) + (buffer[buffer.read++] << 16) + + (buffer[buffer.read++] << 8) + buffer[buffer.read++]; + + default: + throw new Error("cannot parse ints of that size"); + } +} + + +function parseShortString (buffer) { + var length = buffer[buffer.read++]; + var s = buffer.toString('utf8', buffer.read, buffer.read+length); + buffer.read += length; + return s; +} + + +function parseLongString (buffer) { + var length = parseInt(buffer, 4); + var s = buffer.slice(buffer.read, buffer.read + length); + buffer.read += length; + return s.toString(); +} + + +function parseSignedInteger (buffer) { + var int = parseInt(buffer, 4); + if (int & 0x80000000) { + int |= 0xEFFFFFFF; + int = -int; + } + return int; +} + +function parseValue (buffer) { + switch (buffer[buffer.read++]) { + case AMQPTypes.STRING: + return parseLongString(buffer); + + case AMQPTypes.INTEGER: + return parseInt(buffer, 4); + + case AMQPTypes.DECIMAL: + var dec = parseInt(buffer, 1); + var num = parseInt(buffer, 4); + return num / (dec * 10); + + case AMQPTypes._64BIT_FLOAT: + var b = []; + for (var i = 0; i < 8; ++i) + b[i] = buffer[buffer.read++]; + + return (new jspack(true)).Unpack('d', b); + + case AMQPTypes._32BIT_FLOAT: + var b = []; + for (var i = 0; i < 4; ++i) + b[i] = buffer[buffer.read++]; + + return (new jspack(true)).Unpack('f', b); + + case AMQPTypes.TIME: + var int = parseInt(buffer, 8); + return (new Date()).setTime(int * 1000); + + case AMQPTypes.HASH: + return parseTable(buffer); + + case AMQPTypes.SIGNED_64BIT: + return parseInt(buffer, 8); + + case AMQPTypes.BOOLEAN: + return (parseInt(buffer, 1) > 0); + + case AMQPTypes.BYTE_ARRAY: + var len = parseInt(buffer, 4); + var buf = new Buffer(len); + buffer.copy(buf, 0, buffer.read, buffer.read + len); + buffer.read += len; + return buf; + + case AMQPTypes.ARRAY: + var len = parseInt(buffer, 4); + var end = buffer.read + len; + var arr = []; + + while (buffer.read < end) { + arr.push(parseValue(buffer)); + } + + return arr; + + default: + throw new Error("Unknown field value type " + buffer[buffer.read-1]); + } +} + +function parseTable (buffer) { + var length = buffer.read + parseInt(buffer, 4); + var table = {}; + + while (buffer.read < length) { + table[parseShortString(buffer)] = parseValue(buffer); + } + + return table; +} + +function parseFields (buffer, fields) { + var args = {}; + var bitIndex = 0; + var value; + + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + + //debug && debug("parsing field " + field.name + " of type " + field.domain); + + switch (field.domain) { + case 'bit': + // 8 bits can be packed into one octet. + + // XXX check if bitIndex greater than 7? + + value = (buffer[buffer.read] & (1 << bitIndex)) ? true : false; + + if (fields[i+1] && fields[i+1].domain == 'bit') { + bitIndex++; + } else { + bitIndex = 0; + buffer.read++; + } + break; + + case 'octet': + value = buffer[buffer.read++]; + break; + + case 'short': + value = parseInt(buffer, 2); + break; + + case 'long': + value = parseInt(buffer, 4); + break; + + // In a previous version this shared code with 'longlong', which caused problems when passed Date + // integers. Nobody expects to pass a Buffer here, 53 bits is still 28 million years after 1970, we'll be fine. + case 'timestamp': + value = parseInt(buffer, 8); + break; + + // JS doesn't support 64-bit Numbers, so we expect if you're using 'longlong' that you've + // used a Buffer instead + case 'longlong': + value = new Buffer(8); + for (var j = 0; j < 8; j++) { + value[j] = buffer[buffer.read++]; + } + break; + + case 'shortstr': + value = parseShortString(buffer); + break; + + case 'longstr': + value = parseLongString(buffer); + break; + + case 'table': + value = parseTable(buffer); + break; + + default: + throw new Error("Unhandled parameter type " + field.domain); + } + //debug && debug("got " + value); + args[field.name] = value; + } + + return args; +} + + +AMQPParser.prototype._parseMethodFrame = function (channel, buffer) { + buffer.read = 0; + var classId = parseInt(buffer, 2), + methodId = parseInt(buffer, 2); + + // Make sure that this is a method that we understand. + if (!methodTable[classId] || !methodTable[classId][methodId]) { + this.throwError("Received unknown [classId, methodId] pair [" + + classId + ", " + methodId + "]"); + } + + var method = methodTable[classId][methodId]; + + if (!method) this.throwError("bad method?"); + + var args = parseFields(buffer, method.fields); + + if (this.onMethod) { + debug && debug("Executing method", channel, method, args); + this.onMethod(channel, method, args); + } +}; + + +AMQPParser.prototype._parseHeaderFrame = function (channel, buffer) { + buffer.read = 0; + + var classIndex = parseInt(buffer, 2); + var weight = parseInt(buffer, 2); + var size = parseInt(buffer, 8); + + var classInfo = classes[classIndex]; + + if (classInfo.fields.length > 15) { + this.throwError("TODO: support more than 15 properties"); + } + + var propertyFlags = parseInt(buffer, 2); + + var fields = []; + for (var i = 0; i < classInfo.fields.length; i++) { + var field = classInfo.fields[i]; + // groan. + if (propertyFlags & (1 << (15-i))) fields.push(field); + } + + var properties = parseFields(buffer, fields); + + if (this.onContentHeader) { + this.onContentHeader(channel, classInfo, weight, properties, size); + } +}; + +}).call(this,_dereq_("buffer").Buffer) +},{"../jspack":25,"./constants":29,"./debug":30,"./definitions":31,"buffer":128,"events":137,"fs":127,"net":127,"tls":127,"util":159}],35:[function(_dereq_,module,exports){ +(function (process){ +var events = _dereq_('events'); +var inherits = _dereq_('util').inherits; + +exports.Promise = function () { + events.EventEmitter.call(this); + this._blocking = false; + this.hasFired = false; + this.hasAcked = false; + this._values = undefined; +}; +inherits(exports.Promise, events.EventEmitter); + +exports.Promise.prototype.timeout = function(timeout) { + if (!timeout) { + return this._timeoutDuration; + } + + this._timeoutDuration = timeout; + + if (this.hasFired) return; + this._clearTimeout(); + + var self = this; + this._timer = setTimeout(function() { + self._timer = null; + if (self.hasFired) { + return; + } + + self.emitError(new Error('timeout')); + }, timeout); + + return this; +}; + +exports.Promise.prototype._clearTimeout = function() { + if (!this._timer) return; + + clearTimeout(this._timer); + this._timer = null; +} + +exports.Promise.prototype.emitSuccess = function() { + if (this.hasFired) return; + this.hasFired = 'success'; + this._clearTimeout(); + + this._values = Array.prototype.slice.call(arguments); + this.emit.apply(this, ['success'].concat(this._values)); +}; + +exports.Promise.prototype.emitError = function() { + if (this.hasFired) return; + this.hasFired = 'error'; + this._clearTimeout(); + + this._values = Array.prototype.slice.call(arguments); + this.emit.apply(this, ['error'].concat(this._values)); + + if (this.listeners('error').length == 0) { + var self = this; + process.nextTick(function() { + if (self.listeners('error').length == 0) { + throw (self._values[0] instanceof Error) + ? self._values[0] + : new Error('Unhandled emitError: '+JSON.stringify(self._values)); + } + }); + } +}; + +exports.Promise.prototype.addCallback = function (listener) { + if (this.hasFired === 'success') { + listener.apply(this, this._values); + } + + return this.addListener("success", listener); +}; + +exports.Promise.prototype.addErrback = function (listener) { + if (this.hasFired === 'error') { + listener.apply(this, this._values); + } + + return this.addListener("error", listener); +}; + +}).call(this,_dereq_("C:\\Users\\Alex\\AppData\\Roaming\\npm\\node_modules\\browserify\\node_modules\\insert-module-globals\\node_modules\\process\\browser.js")) +},{"C:\\Users\\Alex\\AppData\\Roaming\\npm\\node_modules\\browserify\\node_modules\\insert-module-globals\\node_modules\\process\\browser.js":144,"events":137,"util":159}],36:[function(_dereq_,module,exports){ +(function (process,Buffer){ +'use strict'; +var util = _dereq_('util'); +var fs = _dereq_('fs'); +var _ = _dereq_('lodash'); +var Channel = _dereq_('./channel'); +var Exchange = _dereq_('./exchange'); +var Message = _dereq_('./message'); +var debug = _dereq_('./debug'); +var definitions = _dereq_('./definitions'); +var methods = definitions.methods; +var classes = definitions.classes; + +var Queue = module.exports = function Queue (connection, channel, name, options, callback) { + Channel.call(this, connection, channel); + + var self = this; + this.name = name; + this._bindings = {}; + this.consumerTagListeners = {}; + this.consumerTagOptions = {}; + + // route messages to subscribers based on consumerTag + this.on('rawMessage', function(message) { + if (message.consumerTag && self.consumerTagListeners[message.consumerTag]) { + self.consumerTagListeners[message.consumerTag](message); + } + }); + + this.options = { autoDelete: true, closeChannelOnUnsubscribe: false }; + _.extend(this.options, options || {}); + + this._openCallback = callback; +}; +util.inherits(Queue, Channel); + +Queue.prototype.subscribeRaw = function (options, messageListener) { + var self = this; + + // multiple method signatures + if (typeof options === "function") { + messageListener = options; + options = {}; + } + + var consumerTag = 'node-amqp-' + process.pid + '-' + Math.random(); + this.consumerTagListeners[consumerTag] = messageListener; + + options = options || {}; + options['state'] = 'opening'; + this.consumerTagOptions[consumerTag] = options; + if (options.prefetchCount !== undefined) { + self.connection._sendMethod(self.channel, methods.basicQos, + { reserved1: 0 + , prefetchSize: 0 + , prefetchCount: options.prefetchCount + , global: false + }); + } + + return this._taskPush(methods.basicConsumeOk, function () { + self.connection._sendMethod(self.channel, methods.basicConsume, + { reserved1: 0 + , queue: self.name + , consumerTag: consumerTag + , noLocal: !!options.noLocal + , noAck: !!options.noAck + , exclusive: !!options.exclusive + , noWait: false + , "arguments": {} + }); + self.consumerTagOptions[consumerTag]['state'] = 'open'; + }); +}; + +Queue.prototype.unsubscribe = function(consumerTag) { + var self = this; + return this._taskPush(methods.basicCancelOk, function () { + self.connection._sendMethod(self.channel, methods.basicCancel, + { reserved1: 0, + consumerTag: consumerTag, + noWait: false }); + }) + .addCallback(function () { + if (self.options.closeChannelOnUnsubscribe) { + self.close(); + } + delete self.consumerTagListeners[consumerTag]; + delete self.consumerTagOptions[consumerTag]; + }); +}; + +Queue.prototype.subscribe = function (options, messageListener) { + var self = this; + + // Optional options + if (typeof options === "function") { + messageListener = options; + options = {}; + } + + options = _.defaults(options || {}, { + ack: false, + prefetchCount: 1, + routingKeyInPayload: self.connection.options.routingKeyInPayload, + deliveryTagInPayload: self.connection.options.deliveryTagInPayload + }); + + // basic consume + var rawOptions = { + noAck: !options.ack, + exclusive: options.exclusive + }; + + if (options.ack) { + rawOptions['prefetchCount'] = options.prefetchCount; + } + + return this.subscribeRaw(rawOptions, function (m) { + var contentType = m.contentType; + + if (contentType == null && m.headers && m.headers.properties) { + contentType = m.headers.properties.content_type; + } + + var isJSON = contentType == 'text/json' || + contentType == 'application/json'; + + var buffer; + + if (isJSON) { + buffer = ""; + } else { + buffer = new Buffer(m.size); + buffer.used = 0; + } + + self._lastMessage = m; + + m.addListener('data', function (d) { + if (isJSON) { + buffer += d.toString(); + } else { + d.copy(buffer, buffer.used); + buffer.used += d.length; + } + }); + + m.addListener('end', function () { + var json, deliveryInfo = {}, msgProperties = classes[60].fields, i, l; + + if (isJSON) { + try { + json = JSON.parse(buffer); + } catch (e) { + json = null; + deliveryInfo.parseError = e; + deliveryInfo.rawData = buffer; + } + } else { + json = { data: buffer, contentType: m.contentType }; + } + + for (i = 0, l = msgProperties.length; i b.length) { + throw new Error("write out of bounds"); + } + + // Only 4 cases - just going to be explicit instead of looping. + + switch (size) { + // octet + case 1: + b[b.used++] = int; + break; + + // short + case 2: + b[b.used++] = (int & 0xFF00) >> 8; + b[b.used++] = (int & 0x00FF) >> 0; + break; + + // long + case 4: + b[b.used++] = (int & 0xFF000000) >> 24; + b[b.used++] = (int & 0x00FF0000) >> 16; + b[b.used++] = (int & 0x0000FF00) >> 8; + b[b.used++] = (int & 0x000000FF) >> 0; + break; + + + // long long + case 8: + b[b.used++] = (int & 0xFF00000000000000) >> 56; + b[b.used++] = (int & 0x00FF000000000000) >> 48; + b[b.used++] = (int & 0x0000FF0000000000) >> 40; + b[b.used++] = (int & 0x000000FF00000000) >> 32; + b[b.used++] = (int & 0x00000000FF000000) >> 24; + b[b.used++] = (int & 0x0000000000FF0000) >> 16; + b[b.used++] = (int & 0x000000000000FF00) >> 8; + b[b.used++] = (int & 0x00000000000000FF) >> 0; + break; + + default: + throw new Error("Bad size"); + } + }, + + + serializeShortString: function (b, string) { + if (typeof(string) != "string") { + throw new Error("param must be a string"); + } + var byteLength = Buffer.byteLength(string, 'utf8'); + if (byteLength > 0xFF) { + throw new Error("String too long for 'shortstr' parameter"); + } + if (1 + byteLength + b.used >= b.length) { + throw new Error("Not enough space in buffer for 'shortstr'"); + } + b[b.used++] = byteLength; + b.write(string, b.used, 'utf8'); + b.used += byteLength; + }, + + serializeLongString: function(b, string) { + // we accept string, object, or buffer for this parameter. + // in the case of string we serialize it to utf8. + if (typeof(string) == 'string') { + var byteLength = Buffer.byteLength(string, 'utf8'); + serializer.serializeInt(b, 4, byteLength); + b.write(string, b.used, 'utf8'); + b.used += byteLength; + } else if (typeof(string) == 'object') { + serializer.serializeTable(b, string); + } else { + // data is Buffer + var byteLength = string.length; + serializer.serializeInt(b, 4, byteLength); + b.write(string, b.used); // memcpy + b.used += byteLength; + } + }, + + serializeDate: function(b, date) { + serializer.serializeInt(b, 8, date.valueOf() / 1000); + }, + + serializeBuffer: function(b, buffer) { + serializer.serializeInt(b, 4, buffer.length); + buffer.copy(b, b.used, 0); + b.used += buffer.length; + }, + + serializeBase64: function(b, buffer) { + serializer.serializeLongString(b, buffer.toString('base64')); + }, + + isBigInt: function(value) { + return value > 0xffffffff; + }, + + getCode: function(dec) { + var hexArray = "0123456789ABCDEF".split(''); + var code1 = Math.floor(dec / 16); + var code2 = dec - code1 * 16; + return hexArray[code2]; + }, + + isFloat: function(value){ + return value === +value && value !== (value|0); + }, + + serializeValue: function(b, value) { + switch (typeof(value)) { + case 'string': + b[b.used++] = 'S'.charCodeAt(0); + serializer.serializeLongString(b, value); + break; + + case 'number': + if (!serializer.isFloat(value)) { + if (serializer.isBigInt(value)) { + // 64-bit uint + b[b.used++] = 'l'.charCodeAt(0); + serializer.serializeInt(b, 8, value); + } else { + //32-bit uint + b[b.used++] = 'I'.charCodeAt(0); + serializer.serializeInt(b, 4, value); + } + } else { + //64-bit float + b[b.used++] = 'd'.charCodeAt(0); + serializer.serializeFloat(b, 8, value); + } + break; + + case 'boolean': + b[b.used++] = 't'.charCodeAt(0); + b[b.used++] = value; + break; + + default: + if (value instanceof Date) { + b[b.used++] = 'T'.charCodeAt(0); + serializer.serializeDate(b, value); + } else if (value instanceof Buffer) { + b[b.used++] = 'x'.charCodeAt(0); + serializer.serializeBuffer(b, value); + } else if (Array.isArray(value)) { + b[b.used++] = 'A'.charCodeAt(0); + serializer.serializeArray(b, value); + } else if (typeof(value) === 'object') { + b[b.used++] = 'F'.charCodeAt(0); + serializer.serializeTable(b, value); + } else { + throw new Error("unsupported type in amqp table: " + typeof(value)); + } + } + }, + + serializeTable: function(b, object) { + if (typeof(object) != "object") { + throw new Error("param must be an object"); + } + + // Save our position so that we can go back and write the length of this table + // at the beginning of the packet (once we know how many entries there are). + var lengthIndex = b.used; + b.used += 4; // sizeof long + var startIndex = b.used; + + for (var key in object) { + if (!object.hasOwnProperty(key)) continue; + serializer.serializeShortString(b, key); + serializer.serializeValue(b, object[key]); + } + + var endIndex = b.used; + b.used = lengthIndex; + serializer.serializeInt(b, 4, endIndex - startIndex); + b.used = endIndex; + }, + + serializeArray: function(b, arr) { + // Save our position so that we can go back and write the byte length of this array + // at the beginning of the packet (once we have serialized all elements). + var lengthIndex = b.used; + b.used += 4; // sizeof long + var startIndex = b.used; + + var len = arr.length; + for (var i = 0; i < len; i++) { + serializer.serializeValue(b, arr[i]); + } + + var endIndex = b.used; + b.used = lengthIndex; + serializer.serializeInt(b, 4, endIndex - startIndex); + b.used = endIndex; + }, + + serializeFields: function(buffer, fields, args, strict) { + var bitField = 0; + var bitIndex = 0; + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + var domain = field.domain; + if (!(field.name in args)) { + if (strict) { + throw new Error("Missing field '" + field.name + "' of type '" + domain + "' while executing AMQP method '" + + arguments.callee.caller.arguments[1].name + "'"); + } + continue; + } + + var param = args[field.name]; + + //debug("domain: " + domain + " param: " + param); + + switch (domain) { + case 'bit': + if (typeof(param) != "boolean") { + throw new Error("Unmatched field " + JSON.stringify(field)); + } + + if (param) bitField |= (1 << bitIndex); + bitIndex++; + + if (!fields[i+1] || fields[i+1].domain != 'bit') { + //debug('SET bit field ' + field.name + ' 0x' + bitField.toString(16)); + buffer[buffer.used++] = bitField; + bitField = 0; + bitIndex = 0; + } + break; + + case 'octet': + if (typeof(param) != "number" || param > 0xFF) { + throw new Error("Unmatched field " + JSON.stringify(field)); + } + buffer[buffer.used++] = param; + break; + + case 'short': + if (typeof(param) != "number" || param > 0xFFFF) { + throw new Error("Unmatched field " + JSON.stringify(field)); + } + serializer.serializeInt(buffer, 2, param); + break; + + case 'long': + if (typeof(param) != "number" || param > 0xFFFFFFFF) { + throw new Error("Unmatched field " + JSON.stringify(field)); + } + serializer.serializeInt(buffer, 4, param); + break; + + // In a previous version this shared code with 'longlong', which caused problems when passed Date + // integers. Nobody expects to pass a Buffer here, 53 bits is still 28 million years after 1970, we'll be fine. + case 'timestamp': + serializer.serializeInt(buffer, 8, param); + break; + + case 'longlong': + for (var j = 0; j < 8; j++) { + buffer[buffer.used++] = param[j]; + } + break; + + case 'shortstr': + if (typeof(param) != "string" || param.length > 0xFF) { + throw new Error("Unmatched field " + JSON.stringify(field)); + } + serializer.serializeShortString(buffer, param); + break; + + case 'longstr': + serializer.serializeLongString(buffer, param); + break; + + case 'table': + if (typeof(param) != "object") { + throw new Error("Unmatched field " + JSON.stringify(field)); + } + serializer.serializeTable(buffer, param); + break; + + default: + throw new Error("Unknown domain value type " + domain); + } + } + } +}; + +}).call(this,_dereq_("buffer").Buffer) +},{"../jspack":25,"buffer":128}],38:[function(_dereq_,module,exports){ +(function (global){ +/** + * @license + * Lo-Dash 1.3.1 (Custom Build) + * Build: `lodash modern -o ./dist/lodash.js` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.4.4 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. + * Available under MIT license + */ +;(function(window) { + + /** Used as a safe reference for `undefined` in pre ES5 environments */ + var undefined; + + /** Used to pool arrays and objects used internally */ + var arrayPool = [], + objectPool = []; + + /** Used to generate unique IDs */ + var idCounter = 0; + + /** Used internally to indicate various things */ + var indicatorObject = {}; + + /** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */ + var keyPrefix = +new Date + ''; + + /** Used as the size when optimizations are enabled for large arrays */ + var largeArraySize = 75; + + /** Used as the max size of the `arrayPool` and `objectPool` */ + var maxPoolSize = 40; + + /** Used to match empty string literals in compiled template source */ + var reEmptyStringLeading = /\b__p \+= '';/g, + reEmptyStringMiddle = /\b(__p \+=) '' \+/g, + reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; + + /** Used to match HTML entities */ + var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g; + + /** + * Used to match ES6 template delimiters + * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-7.8.6 + */ + var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; + + /** Used to match regexp flags from their coerced string values */ + var reFlags = /\w*$/; + + /** Used to match "interpolate" template delimiters */ + var reInterpolate = /<%=([\s\S]+?)%>/g; + + /** Used to detect functions containing a `this` reference */ + var reThis = (reThis = /\bthis\b/) && reThis.test(runInContext) && reThis; + + /** Used to detect and test whitespace */ + var whitespace = ( + // whitespace + ' \t\x0B\f\xA0\ufeff' + + + // line terminators + '\n\r\u2028\u2029' + + + // unicode category "Zs" space separators + '\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000' + ); + + /** Used to match leading whitespace and zeros to be removed */ + var reLeadingSpacesAndZeros = RegExp('^[' + whitespace + ']*0+(?=.$)'); + + /** Used to ensure capturing order of template delimiters */ + var reNoMatch = /($^)/; + + /** Used to match HTML characters */ + var reUnescapedHtml = /[&<>"']/g; + + /** Used to match unescaped characters in compiled string literals */ + var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g; + + /** Used to assign default `context` object properties */ + var contextProps = [ + 'Array', 'Boolean', 'Date', 'Function', 'Math', 'Number', 'Object', + 'RegExp', 'String', '_', 'attachEvent', 'clearTimeout', 'isFinite', 'isNaN', + 'parseInt', 'setImmediate', 'setTimeout' + ]; + + /** Used to make template sourceURLs easier to identify */ + var templateCounter = 0; + + /** `Object#toString` result shortcuts */ + var argsClass = '[object Arguments]', + arrayClass = '[object Array]', + boolClass = '[object Boolean]', + dateClass = '[object Date]', + errorClass = '[object Error]', + funcClass = '[object Function]', + numberClass = '[object Number]', + objectClass = '[object Object]', + regexpClass = '[object RegExp]', + stringClass = '[object String]'; + + /** Used to identify object classifications that `_.clone` supports */ + var cloneableClasses = {}; + cloneableClasses[funcClass] = false; + cloneableClasses[argsClass] = cloneableClasses[arrayClass] = + cloneableClasses[boolClass] = cloneableClasses[dateClass] = + cloneableClasses[numberClass] = cloneableClasses[objectClass] = + cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true; + + /** Used to determine if values are of the language type Object */ + var objectTypes = { + 'boolean': false, + 'function': true, + 'object': true, + 'number': false, + 'string': false, + 'undefined': false + }; + + /** Used to escape characters for inclusion in compiled string literals */ + var stringEscapes = { + '\\': '\\', + "'": "'", + '\n': 'n', + '\r': 'r', + '\t': 't', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + /** Detect free variable `exports` */ + var freeExports = objectTypes[typeof exports] && exports; + + /** Detect free variable `module` */ + var freeModule = objectTypes[typeof module] && module && module.exports == freeExports && module; + + /** Detect free variable `global`, from Node.js or Browserified code, and use it as `window` */ + var freeGlobal = objectTypes[typeof global] && global; + if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) { + window = freeGlobal; + } + + /*--------------------------------------------------------------------------*/ + + /** + * A basic implementation of `_.indexOf` without support for binary searches + * or `fromIndex` constraints. + * + * @private + * @param {Array} array The array to search. + * @param {Mixed} value The value to search for. + * @param {Number} [fromIndex=0] The index to search from. + * @returns {Number} Returns the index of the matched value or `-1`. + */ + function basicIndexOf(array, value, fromIndex) { + var index = (fromIndex || 0) - 1, + length = array.length; + + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; + } + + /** + * An implementation of `_.contains` for cache objects that mimics the return + * signature of `_.indexOf` by returning `0` if the value is found, else `-1`. + * + * @private + * @param {Object} cache The cache object to inspect. + * @param {Mixed} value The value to search for. + * @returns {Number} Returns `0` if `value` is found, else `-1`. + */ + function cacheIndexOf(cache, value) { + var type = typeof value; + cache = cache.cache; + + if (type == 'boolean' || value == null) { + return cache[value]; + } + if (type != 'number' && type != 'string') { + type = 'object'; + } + var key = type == 'number' ? value : keyPrefix + value; + cache = cache[type] || (cache[type] = {}); + + return type == 'object' + ? (cache[key] && basicIndexOf(cache[key], value) > -1 ? 0 : -1) + : (cache[key] ? 0 : -1); + } + + /** + * Adds a given `value` to the corresponding cache object. + * + * @private + * @param {Mixed} value The value to add to the cache. + */ + function cachePush(value) { + var cache = this.cache, + type = typeof value; + + if (type == 'boolean' || value == null) { + cache[value] = true; + } else { + if (type != 'number' && type != 'string') { + type = 'object'; + } + var key = type == 'number' ? value : keyPrefix + value, + typeCache = cache[type] || (cache[type] = {}); + + if (type == 'object') { + if ((typeCache[key] || (typeCache[key] = [])).push(value) == this.array.length) { + cache[type] = false; + } + } else { + typeCache[key] = true; + } + } + } + + /** + * Used by `_.max` and `_.min` as the default `callback` when a given + * `collection` is a string value. + * + * @private + * @param {String} value The character to inspect. + * @returns {Number} Returns the code unit of given character. + */ + function charAtCallback(value) { + return value.charCodeAt(0); + } + + /** + * Used by `sortBy` to compare transformed `collection` values, stable sorting + * them in ascending order. + * + * @private + * @param {Object} a The object to compare to `b`. + * @param {Object} b The object to compare to `a`. + * @returns {Number} Returns the sort order indicator of `1` or `-1`. + */ + function compareAscending(a, b) { + var ai = a.index, + bi = b.index; + + a = a.criteria; + b = b.criteria; + + // ensure a stable sort in V8 and other engines + // http://code.google.com/p/v8/issues/detail?id=90 + if (a !== b) { + if (a > b || typeof a == 'undefined') { + return 1; + } + if (a < b || typeof b == 'undefined') { + return -1; + } + } + return ai < bi ? -1 : 1; + } + + /** + * Creates a cache object to optimize linear searches of large arrays. + * + * @private + * @param {Array} [array=[]] The array to search. + * @returns {Null|Object} Returns the cache object or `null` if caching should not be used. + */ + function createCache(array) { + var index = -1, + length = array.length; + + var cache = getObject(); + cache['false'] = cache['null'] = cache['true'] = cache['undefined'] = false; + + var result = getObject(); + result.array = array; + result.cache = cache; + result.push = cachePush; + + while (++index < length) { + result.push(array[index]); + } + return cache.object === false + ? (releaseObject(result), null) + : result; + } + + /** + * Used by `template` to escape characters for inclusion in compiled + * string literals. + * + * @private + * @param {String} match The matched character to escape. + * @returns {String} Returns the escaped character. + */ + function escapeStringChar(match) { + return '\\' + stringEscapes[match]; + } + + /** + * Gets an array from the array pool or creates a new one if the pool is empty. + * + * @private + * @returns {Array} The array from the pool. + */ + function getArray() { + return arrayPool.pop() || []; + } + + /** + * Gets an object from the object pool or creates a new one if the pool is empty. + * + * @private + * @returns {Object} The object from the pool. + */ + function getObject() { + return objectPool.pop() || { + 'array': null, + 'cache': null, + 'criteria': null, + 'false': false, + 'index': 0, + 'leading': false, + 'maxWait': 0, + 'null': false, + 'number': null, + 'object': null, + 'push': null, + 'string': null, + 'trailing': false, + 'true': false, + 'undefined': false, + 'value': null + }; + } + + /** + * A no-operation function. + * + * @private + */ + function noop() { + // no operation performed + } + + /** + * Releases the given `array` back to the array pool. + * + * @private + * @param {Array} [array] The array to release. + */ + function releaseArray(array) { + array.length = 0; + if (arrayPool.length < maxPoolSize) { + arrayPool.push(array); + } + } + + /** + * Releases the given `object` back to the object pool. + * + * @private + * @param {Object} [object] The object to release. + */ + function releaseObject(object) { + var cache = object.cache; + if (cache) { + releaseObject(cache); + } + object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null; + if (objectPool.length < maxPoolSize) { + objectPool.push(object); + } + } + + /** + * Slices the `collection` from the `start` index up to, but not including, + * the `end` index. + * + * Note: This function is used, instead of `Array#slice`, to support node lists + * in IE < 9 and to ensure dense arrays are returned. + * + * @private + * @param {Array|Object|String} collection The collection to slice. + * @param {Number} start The start index. + * @param {Number} end The end index. + * @returns {Array} Returns the new array. + */ + function slice(array, start, end) { + start || (start = 0); + if (typeof end == 'undefined') { + end = array ? array.length : 0; + } + var index = -1, + length = end - start || 0, + result = Array(length < 0 ? 0 : length); + + while (++index < length) { + result[index] = array[start + index]; + } + return result; + } + + /*--------------------------------------------------------------------------*/ + + /** + * Create a new `lodash` function using the given `context` object. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Object} [context=window] The context object. + * @returns {Function} Returns the `lodash` function. + */ + function runInContext(context) { + // Avoid issues with some ES3 environments that attempt to use values, named + // after built-in constructors like `Object`, for the creation of literals. + // ES5 clears this up by stating that literals must use built-in constructors. + // See http://es5.github.com/#x11.1.5. + context = context ? _.defaults(window.Object(), context, _.pick(window, contextProps)) : window; + + /** Native constructor references */ + var Array = context.Array, + Boolean = context.Boolean, + Date = context.Date, + Function = context.Function, + Math = context.Math, + Number = context.Number, + Object = context.Object, + RegExp = context.RegExp, + String = context.String, + TypeError = context.TypeError; + + /** + * Used for `Array` method references. + * + * Normally `Array.prototype` would suffice, however, using an array literal + * avoids issues in Narwhal. + */ + var arrayRef = []; + + /** Used for native method references */ + var objectProto = Object.prototype, + stringProto = String.prototype; + + /** Used to restore the original `_` reference in `noConflict` */ + var oldDash = context._; + + /** Used to detect if a method is native */ + var reNative = RegExp('^' + + String(objectProto.valueOf) + .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + .replace(/valueOf|for [^\]]+/g, '.+?') + '$' + ); + + /** Native method shortcuts */ + var ceil = Math.ceil, + clearTimeout = context.clearTimeout, + concat = arrayRef.concat, + floor = Math.floor, + fnToString = Function.prototype.toString, + getPrototypeOf = reNative.test(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf, + hasOwnProperty = objectProto.hasOwnProperty, + push = arrayRef.push, + propertyIsEnumerable = objectProto.propertyIsEnumerable, + setImmediate = context.setImmediate, + setTimeout = context.setTimeout, + toString = objectProto.toString; + + /* Native method shortcuts for methods with the same name as other `lodash` methods */ + var nativeBind = reNative.test(nativeBind = toString.bind) && nativeBind, + nativeCreate = reNative.test(nativeCreate = Object.create) && nativeCreate, + nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, + nativeIsFinite = context.isFinite, + nativeIsNaN = context.isNaN, + nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys, + nativeMax = Math.max, + nativeMin = Math.min, + nativeParseInt = context.parseInt, + nativeRandom = Math.random, + nativeSlice = arrayRef.slice; + + /** Detect various environments */ + var isIeOpera = reNative.test(context.attachEvent), + isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera); + + /** Used to lookup a built-in constructor by [[Class]] */ + var ctorByClass = {}; + ctorByClass[arrayClass] = Array; + ctorByClass[boolClass] = Boolean; + ctorByClass[dateClass] = Date; + ctorByClass[funcClass] = Function; + ctorByClass[objectClass] = Object; + ctorByClass[numberClass] = Number; + ctorByClass[regexpClass] = RegExp; + ctorByClass[stringClass] = String; + + /*--------------------------------------------------------------------------*/ + + /** + * Creates a `lodash` object, which wraps the given `value`, to enable method + * chaining. + * + * In addition to Lo-Dash methods, wrappers also have the following `Array` methods: + * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`, + * and `unshift` + * + * Chaining is supported in custom builds as long as the `value` method is + * implicitly or explicitly included in the build. + * + * The chainable wrapper functions are: + * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, + * `compose`, `concat`, `countBy`, `createCallback`, `debounce`, `defaults`, + * `defer`, `delay`, `difference`, `filter`, `flatten`, `forEach`, `forIn`, + * `forOwn`, `functions`, `groupBy`, `initial`, `intersection`, `invert`, + * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`, + * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `push`, `range`, + * `reject`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, `sortBy`, `splice`, + * `tap`, `throttle`, `times`, `toArray`, `transform`, `union`, `uniq`, `unshift`, + * `unzip`, `values`, `where`, `without`, `wrap`, and `zip` + * + * The non-chainable wrapper functions are: + * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `has`, + * `identity`, `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, + * `isElement`, `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, + * `isNull`, `isNumber`, `isObject`, `isPlainObject`, `isRegExp`, `isString`, + * `isUndefined`, `join`, `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, + * `pop`, `random`, `reduce`, `reduceRight`, `result`, `shift`, `size`, `some`, + * `sortedIndex`, `runInContext`, `template`, `unescape`, `uniqueId`, and `value` + * + * The wrapper functions `first` and `last` return wrapped values when `n` is + * passed, otherwise they return unwrapped values. + * + * @name _ + * @constructor + * @alias chain + * @category Chaining + * @param {Mixed} value The value to wrap in a `lodash` instance. + * @returns {Object} Returns a `lodash` instance. + * @example + * + * var wrapped = _([1, 2, 3]); + * + * // returns an unwrapped value + * wrapped.reduce(function(sum, num) { + * return sum + num; + * }); + * // => 6 + * + * // returns a wrapped value + * var squares = wrapped.map(function(num) { + * return num * num; + * }); + * + * _.isArray(squares); + * // => false + * + * _.isArray(squares.value()); + * // => true + */ + function lodash(value) { + // don't wrap if already wrapped, even if wrapped by a different `lodash` constructor + return (value && typeof value == 'object' && !isArray(value) && hasOwnProperty.call(value, '__wrapped__')) + ? value + : new lodashWrapper(value); + } + + /** + * A fast path for creating `lodash` wrapper objects. + * + * @private + * @param {Mixed} value The value to wrap in a `lodash` instance. + * @returns {Object} Returns a `lodash` instance. + */ + function lodashWrapper(value) { + this.__wrapped__ = value; + } + // ensure `new lodashWrapper` is an instance of `lodash` + lodashWrapper.prototype = lodash.prototype; + + /** + * An object used to flag environments features. + * + * @static + * @memberOf _ + * @type Object + */ + var support = lodash.support = {}; + + /** + * Detect if `Function#bind` exists and is inferred to be fast (all but V8). + * + * @memberOf _.support + * @type Boolean + */ + support.fastBind = nativeBind && !isV8; + + /** + * By default, the template delimiters used by Lo-Dash are similar to those in + * embedded Ruby (ERB). Change the following template settings to use alternative + * delimiters. + * + * @static + * @memberOf _ + * @type Object + */ + lodash.templateSettings = { + + /** + * Used to detect `data` property values to be HTML-escaped. + * + * @memberOf _.templateSettings + * @type RegExp + */ + 'escape': /<%-([\s\S]+?)%>/g, + + /** + * Used to detect code to be evaluated. + * + * @memberOf _.templateSettings + * @type RegExp + */ + 'evaluate': /<%([\s\S]+?)%>/g, + + /** + * Used to detect `data` property values to inject. + * + * @memberOf _.templateSettings + * @type RegExp + */ + 'interpolate': reInterpolate, + + /** + * Used to reference the data object in the template text. + * + * @memberOf _.templateSettings + * @type String + */ + 'variable': '', + + /** + * Used to import variables into the compiled template. + * + * @memberOf _.templateSettings + * @type Object + */ + 'imports': { + + /** + * A reference to the `lodash` function. + * + * @memberOf _.templateSettings.imports + * @type Function + */ + '_': lodash + } + }; + + /*--------------------------------------------------------------------------*/ + + /** + * Creates a function that, when called, invokes `func` with the `this` binding + * of `thisArg` and prepends any `partialArgs` to the arguments passed to the + * bound function. + * + * @private + * @param {Function|String} func The function to bind or the method name. + * @param {Mixed} [thisArg] The `this` binding of `func`. + * @param {Array} partialArgs An array of arguments to be partially applied. + * @param {Object} [idicator] Used to indicate binding by key or partially + * applying arguments from the right. + * @returns {Function} Returns the new bound function. + */ + function createBound(func, thisArg, partialArgs, indicator) { + var isFunc = isFunction(func), + isPartial = !partialArgs, + key = thisArg; + + // juggle arguments + if (isPartial) { + var rightIndicator = indicator; + partialArgs = thisArg; + } + else if (!isFunc) { + if (!indicator) { + throw new TypeError; + } + thisArg = func; + } + + function bound() { + // `Function#bind` spec + // http://es5.github.com/#x15.3.4.5 + var args = arguments, + thisBinding = isPartial ? this : thisArg; + + if (!isFunc) { + func = thisArg[key]; + } + if (partialArgs.length) { + args = args.length + ? (args = nativeSlice.call(args), rightIndicator ? args.concat(partialArgs) : partialArgs.concat(args)) + : partialArgs; + } + if (this instanceof bound) { + // ensure `new bound` is an instance of `func` + thisBinding = createObject(func.prototype); + + // mimic the constructor's `return` behavior + // http://es5.github.com/#x13.2.2 + var result = func.apply(thisBinding, args); + return isObject(result) ? result : thisBinding; + } + return func.apply(thisBinding, args); + } + return bound; + } + + /** + * Creates a new object with the specified `prototype`. + * + * @private + * @param {Object} prototype The prototype object. + * @returns {Object} Returns the new object. + */ + function createObject(prototype) { + return isObject(prototype) ? nativeCreate(prototype) : {}; + } + + /** + * Used by `escape` to convert characters to HTML entities. + * + * @private + * @param {String} match The matched character to escape. + * @returns {String} Returns the escaped character. + */ + function escapeHtmlChar(match) { + return htmlEscapes[match]; + } + + /** + * Gets the appropriate "indexOf" function. If the `_.indexOf` method is + * customized, this method returns the custom method, otherwise it returns + * the `basicIndexOf` function. + * + * @private + * @returns {Function} Returns the "indexOf" function. + */ + function getIndexOf(array, value, fromIndex) { + var result = (result = lodash.indexOf) === indexOf ? basicIndexOf : result; + return result; + } + + /** + * Creates a function that juggles arguments, allowing argument overloading + * for `_.flatten` and `_.uniq`, before passing them to the given `func`. + * + * @private + * @param {Function} func The function to wrap. + * @returns {Function} Returns the new function. + */ + function overloadWrapper(func) { + return function(array, flag, callback, thisArg) { + // juggle arguments + if (typeof flag != 'boolean' && flag != null) { + thisArg = callback; + callback = !(thisArg && thisArg[flag] === array) ? flag : undefined; + flag = false; + } + if (callback != null) { + callback = lodash.createCallback(callback, thisArg); + } + return func(array, flag, callback, thisArg); + }; + } + + /** + * A fallback implementation of `isPlainObject` which checks if a given `value` + * is an object created by the `Object` constructor, assuming objects created + * by the `Object` constructor have no inherited enumerable properties and that + * there are no `Object.prototype` extensions. + * + * @private + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if `value` is a plain object, else `false`. + */ + function shimIsPlainObject(value) { + var ctor, + result; + + // avoid non Object objects, `arguments` objects, and DOM elements + if (!(value && toString.call(value) == objectClass) || + (ctor = value.constructor, isFunction(ctor) && !(ctor instanceof ctor))) { + return false; + } + // In most environments an object's own properties are iterated before + // its inherited properties. If the last iterated property is an object's + // own property then there are no inherited enumerable properties. + forIn(value, function(value, key) { + result = key; + }); + return result === undefined || hasOwnProperty.call(value, result); + } + + /** + * Used by `unescape` to convert HTML entities to characters. + * + * @private + * @param {String} match The matched character to unescape. + * @returns {String} Returns the unescaped character. + */ + function unescapeHtmlChar(match) { + return htmlUnescapes[match]; + } + + /*--------------------------------------------------------------------------*/ + + /** + * Checks if `value` is an `arguments` object. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is an `arguments` object, else `false`. + * @example + * + * (function() { return _.isArguments(arguments); })(1, 2, 3); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + function isArguments(value) { + return toString.call(value) == argsClass; + } + + /** + * Checks if `value` is an array. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is an array, else `false`. + * @example + * + * (function() { return _.isArray(arguments); })(); + * // => false + * + * _.isArray([1, 2, 3]); + * // => true + */ + var isArray = nativeIsArray; + + /** + * A fallback implementation of `Object.keys` which produces an array of the + * given object's own enumerable property names. + * + * @private + * @type Function + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names. + */ + var shimKeys = function (object) { + var index, iterable = object, result = []; + if (!iterable) return result; + if (!(objectTypes[typeof object])) return result; + for (index in iterable) { + if (hasOwnProperty.call(iterable, index)) { + result.push(index); + } + } + return result + }; + + /** + * Creates an array composed of the own enumerable property names of `object`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names. + * @example + * + * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); + * // => ['one', 'two', 'three'] (order is not guaranteed) + */ + var keys = !nativeKeys ? shimKeys : function(object) { + if (!isObject(object)) { + return []; + } + return nativeKeys(object); + }; + + /** + * Used to convert characters to HTML entities: + * + * Though the `>` character is escaped for symmetry, characters like `>` and `/` + * don't require escaping in HTML and have no special meaning unless they're part + * of a tag or an unquoted attribute value. + * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact") + */ + var htmlEscapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + + /** Used to convert HTML entities to characters */ + var htmlUnescapes = invert(htmlEscapes); + + /*--------------------------------------------------------------------------*/ + + /** + * Assigns own enumerable properties of source object(s) to the destination + * object. Subsequent sources will overwrite property assignments of previous + * sources. If a `callback` function is passed, it will be executed to produce + * the assigned values. The `callback` is bound to `thisArg` and invoked with + * two arguments; (objectValue, sourceValue). + * + * @static + * @memberOf _ + * @type Function + * @alias extend + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @param {Function} [callback] The function to customize assigning values. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the destination object. + * @example + * + * _.assign({ 'name': 'moe' }, { 'age': 40 }); + * // => { 'name': 'moe', 'age': 40 } + * + * var defaults = _.partialRight(_.assign, function(a, b) { + * return typeof a == 'undefined' ? b : a; + * }); + * + * var food = { 'name': 'apple' }; + * defaults(food, { 'name': 'banana', 'type': 'fruit' }); + * // => { 'name': 'apple', 'type': 'fruit' } + */ + var assign = function (object, source, guard) { + var index, iterable = object, result = iterable; + if (!iterable) return result; + var args = arguments, + argsIndex = 0, + argsLength = typeof guard == 'number' ? 2 : args.length; + if (argsLength > 3 && typeof args[argsLength - 2] == 'function') { + var callback = lodash.createCallback(args[--argsLength - 1], args[argsLength--], 2); + } else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') { + callback = args[--argsLength]; + } + while (++argsIndex < argsLength) { + iterable = args[argsIndex]; + if (iterable && objectTypes[typeof iterable]) { + var ownIndex = -1, + ownProps = objectTypes[typeof iterable] && keys(iterable), + length = ownProps ? ownProps.length : 0; + + while (++ownIndex < length) { + index = ownProps[ownIndex]; + result[index] = callback ? callback(result[index], iterable[index]) : iterable[index]; + } + } + } + return result + }; + + /** + * Creates a clone of `value`. If `deep` is `true`, nested objects will also + * be cloned, otherwise they will be assigned by reference. If a `callback` + * function is passed, it will be executed to produce the cloned values. If + * `callback` returns `undefined`, cloning will be handled by the method instead. + * The `callback` is bound to `thisArg` and invoked with one argument; (value). + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to clone. + * @param {Boolean} [deep=false] A flag to indicate a deep clone. + * @param {Function} [callback] The function to customize cloning values. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @param- {Array} [stackA=[]] Tracks traversed source objects. + * @param- {Array} [stackB=[]] Associates clones with source counterparts. + * @returns {Mixed} Returns the cloned `value`. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * var shallow = _.clone(stooges); + * shallow[0] === stooges[0]; + * // => true + * + * var deep = _.clone(stooges, true); + * deep[0] === stooges[0]; + * // => false + * + * _.mixin({ + * 'clone': _.partialRight(_.clone, function(value) { + * return _.isElement(value) ? value.cloneNode(false) : undefined; + * }) + * }); + * + * var clone = _.clone(document.body); + * clone.childNodes.length; + * // => 0 + */ + function clone(value, deep, callback, thisArg, stackA, stackB) { + var result = value; + + // allows working with "Collections" methods without using their `callback` + // argument, `index|key`, for this method's `callback` + if (typeof deep != 'boolean' && deep != null) { + thisArg = callback; + callback = deep; + deep = false; + } + if (typeof callback == 'function') { + callback = (typeof thisArg == 'undefined') + ? callback + : lodash.createCallback(callback, thisArg, 1); + + result = callback(result); + if (typeof result != 'undefined') { + return result; + } + result = value; + } + // inspect [[Class]] + var isObj = isObject(result); + if (isObj) { + var className = toString.call(result); + if (!cloneableClasses[className]) { + return result; + } + var isArr = isArray(result); + } + // shallow clone + if (!isObj || !deep) { + return isObj + ? (isArr ? slice(result) : assign({}, result)) + : result; + } + var ctor = ctorByClass[className]; + switch (className) { + case boolClass: + case dateClass: + return new ctor(+result); + + case numberClass: + case stringClass: + return new ctor(result); + + case regexpClass: + return ctor(result.source, reFlags.exec(result)); + } + // check for circular references and return corresponding clone + var initedStack = !stackA; + stackA || (stackA = getArray()); + stackB || (stackB = getArray()); + + var length = stackA.length; + while (length--) { + if (stackA[length] == value) { + return stackB[length]; + } + } + // init cloned object + result = isArr ? ctor(result.length) : {}; + + // add array properties assigned by `RegExp#exec` + if (isArr) { + if (hasOwnProperty.call(value, 'index')) { + result.index = value.index; + } + if (hasOwnProperty.call(value, 'input')) { + result.input = value.input; + } + } + // add the source value to the stack of traversed objects + // and associate it with its clone + stackA.push(value); + stackB.push(result); + + // recursively populate clone (susceptible to call stack limits) + (isArr ? forEach : forOwn)(value, function(objValue, key) { + result[key] = clone(objValue, deep, callback, undefined, stackA, stackB); + }); + + if (initedStack) { + releaseArray(stackA); + releaseArray(stackB); + } + return result; + } + + /** + * Creates a deep clone of `value`. If a `callback` function is passed, + * it will be executed to produce the cloned values. If `callback` returns + * `undefined`, cloning will be handled by the method instead. The `callback` + * is bound to `thisArg` and invoked with one argument; (value). + * + * Note: This method is loosely based on the structured clone algorithm. Functions + * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and + * objects created by constructors other than `Object` are cloned to plain `Object` objects. + * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to deep clone. + * @param {Function} [callback] The function to customize cloning values. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the deep cloned `value`. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * var deep = _.cloneDeep(stooges); + * deep[0] === stooges[0]; + * // => false + * + * var view = { + * 'label': 'docs', + * 'node': element + * }; + * + * var clone = _.cloneDeep(view, function(value) { + * return _.isElement(value) ? value.cloneNode(true) : undefined; + * }); + * + * clone.node == view.node; + * // => false + */ + function cloneDeep(value, callback, thisArg) { + return clone(value, true, callback, thisArg); + } + + /** + * Assigns own enumerable properties of source object(s) to the destination + * object for all destination properties that resolve to `undefined`. Once a + * property is set, additional defaults of the same property will be ignored. + * + * @static + * @memberOf _ + * @type Function + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @param- {Object} [guard] Allows working with `_.reduce` without using its + * callback's `key` and `object` arguments as sources. + * @returns {Object} Returns the destination object. + * @example + * + * var food = { 'name': 'apple' }; + * _.defaults(food, { 'name': 'banana', 'type': 'fruit' }); + * // => { 'name': 'apple', 'type': 'fruit' } + */ + var defaults = function (object, source, guard) { + var index, iterable = object, result = iterable; + if (!iterable) return result; + var args = arguments, + argsIndex = 0, + argsLength = typeof guard == 'number' ? 2 : args.length; + while (++argsIndex < argsLength) { + iterable = args[argsIndex]; + if (iterable && objectTypes[typeof iterable]) { + var ownIndex = -1, + ownProps = objectTypes[typeof iterable] && keys(iterable), + length = ownProps ? ownProps.length : 0; + + while (++ownIndex < length) { + index = ownProps[ownIndex]; + if (typeof result[index] == 'undefined') result[index] = iterable[index]; + } + } + } + return result + }; + + /** + * This method is similar to `_.find`, except that it returns the key of the + * element that passes the callback check, instead of the element itself. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to search. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the key of the found element, else `undefined`. + * @example + * + * _.findKey({ 'a': 1, 'b': 2, 'c': 3, 'd': 4 }, function(num) { + * return num % 2 == 0; + * }); + * // => 'b' + */ + function findKey(object, callback, thisArg) { + var result; + callback = lodash.createCallback(callback, thisArg); + forOwn(object, function(value, key, object) { + if (callback(value, key, object)) { + result = key; + return false; + } + }); + return result; + } + + /** + * Iterates over `object`'s own and inherited enumerable properties, executing + * the `callback` for each property. The `callback` is bound to `thisArg` and + * invoked with three arguments; (value, key, object). Callbacks may exit iteration + * early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @type Function + * @category Objects + * @param {Object} object The object to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns `object`. + * @example + * + * function Dog(name) { + * this.name = name; + * } + * + * Dog.prototype.bark = function() { + * alert('Woof, woof!'); + * }; + * + * _.forIn(new Dog('Dagny'), function(value, key) { + * alert(key); + * }); + * // => alerts 'name' and 'bark' (order is not guaranteed) + */ + var forIn = function (collection, callback, thisArg) { + var index, iterable = collection, result = iterable; + if (!iterable) return result; + if (!objectTypes[typeof iterable]) return result; + callback = callback && typeof thisArg == 'undefined' ? callback : lodash.createCallback(callback, thisArg); + for (index in iterable) { + if (callback(iterable[index], index, collection) === false) return result; + } + return result + }; + + /** + * Iterates over an object's own enumerable properties, executing the `callback` + * for each property. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, key, object). Callbacks may exit iteration early by explicitly + * returning `false`. + * + * @static + * @memberOf _ + * @type Function + * @category Objects + * @param {Object} object The object to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns `object`. + * @example + * + * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { + * alert(key); + * }); + * // => alerts '0', '1', and 'length' (order is not guaranteed) + */ + var forOwn = function (collection, callback, thisArg) { + var index, iterable = collection, result = iterable; + if (!iterable) return result; + if (!objectTypes[typeof iterable]) return result; + callback = callback && typeof thisArg == 'undefined' ? callback : lodash.createCallback(callback, thisArg); + var ownIndex = -1, + ownProps = objectTypes[typeof iterable] && keys(iterable), + length = ownProps ? ownProps.length : 0; + + while (++ownIndex < length) { + index = ownProps[ownIndex]; + if (callback(iterable[index], index, collection) === false) return result; + } + return result + }; + + /** + * Creates a sorted array of all enumerable properties, own and inherited, + * of `object` that have function values. + * + * @static + * @memberOf _ + * @alias methods + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property names that have function values. + * @example + * + * _.functions(_); + * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] + */ + function functions(object) { + var result = []; + forIn(object, function(value, key) { + if (isFunction(value)) { + result.push(key); + } + }); + return result.sort(); + } + + /** + * Checks if the specified object `property` exists and is a direct property, + * instead of an inherited property. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to check. + * @param {String} property The property to check for. + * @returns {Boolean} Returns `true` if key is a direct property, else `false`. + * @example + * + * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b'); + * // => true + */ + function has(object, property) { + return object ? hasOwnProperty.call(object, property) : false; + } + + /** + * Creates an object composed of the inverted keys and values of the given `object`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to invert. + * @returns {Object} Returns the created inverted object. + * @example + * + * _.invert({ 'first': 'moe', 'second': 'larry' }); + * // => { 'moe': 'first', 'larry': 'second' } + */ + function invert(object) { + var index = -1, + props = keys(object), + length = props.length, + result = {}; + + while (++index < length) { + var key = props[index]; + result[object[key]] = key; + } + return result; + } + + /** + * Checks if `value` is a boolean value. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a boolean value, else `false`. + * @example + * + * _.isBoolean(null); + * // => false + */ + function isBoolean(value) { + return value === true || value === false || toString.call(value) == boolClass; + } + + /** + * Checks if `value` is a date. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a date, else `false`. + * @example + * + * _.isDate(new Date); + * // => true + */ + function isDate(value) { + return value ? (typeof value == 'object' && toString.call(value) == dateClass) : false; + } + + /** + * Checks if `value` is a DOM element. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a DOM element, else `false`. + * @example + * + * _.isElement(document.body); + * // => true + */ + function isElement(value) { + return value ? value.nodeType === 1 : false; + } + + /** + * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a + * length of `0` and objects with no own enumerable properties are considered + * "empty". + * + * @static + * @memberOf _ + * @category Objects + * @param {Array|Object|String} value The value to inspect. + * @returns {Boolean} Returns `true`, if the `value` is empty, else `false`. + * @example + * + * _.isEmpty([1, 2, 3]); + * // => false + * + * _.isEmpty({}); + * // => true + * + * _.isEmpty(''); + * // => true + */ + function isEmpty(value) { + var result = true; + if (!value) { + return result; + } + var className = toString.call(value), + length = value.length; + + if ((className == arrayClass || className == stringClass || className == argsClass ) || + (className == objectClass && typeof length == 'number' && isFunction(value.splice))) { + return !length; + } + forOwn(value, function() { + return (result = false); + }); + return result; + } + + /** + * Performs a deep comparison between two values to determine if they are + * equivalent to each other. If `callback` is passed, it will be executed to + * compare values. If `callback` returns `undefined`, comparisons will be handled + * by the method instead. The `callback` is bound to `thisArg` and invoked with + * two arguments; (a, b). + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} a The value to compare. + * @param {Mixed} b The other value to compare. + * @param {Function} [callback] The function to customize comparing values. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @param- {Array} [stackA=[]] Tracks traversed `a` objects. + * @param- {Array} [stackB=[]] Tracks traversed `b` objects. + * @returns {Boolean} Returns `true`, if the values are equivalent, else `false`. + * @example + * + * var moe = { 'name': 'moe', 'age': 40 }; + * var copy = { 'name': 'moe', 'age': 40 }; + * + * moe == copy; + * // => false + * + * _.isEqual(moe, copy); + * // => true + * + * var words = ['hello', 'goodbye']; + * var otherWords = ['hi', 'goodbye']; + * + * _.isEqual(words, otherWords, function(a, b) { + * var reGreet = /^(?:hello|hi)$/i, + * aGreet = _.isString(a) && reGreet.test(a), + * bGreet = _.isString(b) && reGreet.test(b); + * + * return (aGreet || bGreet) ? (aGreet == bGreet) : undefined; + * }); + * // => true + */ + function isEqual(a, b, callback, thisArg, stackA, stackB) { + // used to indicate that when comparing objects, `a` has at least the properties of `b` + var whereIndicator = callback === indicatorObject; + if (typeof callback == 'function' && !whereIndicator) { + callback = lodash.createCallback(callback, thisArg, 2); + var result = callback(a, b); + if (typeof result != 'undefined') { + return !!result; + } + } + // exit early for identical values + if (a === b) { + // treat `+0` vs. `-0` as not equal + return a !== 0 || (1 / a == 1 / b); + } + var type = typeof a, + otherType = typeof b; + + // exit early for unlike primitive values + if (a === a && + (!a || (type != 'function' && type != 'object')) && + (!b || (otherType != 'function' && otherType != 'object'))) { + return false; + } + // exit early for `null` and `undefined`, avoiding ES3's Function#call behavior + // http://es5.github.com/#x15.3.4.4 + if (a == null || b == null) { + return a === b; + } + // compare [[Class]] names + var className = toString.call(a), + otherClass = toString.call(b); + + if (className == argsClass) { + className = objectClass; + } + if (otherClass == argsClass) { + otherClass = objectClass; + } + if (className != otherClass) { + return false; + } + switch (className) { + case boolClass: + case dateClass: + // coerce dates and booleans to numbers, dates to milliseconds and booleans + // to `1` or `0`, treating invalid dates coerced to `NaN` as not equal + return +a == +b; + + case numberClass: + // treat `NaN` vs. `NaN` as equal + return (a != +a) + ? b != +b + // but treat `+0` vs. `-0` as not equal + : (a == 0 ? (1 / a == 1 / b) : a == +b); + + case regexpClass: + case stringClass: + // coerce regexes to strings (http://es5.github.com/#x15.10.6.4) + // treat string primitives and their corresponding object instances as equal + return a == String(b); + } + var isArr = className == arrayClass; + if (!isArr) { + // unwrap any `lodash` wrapped values + if (hasOwnProperty.call(a, '__wrapped__ ') || hasOwnProperty.call(b, '__wrapped__')) { + return isEqual(a.__wrapped__ || a, b.__wrapped__ || b, callback, thisArg, stackA, stackB); + } + // exit for functions and DOM nodes + if (className != objectClass) { + return false; + } + // in older versions of Opera, `arguments` objects have `Array` constructors + var ctorA = a.constructor, + ctorB = b.constructor; + + // non `Object` object instances with different constructors are not equal + if (ctorA != ctorB && !( + isFunction(ctorA) && ctorA instanceof ctorA && + isFunction(ctorB) && ctorB instanceof ctorB + )) { + return false; + } + } + // assume cyclic structures are equal + // the algorithm for detecting cyclic structures is adapted from ES 5.1 + // section 15.12.3, abstract operation `JO` (http://es5.github.com/#x15.12.3) + var initedStack = !stackA; + stackA || (stackA = getArray()); + stackB || (stackB = getArray()); + + var length = stackA.length; + while (length--) { + if (stackA[length] == a) { + return stackB[length] == b; + } + } + var size = 0; + result = true; + + // add `a` and `b` to the stack of traversed objects + stackA.push(a); + stackB.push(b); + + // recursively compare objects and arrays (susceptible to call stack limits) + if (isArr) { + length = a.length; + size = b.length; + + // compare lengths to determine if a deep comparison is necessary + result = size == a.length; + if (!result && !whereIndicator) { + return result; + } + // deep compare the contents, ignoring non-numeric properties + while (size--) { + var index = length, + value = b[size]; + + if (whereIndicator) { + while (index--) { + if ((result = isEqual(a[index], value, callback, thisArg, stackA, stackB))) { + break; + } + } + } else if (!(result = isEqual(a[size], value, callback, thisArg, stackA, stackB))) { + break; + } + } + return result; + } + // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys` + // which, in this case, is more costly + forIn(b, function(value, key, b) { + if (hasOwnProperty.call(b, key)) { + // count the number of properties. + size++; + // deep compare each property value. + return (result = hasOwnProperty.call(a, key) && isEqual(a[key], value, callback, thisArg, stackA, stackB)); + } + }); + + if (result && !whereIndicator) { + // ensure both objects have the same number of properties + forIn(a, function(value, key, a) { + if (hasOwnProperty.call(a, key)) { + // `size` will be `-1` if `a` has more properties than `b` + return (result = --size > -1); + } + }); + } + if (initedStack) { + releaseArray(stackA); + releaseArray(stackB); + } + return result; + } + + /** + * Checks if `value` is, or can be coerced to, a finite number. + * + * Note: This is not the same as native `isFinite`, which will return true for + * booleans and empty strings. See http://es5.github.com/#x15.1.2.5. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is finite, else `false`. + * @example + * + * _.isFinite(-101); + * // => true + * + * _.isFinite('10'); + * // => true + * + * _.isFinite(true); + * // => false + * + * _.isFinite(''); + * // => false + * + * _.isFinite(Infinity); + * // => false + */ + function isFinite(value) { + return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value)); + } + + /** + * Checks if `value` is a function. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + */ + function isFunction(value) { + return typeof value == 'function'; + } + + /** + * Checks if `value` is the language type of Object. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false + */ + function isObject(value) { + // check if the value is the ECMAScript language type of Object + // http://es5.github.com/#x8 + // and avoid a V8 bug + // http://code.google.com/p/v8/issues/detail?id=2291 + return !!(value && objectTypes[typeof value]); + } + + /** + * Checks if `value` is `NaN`. + * + * Note: This is not the same as native `isNaN`, which will return `true` for + * `undefined` and other values. See http://es5.github.com/#x15.1.2.4. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is `NaN`, else `false`. + * @example + * + * _.isNaN(NaN); + * // => true + * + * _.isNaN(new Number(NaN)); + * // => true + * + * isNaN(undefined); + * // => true + * + * _.isNaN(undefined); + * // => false + */ + function isNaN(value) { + // `NaN` as a primitive is the only value that is not equal to itself + // (perform the [[Class]] check first to avoid errors with some host objects in IE) + return isNumber(value) && value != +value + } + + /** + * Checks if `value` is `null`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is `null`, else `false`. + * @example + * + * _.isNull(null); + * // => true + * + * _.isNull(undefined); + * // => false + */ + function isNull(value) { + return value === null; + } + + /** + * Checks if `value` is a number. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a number, else `false`. + * @example + * + * _.isNumber(8.4 * 5); + * // => true + */ + function isNumber(value) { + return typeof value == 'number' || toString.call(value) == numberClass; + } + + /** + * Checks if a given `value` is an object created by the `Object` constructor. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if `value` is a plain object, else `false`. + * @example + * + * function Stooge(name, age) { + * this.name = name; + * this.age = age; + * } + * + * _.isPlainObject(new Stooge('moe', 40)); + * // => false + * + * _.isPlainObject([1, 2, 3]); + * // => false + * + * _.isPlainObject({ 'name': 'moe', 'age': 40 }); + * // => true + */ + var isPlainObject = function(value) { + if (!(value && toString.call(value) == objectClass)) { + return false; + } + var valueOf = value.valueOf, + objProto = typeof valueOf == 'function' && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto); + + return objProto + ? (value == objProto || getPrototypeOf(value) == objProto) + : shimIsPlainObject(value); + }; + + /** + * Checks if `value` is a regular expression. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a regular expression, else `false`. + * @example + * + * _.isRegExp(/moe/); + * // => true + */ + function isRegExp(value) { + return value ? (typeof value == 'object' && toString.call(value) == regexpClass) : false; + } + + /** + * Checks if `value` is a string. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is a string, else `false`. + * @example + * + * _.isString('moe'); + * // => true + */ + function isString(value) { + return typeof value == 'string' || toString.call(value) == stringClass; + } + + /** + * Checks if `value` is `undefined`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is `undefined`, else `false`. + * @example + * + * _.isUndefined(void 0); + * // => true + */ + function isUndefined(value) { + return typeof value == 'undefined'; + } + + /** + * Recursively merges own enumerable properties of the source object(s), that + * don't resolve to `undefined`, into the destination object. Subsequent sources + * will overwrite property assignments of previous sources. If a `callback` function + * is passed, it will be executed to produce the merged values of the destination + * and source properties. If `callback` returns `undefined`, merging will be + * handled by the method instead. The `callback` is bound to `thisArg` and + * invoked with two arguments; (objectValue, sourceValue). + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @param {Function} [callback] The function to customize merging properties. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @param- {Object} [deepIndicator] Indicates that `stackA` and `stackB` are + * arrays of traversed objects, instead of source objects. + * @param- {Array} [stackA=[]] Tracks traversed source objects. + * @param- {Array} [stackB=[]] Associates values with source counterparts. + * @returns {Object} Returns the destination object. + * @example + * + * var names = { + * 'stooges': [ + * { 'name': 'moe' }, + * { 'name': 'larry' } + * ] + * }; + * + * var ages = { + * 'stooges': [ + * { 'age': 40 }, + * { 'age': 50 } + * ] + * }; + * + * _.merge(names, ages); + * // => { 'stooges': [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] } + * + * var food = { + * 'fruits': ['apple'], + * 'vegetables': ['beet'] + * }; + * + * var otherFood = { + * 'fruits': ['banana'], + * 'vegetables': ['carrot'] + * }; + * + * _.merge(food, otherFood, function(a, b) { + * return _.isArray(a) ? a.concat(b) : undefined; + * }); + * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] } + */ + function merge(object, source, deepIndicator) { + var args = arguments, + index = 0, + length = 2; + + if (!isObject(object)) { + return object; + } + if (deepIndicator === indicatorObject) { + var callback = args[3], + stackA = args[4], + stackB = args[5]; + } else { + var initedStack = true; + stackA = getArray(); + stackB = getArray(); + + // allows working with `_.reduce` and `_.reduceRight` without + // using their `callback` arguments, `index|key` and `collection` + if (typeof deepIndicator != 'number') { + length = args.length; + } + if (length > 3 && typeof args[length - 2] == 'function') { + callback = lodash.createCallback(args[--length - 1], args[length--], 2); + } else if (length > 2 && typeof args[length - 1] == 'function') { + callback = args[--length]; + } + } + while (++index < length) { + (isArray(args[index]) ? forEach : forOwn)(args[index], function(source, key) { + var found, + isArr, + result = source, + value = object[key]; + + if (source && ((isArr = isArray(source)) || isPlainObject(source))) { + // avoid merging previously merged cyclic sources + var stackLength = stackA.length; + while (stackLength--) { + if ((found = stackA[stackLength] == source)) { + value = stackB[stackLength]; + break; + } + } + if (!found) { + var isShallow; + if (callback) { + result = callback(value, source); + if ((isShallow = typeof result != 'undefined')) { + value = result; + } + } + if (!isShallow) { + value = isArr + ? (isArray(value) ? value : []) + : (isPlainObject(value) ? value : {}); + } + // add `source` and associated `value` to the stack of traversed objects + stackA.push(source); + stackB.push(value); + + // recursively merge objects and arrays (susceptible to call stack limits) + if (!isShallow) { + value = merge(value, source, indicatorObject, callback, stackA, stackB); + } + } + } + else { + if (callback) { + result = callback(value, source); + if (typeof result == 'undefined') { + result = source; + } + } + if (typeof result != 'undefined') { + value = result; + } + } + object[key] = value; + }); + } + + if (initedStack) { + releaseArray(stackA); + releaseArray(stackB); + } + return object; + } + + /** + * Creates a shallow clone of `object` excluding the specified properties. + * Property names may be specified as individual arguments or as arrays of + * property names. If a `callback` function is passed, it will be executed + * for each property in the `object`, omitting the properties `callback` + * returns truthy for. The `callback` is bound to `thisArg` and invoked + * with three arguments; (value, key, object). + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The source object. + * @param {Function|String} callback|[prop1, prop2, ...] The properties to omit + * or the function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns an object without the omitted properties. + * @example + * + * _.omit({ 'name': 'moe', 'age': 40 }, 'age'); + * // => { 'name': 'moe' } + * + * _.omit({ 'name': 'moe', 'age': 40 }, function(value) { + * return typeof value == 'number'; + * }); + * // => { 'name': 'moe' } + */ + function omit(object, callback, thisArg) { + var indexOf = getIndexOf(), + isFunc = typeof callback == 'function', + result = {}; + + if (isFunc) { + callback = lodash.createCallback(callback, thisArg); + } else { + var props = concat.apply(arrayRef, nativeSlice.call(arguments, 1)); + } + forIn(object, function(value, key, object) { + if (isFunc + ? !callback(value, key, object) + : indexOf(props, key) < 0 + ) { + result[key] = value; + } + }); + return result; + } + + /** + * Creates a two dimensional array of the given object's key-value pairs, + * i.e. `[[key1, value1], [key2, value2]]`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns new array of key-value pairs. + * @example + * + * _.pairs({ 'moe': 30, 'larry': 40 }); + * // => [['moe', 30], ['larry', 40]] (order is not guaranteed) + */ + function pairs(object) { + var index = -1, + props = keys(object), + length = props.length, + result = Array(length); + + while (++index < length) { + var key = props[index]; + result[index] = [key, object[key]]; + } + return result; + } + + /** + * Creates a shallow clone of `object` composed of the specified properties. + * Property names may be specified as individual arguments or as arrays of property + * names. If `callback` is passed, it will be executed for each property in the + * `object`, picking the properties `callback` returns truthy for. The `callback` + * is bound to `thisArg` and invoked with three arguments; (value, key, object). + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The source object. + * @param {Array|Function|String} callback|[prop1, prop2, ...] The function called + * per iteration or properties to pick, either as individual arguments or arrays. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns an object composed of the picked properties. + * @example + * + * _.pick({ 'name': 'moe', '_userid': 'moe1' }, 'name'); + * // => { 'name': 'moe' } + * + * _.pick({ 'name': 'moe', '_userid': 'moe1' }, function(value, key) { + * return key.charAt(0) != '_'; + * }); + * // => { 'name': 'moe' } + */ + function pick(object, callback, thisArg) { + var result = {}; + if (typeof callback != 'function') { + var index = -1, + props = concat.apply(arrayRef, nativeSlice.call(arguments, 1)), + length = isObject(object) ? props.length : 0; + + while (++index < length) { + var key = props[index]; + if (key in object) { + result[key] = object[key]; + } + } + } else { + callback = lodash.createCallback(callback, thisArg); + forIn(object, function(value, key, object) { + if (callback(value, key, object)) { + result[key] = value; + } + }); + } + return result; + } + + /** + * An alternative to `_.reduce`, this method transforms an `object` to a new + * `accumulator` object which is the result of running each of its elements + * through the `callback`, with each `callback` execution potentially mutating + * the `accumulator` object. The `callback` is bound to `thisArg` and invoked + * with four arguments; (accumulator, value, key, object). Callbacks may exit + * iteration early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [accumulator] The custom accumulator value. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the accumulated value. + * @example + * + * var squares = _.transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(result, num) { + * num *= num; + * if (num % 2) { + * return result.push(num) < 3; + * } + * }); + * // => [1, 9, 25] + * + * var mapped = _.transform({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) { + * result[key] = num * 3; + * }); + * // => { 'a': 3, 'b': 6, 'c': 9 } + */ + function transform(object, callback, accumulator, thisArg) { + var isArr = isArray(object); + callback = lodash.createCallback(callback, thisArg, 4); + + if (accumulator == null) { + if (isArr) { + accumulator = []; + } else { + var ctor = object && object.constructor, + proto = ctor && ctor.prototype; + + accumulator = createObject(proto); + } + } + (isArr ? forEach : forOwn)(object, function(value, index, object) { + return callback(accumulator, value, index, object); + }); + return accumulator; + } + + /** + * Creates an array composed of the own enumerable property values of `object`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns a new array of property values. + * @example + * + * _.values({ 'one': 1, 'two': 2, 'three': 3 }); + * // => [1, 2, 3] (order is not guaranteed) + */ + function values(object) { + var index = -1, + props = keys(object), + length = props.length, + result = Array(length); + + while (++index < length) { + result[index] = object[props[index]]; + } + return result; + } + + /*--------------------------------------------------------------------------*/ + + /** + * Creates an array of elements from the specified indexes, or keys, of the + * `collection`. Indexes may be specified as individual arguments or as arrays + * of indexes. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Array|Number|String} [index1, index2, ...] The indexes of + * `collection` to retrieve, either as individual arguments or arrays. + * @returns {Array} Returns a new array of elements corresponding to the + * provided indexes. + * @example + * + * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]); + * // => ['a', 'c', 'e'] + * + * _.at(['moe', 'larry', 'curly'], 0, 2); + * // => ['moe', 'curly'] + */ + function at(collection) { + var index = -1, + props = concat.apply(arrayRef, nativeSlice.call(arguments, 1)), + length = props.length, + result = Array(length); + + while(++index < length) { + result[index] = collection[props[index]]; + } + return result; + } + + /** + * Checks if a given `target` element is present in a `collection` using strict + * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used + * as the offset from the end of the collection. + * + * @static + * @memberOf _ + * @alias include + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Mixed} target The value to check for. + * @param {Number} [fromIndex=0] The index to search from. + * @returns {Boolean} Returns `true` if the `target` element is found, else `false`. + * @example + * + * _.contains([1, 2, 3], 1); + * // => true + * + * _.contains([1, 2, 3], 1, 2); + * // => false + * + * _.contains({ 'name': 'moe', 'age': 40 }, 'moe'); + * // => true + * + * _.contains('curly', 'ur'); + * // => true + */ + function contains(collection, target, fromIndex) { + var index = -1, + indexOf = getIndexOf(), + length = collection ? collection.length : 0, + result = false; + + fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0; + if (length && typeof length == 'number') { + result = (isString(collection) + ? collection.indexOf(target, fromIndex) + : indexOf(collection, target, fromIndex) + ) > -1; + } else { + forOwn(collection, function(value) { + if (++index >= fromIndex) { + return !(result = value === target); + } + }); + } + return result; + } + + /** + * Creates an object composed of keys returned from running each element of the + * `collection` through the given `callback`. The corresponding value of each key + * is the number of times the key was returned by the `callback`. The `callback` + * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the composed aggregate object. + * @example + * + * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); }); + * // => { '4': 1, '6': 2 } + * + * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math); + * // => { '4': 1, '6': 2 } + * + * _.countBy(['one', 'two', 'three'], 'length'); + * // => { '3': 2, '5': 1 } + */ + function countBy(collection, callback, thisArg) { + var result = {}; + callback = lodash.createCallback(callback, thisArg); + + forEach(collection, function(value, key, collection) { + key = String(callback(value, key, collection)); + (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1); + }); + return result; + } + + /** + * Checks if the `callback` returns a truthy value for **all** elements of a + * `collection`. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias all + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Boolean} Returns `true` if all elements pass the callback check, + * else `false`. + * @example + * + * _.every([true, 1, null, 'yes'], Boolean); + * // => false + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * // using "_.pluck" callback shorthand + * _.every(stooges, 'age'); + * // => true + * + * // using "_.where" callback shorthand + * _.every(stooges, { 'age': 50 }); + * // => false + */ + function every(collection, callback, thisArg) { + var result = true; + callback = lodash.createCallback(callback, thisArg); + + var index = -1, + length = collection ? collection.length : 0; + + if (typeof length == 'number') { + while (++index < length) { + if (!(result = !!callback(collection[index], index, collection))) { + break; + } + } + } else { + forOwn(collection, function(value, index, collection) { + return (result = !!callback(value, index, collection)); + }); + } + return result; + } + + /** + * Examines each element in a `collection`, returning an array of all elements + * the `callback` returns truthy for. The `callback` is bound to `thisArg` and + * invoked with three arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias select + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of elements that passed the callback check. + * @example + * + * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => [2, 4, 6] + * + * var food = [ + * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, + * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } + * ]; + * + * // using "_.pluck" callback shorthand + * _.filter(food, 'organic'); + * // => [{ 'name': 'carrot', 'organic': true, 'type': 'vegetable' }] + * + * // using "_.where" callback shorthand + * _.filter(food, { 'type': 'fruit' }); + * // => [{ 'name': 'apple', 'organic': false, 'type': 'fruit' }] + */ + function filter(collection, callback, thisArg) { + var result = []; + callback = lodash.createCallback(callback, thisArg); + + var index = -1, + length = collection ? collection.length : 0; + + if (typeof length == 'number') { + while (++index < length) { + var value = collection[index]; + if (callback(value, index, collection)) { + result.push(value); + } + } + } else { + forOwn(collection, function(value, index, collection) { + if (callback(value, index, collection)) { + result.push(value); + } + }); + } + return result; + } + + /** + * Examines each element in a `collection`, returning the first that the `callback` + * returns truthy for. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias detect, findWhere + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the found element, else `undefined`. + * @example + * + * _.find([1, 2, 3, 4], function(num) { + * return num % 2 == 0; + * }); + * // => 2 + * + * var food = [ + * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, + * { 'name': 'banana', 'organic': true, 'type': 'fruit' }, + * { 'name': 'beet', 'organic': false, 'type': 'vegetable' } + * ]; + * + * // using "_.where" callback shorthand + * _.find(food, { 'type': 'vegetable' }); + * // => { 'name': 'beet', 'organic': false, 'type': 'vegetable' } + * + * // using "_.pluck" callback shorthand + * _.find(food, 'organic'); + * // => { 'name': 'banana', 'organic': true, 'type': 'fruit' } + */ + function find(collection, callback, thisArg) { + callback = lodash.createCallback(callback, thisArg); + + var index = -1, + length = collection ? collection.length : 0; + + if (typeof length == 'number') { + while (++index < length) { + var value = collection[index]; + if (callback(value, index, collection)) { + return value; + } + } + } else { + var result; + forOwn(collection, function(value, index, collection) { + if (callback(value, index, collection)) { + result = value; + return false; + } + }); + return result; + } + } + + /** + * Iterates over a `collection`, executing the `callback` for each element in + * the `collection`. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index|key, collection). Callbacks may exit iteration early + * by explicitly returning `false`. + * + * @static + * @memberOf _ + * @alias each + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array|Object|String} Returns `collection`. + * @example + * + * _([1, 2, 3]).forEach(alert).join(','); + * // => alerts each number and returns '1,2,3' + * + * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert); + * // => alerts each number value (order is not guaranteed) + */ + function forEach(collection, callback, thisArg) { + var index = -1, + length = collection ? collection.length : 0; + + callback = callback && typeof thisArg == 'undefined' ? callback : lodash.createCallback(callback, thisArg); + if (typeof length == 'number') { + while (++index < length) { + if (callback(collection[index], index, collection) === false) { + break; + } + } + } else { + forOwn(collection, callback); + } + return collection; + } + + /** + * Creates an object composed of keys returned from running each element of the + * `collection` through the `callback`. The corresponding value of each key is + * an array of elements passed to `callback` that returned the key. The `callback` + * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false` + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the composed aggregate object. + * @example + * + * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); }); + * // => { '4': [4.2], '6': [6.1, 6.4] } + * + * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math); + * // => { '4': [4.2], '6': [6.1, 6.4] } + * + * // using "_.pluck" callback shorthand + * _.groupBy(['one', 'two', 'three'], 'length'); + * // => { '3': ['one', 'two'], '5': ['three'] } + */ + function groupBy(collection, callback, thisArg) { + var result = {}; + callback = lodash.createCallback(callback, thisArg); + + forEach(collection, function(value, key, collection) { + key = String(callback(value, key, collection)); + (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value); + }); + return result; + } + + /** + * Invokes the method named by `methodName` on each element in the `collection`, + * returning an array of the results of each invoked method. Additional arguments + * will be passed to each invoked method. If `methodName` is a function, it will + * be invoked for, and `this` bound to, each element in the `collection`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|String} methodName The name of the method to invoke or + * the function invoked per iteration. + * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with. + * @returns {Array} Returns a new array of the results of each invoked method. + * @example + * + * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); + * // => [[1, 5, 7], [1, 2, 3]] + * + * _.invoke([123, 456], String.prototype.split, ''); + * // => [['1', '2', '3'], ['4', '5', '6']] + */ + function invoke(collection, methodName) { + var args = nativeSlice.call(arguments, 2), + index = -1, + isFunc = typeof methodName == 'function', + length = collection ? collection.length : 0, + result = Array(typeof length == 'number' ? length : 0); + + forEach(collection, function(value) { + result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args); + }); + return result; + } + + /** + * Creates an array of values by running each element in the `collection` + * through the `callback`. The `callback` is bound to `thisArg` and invoked with + * three arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias collect + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of the results of each `callback` execution. + * @example + * + * _.map([1, 2, 3], function(num) { return num * 3; }); + * // => [3, 6, 9] + * + * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; }); + * // => [3, 6, 9] (order is not guaranteed) + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * // using "_.pluck" callback shorthand + * _.map(stooges, 'name'); + * // => ['moe', 'larry'] + */ + function map(collection, callback, thisArg) { + var index = -1, + length = collection ? collection.length : 0; + + callback = lodash.createCallback(callback, thisArg); + if (typeof length == 'number') { + var result = Array(length); + while (++index < length) { + result[index] = callback(collection[index], index, collection); + } + } else { + result = []; + forOwn(collection, function(value, key, collection) { + result[++index] = callback(value, key, collection); + }); + } + return result; + } + + /** + * Retrieves the maximum value of an `array`. If `callback` is passed, + * it will be executed for each value in the `array` to generate the + * criterion by which the value is ranked. The `callback` is bound to + * `thisArg` and invoked with three arguments; (value, index, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the maximum value. + * @example + * + * _.max([4, 2, 8, 6]); + * // => 8 + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * _.max(stooges, function(stooge) { return stooge.age; }); + * // => { 'name': 'larry', 'age': 50 }; + * + * // using "_.pluck" callback shorthand + * _.max(stooges, 'age'); + * // => { 'name': 'larry', 'age': 50 }; + */ + function max(collection, callback, thisArg) { + var computed = -Infinity, + result = computed; + + if (!callback && isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + var value = collection[index]; + if (value > result) { + result = value; + } + } + } else { + callback = (!callback && isString(collection)) + ? charAtCallback + : lodash.createCallback(callback, thisArg); + + forEach(collection, function(value, index, collection) { + var current = callback(value, index, collection); + if (current > computed) { + computed = current; + result = value; + } + }); + } + return result; + } + + /** + * Retrieves the minimum value of an `array`. If `callback` is passed, + * it will be executed for each value in the `array` to generate the + * criterion by which the value is ranked. The `callback` is bound to `thisArg` + * and invoked with three arguments; (value, index, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the minimum value. + * @example + * + * _.min([4, 2, 8, 6]); + * // => 2 + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * _.min(stooges, function(stooge) { return stooge.age; }); + * // => { 'name': 'moe', 'age': 40 }; + * + * // using "_.pluck" callback shorthand + * _.min(stooges, 'age'); + * // => { 'name': 'moe', 'age': 40 }; + */ + function min(collection, callback, thisArg) { + var computed = Infinity, + result = computed; + + if (!callback && isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + var value = collection[index]; + if (value < result) { + result = value; + } + } + } else { + callback = (!callback && isString(collection)) + ? charAtCallback + : lodash.createCallback(callback, thisArg); + + forEach(collection, function(value, index, collection) { + var current = callback(value, index, collection); + if (current < computed) { + computed = current; + result = value; + } + }); + } + return result; + } + + /** + * Retrieves the value of a specified property from all elements in the `collection`. + * + * @static + * @memberOf _ + * @type Function + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {String} property The property to pluck. + * @returns {Array} Returns a new array of property values. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * _.pluck(stooges, 'name'); + * // => ['moe', 'larry'] + */ + function pluck(collection, property) { + var index = -1, + length = collection ? collection.length : 0; + + if (typeof length == 'number') { + var result = Array(length); + while (++index < length) { + result[index] = collection[index][property]; + } + } + return result || map(collection, property); + } + + /** + * Reduces a `collection` to a value which is the accumulated result of running + * each element in the `collection` through the `callback`, where each successive + * `callback` execution consumes the return value of the previous execution. + * If `accumulator` is not passed, the first element of the `collection` will be + * used as the initial `accumulator` value. The `callback` is bound to `thisArg` + * and invoked with four arguments; (accumulator, value, index|key, collection). + * + * @static + * @memberOf _ + * @alias foldl, inject + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [accumulator] Initial value of the accumulator. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the accumulated value. + * @example + * + * var sum = _.reduce([1, 2, 3], function(sum, num) { + * return sum + num; + * }); + * // => 6 + * + * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) { + * result[key] = num * 3; + * return result; + * }, {}); + * // => { 'a': 3, 'b': 6, 'c': 9 } + */ + function reduce(collection, callback, accumulator, thisArg) { + if (!collection) return accumulator; + var noaccum = arguments.length < 3; + callback = lodash.createCallback(callback, thisArg, 4); + + var index = -1, + length = collection.length; + + if (typeof length == 'number') { + if (noaccum) { + accumulator = collection[++index]; + } + while (++index < length) { + accumulator = callback(accumulator, collection[index], index, collection); + } + } else { + forOwn(collection, function(value, index, collection) { + accumulator = noaccum + ? (noaccum = false, value) + : callback(accumulator, value, index, collection) + }); + } + return accumulator; + } + + /** + * This method is similar to `_.reduce`, except that it iterates over a + * `collection` from right to left. + * + * @static + * @memberOf _ + * @alias foldr + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [accumulator] Initial value of the accumulator. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the accumulated value. + * @example + * + * var list = [[0, 1], [2, 3], [4, 5]]; + * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []); + * // => [4, 5, 2, 3, 0, 1] + */ + function reduceRight(collection, callback, accumulator, thisArg) { + var iterable = collection, + length = collection ? collection.length : 0, + noaccum = arguments.length < 3; + + if (typeof length != 'number') { + var props = keys(collection); + length = props.length; + } + callback = lodash.createCallback(callback, thisArg, 4); + forEach(collection, function(value, index, collection) { + index = props ? props[--length] : --length; + accumulator = noaccum + ? (noaccum = false, iterable[index]) + : callback(accumulator, iterable[index], index, collection); + }); + return accumulator; + } + + /** + * The opposite of `_.filter`, this method returns the elements of a + * `collection` that `callback` does **not** return truthy for. + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of elements that did **not** pass the + * callback check. + * @example + * + * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => [1, 3, 5] + * + * var food = [ + * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, + * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } + * ]; + * + * // using "_.pluck" callback shorthand + * _.reject(food, 'organic'); + * // => [{ 'name': 'apple', 'organic': false, 'type': 'fruit' }] + * + * // using "_.where" callback shorthand + * _.reject(food, { 'type': 'fruit' }); + * // => [{ 'name': 'carrot', 'organic': true, 'type': 'vegetable' }] + */ + function reject(collection, callback, thisArg) { + callback = lodash.createCallback(callback, thisArg); + return filter(collection, function(value, index, collection) { + return !callback(value, index, collection); + }); + } + + /** + * Creates an array of shuffled `array` values, using a version of the + * Fisher-Yates shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to shuffle. + * @returns {Array} Returns a new shuffled collection. + * @example + * + * _.shuffle([1, 2, 3, 4, 5, 6]); + * // => [4, 1, 6, 3, 5, 2] + */ + function shuffle(collection) { + var index = -1, + length = collection ? collection.length : 0, + result = Array(typeof length == 'number' ? length : 0); + + forEach(collection, function(value) { + var rand = floor(nativeRandom() * (++index + 1)); + result[index] = result[rand]; + result[rand] = value; + }); + return result; + } + + /** + * Gets the size of the `collection` by returning `collection.length` for arrays + * and array-like objects or the number of own enumerable properties for objects. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to inspect. + * @returns {Number} Returns `collection.length` or number of own enumerable properties. + * @example + * + * _.size([1, 2]); + * // => 2 + * + * _.size({ 'one': 1, 'two': 2, 'three': 3 }); + * // => 3 + * + * _.size('curly'); + * // => 5 + */ + function size(collection) { + var length = collection ? collection.length : 0; + return typeof length == 'number' ? length : keys(collection).length; + } + + /** + * Checks if the `callback` returns a truthy value for **any** element of a + * `collection`. The function returns as soon as it finds passing value, and + * does not iterate over the entire `collection`. The `callback` is bound to + * `thisArg` and invoked with three arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias any + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Boolean} Returns `true` if any element passes the callback check, + * else `false`. + * @example + * + * _.some([null, 0, 'yes', false], Boolean); + * // => true + * + * var food = [ + * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, + * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } + * ]; + * + * // using "_.pluck" callback shorthand + * _.some(food, 'organic'); + * // => true + * + * // using "_.where" callback shorthand + * _.some(food, { 'type': 'meat' }); + * // => false + */ + function some(collection, callback, thisArg) { + var result; + callback = lodash.createCallback(callback, thisArg); + + var index = -1, + length = collection ? collection.length : 0; + + if (typeof length == 'number') { + while (++index < length) { + if ((result = callback(collection[index], index, collection))) { + break; + } + } + } else { + forOwn(collection, function(value, index, collection) { + return !(result = callback(value, index, collection)); + }); + } + return !!result; + } + + /** + * Creates an array of elements, sorted in ascending order by the results of + * running each element in the `collection` through the `callback`. This method + * performs a stable sort, that is, it will preserve the original sort order of + * equal elements. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index|key, collection). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of sorted elements. + * @example + * + * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); }); + * // => [3, 1, 2] + * + * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math); + * // => [3, 1, 2] + * + * // using "_.pluck" callback shorthand + * _.sortBy(['banana', 'strawberry', 'apple'], 'length'); + * // => ['apple', 'banana', 'strawberry'] + */ + function sortBy(collection, callback, thisArg) { + var index = -1, + length = collection ? collection.length : 0, + result = Array(typeof length == 'number' ? length : 0); + + callback = lodash.createCallback(callback, thisArg); + forEach(collection, function(value, key, collection) { + var object = result[++index] = getObject(); + object.criteria = callback(value, key, collection); + object.index = index; + object.value = value; + }); + + length = result.length; + result.sort(compareAscending); + while (length--) { + var object = result[length]; + result[length] = object.value; + releaseObject(object); + } + return result; + } + + /** + * Converts the `collection` to an array. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|String} collection The collection to convert. + * @returns {Array} Returns the new converted array. + * @example + * + * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4); + * // => [2, 3, 4] + */ + function toArray(collection) { + if (collection && typeof collection.length == 'number') { + return slice(collection); + } + return values(collection); + } + + /** + * Examines each element in a `collection`, returning an array of all elements + * that have the given `properties`. When checking `properties`, this method + * performs a deep comparison between values to determine if they are equivalent + * to each other. + * + * @static + * @memberOf _ + * @type Function + * @category Collections + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Object} properties The object of property values to filter by. + * @returns {Array} Returns a new array of elements that have the given `properties`. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * _.where(stooges, { 'age': 40 }); + * // => [{ 'name': 'moe', 'age': 40 }] + */ + var where = filter; + + /*--------------------------------------------------------------------------*/ + + /** + * Creates an array with all falsey values of `array` removed. The values + * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to compact. + * @returns {Array} Returns a new filtered array. + * @example + * + * _.compact([0, 1, false, 2, '', 3]); + * // => [1, 2, 3] + */ + function compact(array) { + var index = -1, + length = array ? array.length : 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (value) { + result.push(value); + } + } + return result; + } + + /** + * Creates an array of `array` elements not present in the other arrays + * using strict equality for comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to process. + * @param {Array} [array1, array2, ...] Arrays to check. + * @returns {Array} Returns a new array of `array` elements not present in the + * other arrays. + * @example + * + * _.difference([1, 2, 3, 4, 5], [5, 2, 10]); + * // => [1, 3, 4] + */ + function difference(array) { + var index = -1, + indexOf = getIndexOf(), + length = array ? array.length : 0, + seen = concat.apply(arrayRef, nativeSlice.call(arguments, 1)), + result = []; + + var isLarge = length >= largeArraySize && indexOf === basicIndexOf; + + if (isLarge) { + var cache = createCache(seen); + if (cache) { + indexOf = cacheIndexOf; + seen = cache; + } else { + isLarge = false; + } + } + while (++index < length) { + var value = array[index]; + if (indexOf(seen, value) < 0) { + result.push(value); + } + } + if (isLarge) { + releaseObject(seen); + } + return result; + } + + /** + * This method is similar to `_.find`, except that it returns the index of + * the element that passes the callback check, instead of the element itself. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to search. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the index of the found element, else `-1`. + * @example + * + * _.findIndex(['apple', 'banana', 'beet'], function(food) { + * return /^b/.test(food); + * }); + * // => 1 + */ + function findIndex(array, callback, thisArg) { + var index = -1, + length = array ? array.length : 0; + + callback = lodash.createCallback(callback, thisArg); + while (++index < length) { + if (callback(array[index], index, array)) { + return index; + } + } + return -1; + } + + /** + * Gets the first element of the `array`. If a number `n` is passed, the first + * `n` elements of the `array` are returned. If a `callback` function is passed, + * elements at the beginning of the array are returned as long as the `callback` + * returns truthy. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index, array). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias head, take + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|Number|String} [callback|n] The function called + * per element or the number of elements to return. If a property name or + * object is passed, it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the first element(s) of `array`. + * @example + * + * _.first([1, 2, 3]); + * // => 1 + * + * _.first([1, 2, 3], 2); + * // => [1, 2] + * + * _.first([1, 2, 3], function(num) { + * return num < 3; + * }); + * // => [1, 2] + * + * var food = [ + * { 'name': 'banana', 'organic': true }, + * { 'name': 'beet', 'organic': false }, + * ]; + * + * // using "_.pluck" callback shorthand + * _.first(food, 'organic'); + * // => [{ 'name': 'banana', 'organic': true }] + * + * var food = [ + * { 'name': 'apple', 'type': 'fruit' }, + * { 'name': 'banana', 'type': 'fruit' }, + * { 'name': 'beet', 'type': 'vegetable' } + * ]; + * + * // using "_.where" callback shorthand + * _.first(food, { 'type': 'fruit' }); + * // => [{ 'name': 'apple', 'type': 'fruit' }, { 'name': 'banana', 'type': 'fruit' }] + */ + function first(array, callback, thisArg) { + if (array) { + var n = 0, + length = array.length; + + if (typeof callback != 'number' && callback != null) { + var index = -1; + callback = lodash.createCallback(callback, thisArg); + while (++index < length && callback(array[index], index, array)) { + n++; + } + } else { + n = callback; + if (n == null || thisArg) { + return array[0]; + } + } + return slice(array, 0, nativeMin(nativeMax(0, n), length)); + } + } + + /** + * Flattens a nested array (the nesting can be to any depth). If `isShallow` + * is truthy, `array` will only be flattened a single level. If `callback` + * is passed, each element of `array` is passed through a `callback` before + * flattening. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index, array). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to flatten. + * @param {Boolean} [isShallow=false] A flag to indicate only flattening a single level. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new flattened array. + * @example + * + * _.flatten([1, [2], [3, [[4]]]]); + * // => [1, 2, 3, 4]; + * + * _.flatten([1, [2], [3, [[4]]]], true); + * // => [1, 2, 3, [[4]]]; + * + * var stooges = [ + * { 'name': 'curly', 'quotes': ['Oh, a wise guy, eh?', 'Poifect!'] }, + * { 'name': 'moe', 'quotes': ['Spread out!', 'You knucklehead!'] } + * ]; + * + * // using "_.pluck" callback shorthand + * _.flatten(stooges, 'quotes'); + * // => ['Oh, a wise guy, eh?', 'Poifect!', 'Spread out!', 'You knucklehead!'] + */ + var flatten = overloadWrapper(function flatten(array, isShallow, callback) { + var index = -1, + length = array ? array.length : 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (callback) { + value = callback(value, index, array); + } + // recursively flatten arrays (susceptible to call stack limits) + if (isArray(value)) { + push.apply(result, isShallow ? value : flatten(value)); + } else { + result.push(value); + } + } + return result; + }); + + /** + * Gets the index at which the first occurrence of `value` is found using + * strict equality for comparisons, i.e. `===`. If the `array` is already + * sorted, passing `true` for `fromIndex` will run a faster binary search. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to search. + * @param {Mixed} value The value to search for. + * @param {Boolean|Number} [fromIndex=0] The index to search from or `true` to + * perform a binary search on a sorted `array`. + * @returns {Number} Returns the index of the matched value or `-1`. + * @example + * + * _.indexOf([1, 2, 3, 1, 2, 3], 2); + * // => 1 + * + * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3); + * // => 4 + * + * _.indexOf([1, 1, 2, 2, 3, 3], 2, true); + * // => 2 + */ + function indexOf(array, value, fromIndex) { + if (typeof fromIndex == 'number') { + var length = array ? array.length : 0; + fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0); + } else if (fromIndex) { + var index = sortedIndex(array, value); + return array[index] === value ? index : -1; + } + return array ? basicIndexOf(array, value, fromIndex) : -1; + } + + /** + * Gets all but the last element of `array`. If a number `n` is passed, the + * last `n` elements are excluded from the result. If a `callback` function + * is passed, elements at the end of the array are excluded from the result + * as long as the `callback` returns truthy. The `callback` is bound to + * `thisArg` and invoked with three arguments; (value, index, array). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|Number|String} [callback|n=1] The function called + * per element or the number of elements to exclude. If a property name or + * object is passed, it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a slice of `array`. + * @example + * + * _.initial([1, 2, 3]); + * // => [1, 2] + * + * _.initial([1, 2, 3], 2); + * // => [1] + * + * _.initial([1, 2, 3], function(num) { + * return num > 1; + * }); + * // => [1] + * + * var food = [ + * { 'name': 'beet', 'organic': false }, + * { 'name': 'carrot', 'organic': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.initial(food, 'organic'); + * // => [{ 'name': 'beet', 'organic': false }] + * + * var food = [ + * { 'name': 'banana', 'type': 'fruit' }, + * { 'name': 'beet', 'type': 'vegetable' }, + * { 'name': 'carrot', 'type': 'vegetable' } + * ]; + * + * // using "_.where" callback shorthand + * _.initial(food, { 'type': 'vegetable' }); + * // => [{ 'name': 'banana', 'type': 'fruit' }] + */ + function initial(array, callback, thisArg) { + if (!array) { + return []; + } + var n = 0, + length = array.length; + + if (typeof callback != 'number' && callback != null) { + var index = length; + callback = lodash.createCallback(callback, thisArg); + while (index-- && callback(array[index], index, array)) { + n++; + } + } else { + n = (callback == null || thisArg) ? 1 : callback || n; + } + return slice(array, 0, nativeMin(nativeMax(0, length - n), length)); + } + + /** + * Computes the intersection of all the passed-in arrays using strict equality + * for comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} [array1, array2, ...] Arrays to process. + * @returns {Array} Returns a new array of unique elements that are present + * in **all** of the arrays. + * @example + * + * _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); + * // => [1, 2] + */ + function intersection(array) { + var args = arguments, + argsLength = args.length, + argsIndex = -1, + caches = getArray(), + index = -1, + indexOf = getIndexOf(), + length = array ? array.length : 0, + result = [], + seen = getArray(); + + while (++argsIndex < argsLength) { + var value = args[argsIndex]; + caches[argsIndex] = indexOf === basicIndexOf && + (value ? value.length : 0) >= largeArraySize && + createCache(argsIndex ? args[argsIndex] : seen); + } + outer: + while (++index < length) { + var cache = caches[0]; + value = array[index]; + + if ((cache ? cacheIndexOf(cache, value) : indexOf(seen, value)) < 0) { + argsIndex = argsLength; + (cache || seen).push(value); + while (--argsIndex) { + cache = caches[argsIndex]; + if ((cache ? cacheIndexOf(cache, value) : indexOf(args[argsIndex], value)) < 0) { + continue outer; + } + } + result.push(value); + } + } + while (argsLength--) { + cache = caches[argsLength]; + if (cache) { + releaseObject(cache); + } + } + releaseArray(caches); + releaseArray(seen); + return result; + } + + /** + * Gets the last element of the `array`. If a number `n` is passed, the + * last `n` elements of the `array` are returned. If a `callback` function + * is passed, elements at the end of the array are returned as long as the + * `callback` returns truthy. The `callback` is bound to `thisArg` and + * invoked with three arguments;(value, index, array). + * + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|Number|String} [callback|n] The function called + * per element or the number of elements to return. If a property name or + * object is passed, it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the last element(s) of `array`. + * @example + * + * _.last([1, 2, 3]); + * // => 3 + * + * _.last([1, 2, 3], 2); + * // => [2, 3] + * + * _.last([1, 2, 3], function(num) { + * return num > 1; + * }); + * // => [2, 3] + * + * var food = [ + * { 'name': 'beet', 'organic': false }, + * { 'name': 'carrot', 'organic': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.last(food, 'organic'); + * // => [{ 'name': 'carrot', 'organic': true }] + * + * var food = [ + * { 'name': 'banana', 'type': 'fruit' }, + * { 'name': 'beet', 'type': 'vegetable' }, + * { 'name': 'carrot', 'type': 'vegetable' } + * ]; + * + * // using "_.where" callback shorthand + * _.last(food, { 'type': 'vegetable' }); + * // => [{ 'name': 'beet', 'type': 'vegetable' }, { 'name': 'carrot', 'type': 'vegetable' }] + */ + function last(array, callback, thisArg) { + if (array) { + var n = 0, + length = array.length; + + if (typeof callback != 'number' && callback != null) { + var index = length; + callback = lodash.createCallback(callback, thisArg); + while (index-- && callback(array[index], index, array)) { + n++; + } + } else { + n = callback; + if (n == null || thisArg) { + return array[length - 1]; + } + } + return slice(array, nativeMax(0, length - n)); + } + } + + /** + * Gets the index at which the last occurrence of `value` is found using strict + * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used + * as the offset from the end of the collection. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to search. + * @param {Mixed} value The value to search for. + * @param {Number} [fromIndex=array.length-1] The index to search from. + * @returns {Number} Returns the index of the matched value or `-1`. + * @example + * + * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2); + * // => 4 + * + * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3); + * // => 1 + */ + function lastIndexOf(array, value, fromIndex) { + var index = array ? array.length : 0; + if (typeof fromIndex == 'number') { + index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1; + } + while (index--) { + if (array[index] === value) { + return index; + } + } + return -1; + } + + /** + * Creates an array of numbers (positive and/or negative) progressing from + * `start` up to but not including `end`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Number} [start=0] The start of the range. + * @param {Number} end The end of the range. + * @param {Number} [step=1] The value to increment or decrement by. + * @returns {Array} Returns a new range array. + * @example + * + * _.range(10); + * // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + * + * _.range(1, 11); + * // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + * + * _.range(0, 30, 5); + * // => [0, 5, 10, 15, 20, 25] + * + * _.range(0, -10, -1); + * // => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] + * + * _.range(0); + * // => [] + */ + function range(start, end, step) { + start = +start || 0; + step = +step || 1; + + if (end == null) { + end = start; + start = 0; + } + // use `Array(length)` so V8 will avoid the slower "dictionary" mode + // http://youtu.be/XAqIpGU8ZZk#t=17m25s + var index = -1, + length = nativeMax(0, ceil((end - start) / step)), + result = Array(length); + + while (++index < length) { + result[index] = start; + start += step; + } + return result; + } + + /** + * The opposite of `_.initial`, this method gets all but the first value of + * `array`. If a number `n` is passed, the first `n` values are excluded from + * the result. If a `callback` function is passed, elements at the beginning + * of the array are excluded from the result as long as the `callback` returns + * truthy. The `callback` is bound to `thisArg` and invoked with three + * arguments; (value, index, array). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias drop, tail + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|Number|String} [callback|n=1] The function called + * per element or the number of elements to exclude. If a property name or + * object is passed, it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a slice of `array`. + * @example + * + * _.rest([1, 2, 3]); + * // => [2, 3] + * + * _.rest([1, 2, 3], 2); + * // => [3] + * + * _.rest([1, 2, 3], function(num) { + * return num < 3; + * }); + * // => [3] + * + * var food = [ + * { 'name': 'banana', 'organic': true }, + * { 'name': 'beet', 'organic': false }, + * ]; + * + * // using "_.pluck" callback shorthand + * _.rest(food, 'organic'); + * // => [{ 'name': 'beet', 'organic': false }] + * + * var food = [ + * { 'name': 'apple', 'type': 'fruit' }, + * { 'name': 'banana', 'type': 'fruit' }, + * { 'name': 'beet', 'type': 'vegetable' } + * ]; + * + * // using "_.where" callback shorthand + * _.rest(food, { 'type': 'fruit' }); + * // => [{ 'name': 'beet', 'type': 'vegetable' }] + */ + function rest(array, callback, thisArg) { + if (typeof callback != 'number' && callback != null) { + var n = 0, + index = -1, + length = array ? array.length : 0; + + callback = lodash.createCallback(callback, thisArg); + while (++index < length && callback(array[index], index, array)) { + n++; + } + } else { + n = (callback == null || thisArg) ? 1 : nativeMax(0, callback); + } + return slice(array, n); + } + + /** + * Uses a binary search to determine the smallest index at which the `value` + * should be inserted into `array` in order to maintain the sort order of the + * sorted `array`. If `callback` is passed, it will be executed for `value` and + * each element in `array` to compute their sort ranking. The `callback` is + * bound to `thisArg` and invoked with one argument; (value). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to inspect. + * @param {Mixed} value The value to evaluate. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Number} Returns the index at which the value should be inserted + * into `array`. + * @example + * + * _.sortedIndex([20, 30, 50], 40); + * // => 2 + * + * // using "_.pluck" callback shorthand + * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x'); + * // => 2 + * + * var dict = { + * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 } + * }; + * + * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { + * return dict.wordToNumber[word]; + * }); + * // => 2 + * + * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { + * return this.wordToNumber[word]; + * }, dict); + * // => 2 + */ + function sortedIndex(array, value, callback, thisArg) { + var low = 0, + high = array ? array.length : low; + + // explicitly reference `identity` for better inlining in Firefox + callback = callback ? lodash.createCallback(callback, thisArg, 1) : identity; + value = callback(value); + + while (low < high) { + var mid = (low + high) >>> 1; + (callback(array[mid]) < value) + ? low = mid + 1 + : high = mid; + } + return low; + } + + /** + * Computes the union of the passed-in arrays using strict equality for + * comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} [array1, array2, ...] Arrays to process. + * @returns {Array} Returns a new array of unique values, in order, that are + * present in one or more of the arrays. + * @example + * + * _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); + * // => [1, 2, 3, 101, 10] + */ + function union(array) { + if (!isArray(array)) { + arguments[0] = array ? nativeSlice.call(array) : arrayRef; + } + return uniq(concat.apply(arrayRef, arguments)); + } + + /** + * Creates a duplicate-value-free version of the `array` using strict equality + * for comparisons, i.e. `===`. If the `array` is already sorted, passing `true` + * for `isSorted` will run a faster algorithm. If `callback` is passed, each + * element of `array` is passed through the `callback` before uniqueness is computed. + * The `callback` is bound to `thisArg` and invoked with three arguments; (value, index, array). + * + * If a property name is passed for `callback`, the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is passed for `callback`, the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias unique + * @category Arrays + * @param {Array} array The array to process. + * @param {Boolean} [isSorted=false] A flag to indicate that the `array` is already sorted. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a duplicate-value-free array. + * @example + * + * _.uniq([1, 2, 1, 3, 1]); + * // => [1, 2, 3] + * + * _.uniq([1, 1, 2, 2, 3], true); + * // => [1, 2, 3] + * + * _.uniq(['A', 'b', 'C', 'a', 'B', 'c'], function(letter) { return letter.toLowerCase(); }); + * // => ['A', 'b', 'C'] + * + * _.uniq([1, 2.5, 3, 1.5, 2, 3.5], function(num) { return this.floor(num); }, Math); + * // => [1, 2.5, 3] + * + * // using "_.pluck" callback shorthand + * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x'); + * // => [{ 'x': 1 }, { 'x': 2 }] + */ + var uniq = overloadWrapper(function(array, isSorted, callback) { + var index = -1, + indexOf = getIndexOf(), + length = array ? array.length : 0, + result = []; + + var isLarge = !isSorted && length >= largeArraySize && indexOf === basicIndexOf, + seen = (callback || isLarge) ? getArray() : result; + + if (isLarge) { + var cache = createCache(seen); + if (cache) { + indexOf = cacheIndexOf; + seen = cache; + } else { + isLarge = false; + seen = callback ? seen : (releaseArray(seen), result); + } + } + while (++index < length) { + var value = array[index], + computed = callback ? callback(value, index, array) : value; + + if (isSorted + ? !index || seen[seen.length - 1] !== computed + : indexOf(seen, computed) < 0 + ) { + if (callback || isLarge) { + seen.push(computed); + } + result.push(value); + } + } + if (isLarge) { + releaseArray(seen.array); + releaseObject(seen); + } else if (callback) { + releaseArray(seen); + } + return result; + }); + + /** + * The inverse of `_.zip`, this method splits groups of elements into arrays + * composed of elements from each group at their corresponding indexes. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to process. + * @returns {Array} Returns a new array of the composed arrays. + * @example + * + * _.unzip([['moe', 30, true], ['larry', 40, false]]); + * // => [['moe', 'larry'], [30, 40], [true, false]]; + */ + function unzip(array) { + var index = -1, + length = array ? max(pluck(array, 'length')) : 0, + result = Array(length < 0 ? 0 : length); + + while (++index < length) { + result[index] = pluck(array, index); + } + return result; + } + + /** + * Creates an array with all occurrences of the passed values removed using + * strict equality for comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to filter. + * @param {Mixed} [value1, value2, ...] Values to remove. + * @returns {Array} Returns a new filtered array. + * @example + * + * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1); + * // => [2, 3, 4] + */ + function without(array) { + return difference(array, nativeSlice.call(arguments, 1)); + } + + /** + * Groups the elements of each array at their corresponding indexes. Useful for + * separate data sources that are coordinated through matching array indexes. + * For a matrix of nested arrays, `_.zip.apply(...)` can transpose the matrix + * in a similar fashion. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} [array1, array2, ...] Arrays to process. + * @returns {Array} Returns a new array of grouped elements. + * @example + * + * _.zip(['moe', 'larry'], [30, 40], [true, false]); + * // => [['moe', 30, true], ['larry', 40, false]] + */ + function zip(array) { + return array ? unzip(arguments) : []; + } + + /** + * Creates an object composed from arrays of `keys` and `values`. Pass either + * a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`, or + * two arrays, one of `keys` and one of corresponding `values`. + * + * @static + * @memberOf _ + * @alias object + * @category Arrays + * @param {Array} keys The array of keys. + * @param {Array} [values=[]] The array of values. + * @returns {Object} Returns an object composed of the given keys and + * corresponding values. + * @example + * + * _.zipObject(['moe', 'larry'], [30, 40]); + * // => { 'moe': 30, 'larry': 40 } + */ + function zipObject(keys, values) { + var index = -1, + length = keys ? keys.length : 0, + result = {}; + + while (++index < length) { + var key = keys[index]; + if (values) { + result[key] = values[index]; + } else { + result[key[0]] = key[1]; + } + } + return result; + } + + /*--------------------------------------------------------------------------*/ + + /** + * If `n` is greater than `0`, a function is created that is restricted to + * executing `func`, with the `this` binding and arguments of the created + * function, only after it is called `n` times. If `n` is less than `1`, + * `func` is executed immediately, without a `this` binding or additional + * arguments, and its result is returned. + * + * @static + * @memberOf _ + * @category Functions + * @param {Number} n The number of times the function must be called before + * it is executed. + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. + * @example + * + * var renderNotes = _.after(notes.length, render); + * _.forEach(notes, function(note) { + * note.asyncSave({ 'success': renderNotes }); + * }); + * // `renderNotes` is run once, after all notes have saved + */ + function after(n, func) { + if (n < 1) { + return func(); + } + return function() { + if (--n < 1) { + return func.apply(this, arguments); + } + }; + } + + /** + * Creates a function that, when called, invokes `func` with the `this` + * binding of `thisArg` and prepends any additional `bind` arguments to those + * passed to the bound function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to bind. + * @param {Mixed} [thisArg] The `this` binding of `func`. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new bound function. + * @example + * + * var func = function(greeting) { + * return greeting + ' ' + this.name; + * }; + * + * func = _.bind(func, { 'name': 'moe' }, 'hi'); + * func(); + * // => 'hi moe' + */ + function bind(func, thisArg) { + // use `Function#bind` if it exists and is fast + // (in V8 `Function#bind` is slower except when partially applied) + return support.fastBind || (nativeBind && arguments.length > 2) + ? nativeBind.call.apply(nativeBind, arguments) + : createBound(func, thisArg, nativeSlice.call(arguments, 2)); + } + + /** + * Binds methods on `object` to `object`, overwriting the existing method. + * Method names may be specified as individual arguments or as arrays of method + * names. If no method names are provided, all the function properties of `object` + * will be bound. + * + * @static + * @memberOf _ + * @category Functions + * @param {Object} object The object to bind and assign the bound methods to. + * @param {String} [methodName1, methodName2, ...] Method names on the object to bind. + * @returns {Object} Returns `object`. + * @example + * + * var view = { + * 'label': 'docs', + * 'onClick': function() { alert('clicked ' + this.label); } + * }; + * + * _.bindAll(view); + * jQuery('#docs').on('click', view.onClick); + * // => alerts 'clicked docs', when the button is clicked + */ + function bindAll(object) { + var funcs = arguments.length > 1 ? concat.apply(arrayRef, nativeSlice.call(arguments, 1)) : functions(object), + index = -1, + length = funcs.length; + + while (++index < length) { + var key = funcs[index]; + object[key] = bind(object[key], object); + } + return object; + } + + /** + * Creates a function that, when called, invokes the method at `object[key]` + * and prepends any additional `bindKey` arguments to those passed to the bound + * function. This method differs from `_.bind` by allowing bound functions to + * reference methods that will be redefined or don't yet exist. + * See http://michaux.ca/articles/lazy-function-definition-pattern. + * + * @static + * @memberOf _ + * @category Functions + * @param {Object} object The object the method belongs to. + * @param {String} key The key of the method. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new bound function. + * @example + * + * var object = { + * 'name': 'moe', + * 'greet': function(greeting) { + * return greeting + ' ' + this.name; + * } + * }; + * + * var func = _.bindKey(object, 'greet', 'hi'); + * func(); + * // => 'hi moe' + * + * object.greet = function(greeting) { + * return greeting + ', ' + this.name + '!'; + * }; + * + * func(); + * // => 'hi, moe!' + */ + function bindKey(object, key) { + return createBound(object, key, nativeSlice.call(arguments, 2), indicatorObject); + } + + /** + * Creates a function that is the composition of the passed functions, + * where each function consumes the return value of the function that follows. + * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`. + * Each function is executed with the `this` binding of the composed function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} [func1, func2, ...] Functions to compose. + * @returns {Function} Returns the new composed function. + * @example + * + * var greet = function(name) { return 'hi ' + name; }; + * var exclaim = function(statement) { return statement + '!'; }; + * var welcome = _.compose(exclaim, greet); + * welcome('moe'); + * // => 'hi moe!' + */ + function compose() { + var funcs = arguments; + return function() { + var args = arguments, + length = funcs.length; + + while (length--) { + args = [funcs[length].apply(this, args)]; + } + return args[0]; + }; + } + + /** + * Produces a callback bound to an optional `thisArg`. If `func` is a property + * name, the created callback will return the property value for a given element. + * If `func` is an object, the created callback will return `true` for elements + * that contain the equivalent object properties, otherwise it will return `false`. + * + * Note: All Lo-Dash methods, that accept a `callback` argument, use `_.createCallback`. + * + * @static + * @memberOf _ + * @category Functions + * @param {Mixed} [func=identity] The value to convert to a callback. + * @param {Mixed} [thisArg] The `this` binding of the created callback. + * @param {Number} [argCount=3] The number of arguments the callback accepts. + * @returns {Function} Returns a callback function. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 } + * ]; + * + * // wrap to create custom callback shorthands + * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) { + * var match = /^(.+?)__([gl]t)(.+)$/.exec(callback); + * return !match ? func(callback, thisArg) : function(object) { + * return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3]; + * }; + * }); + * + * _.filter(stooges, 'age__gt45'); + * // => [{ 'name': 'larry', 'age': 50 }] + * + * // create mixins with support for "_.pluck" and "_.where" callback shorthands + * _.mixin({ + * 'toLookup': function(collection, callback, thisArg) { + * callback = _.createCallback(callback, thisArg); + * return _.reduce(collection, function(result, value, index, collection) { + * return (result[callback(value, index, collection)] = value, result); + * }, {}); + * } + * }); + * + * _.toLookup(stooges, 'name'); + * // => { 'moe': { 'name': 'moe', 'age': 40 }, 'larry': { 'name': 'larry', 'age': 50 } } + */ + function createCallback(func, thisArg, argCount) { + if (func == null) { + return identity; + } + var type = typeof func; + if (type != 'function') { + if (type != 'object') { + return function(object) { + return object[func]; + }; + } + var props = keys(func); + return function(object) { + var length = props.length, + result = false; + while (length--) { + if (!(result = isEqual(object[props[length]], func[props[length]], indicatorObject))) { + break; + } + } + return result; + }; + } + if (typeof thisArg == 'undefined' || (reThis && !reThis.test(fnToString.call(func)))) { + return func; + } + if (argCount === 1) { + return function(value) { + return func.call(thisArg, value); + }; + } + if (argCount === 2) { + return function(a, b) { + return func.call(thisArg, a, b); + }; + } + if (argCount === 4) { + return function(accumulator, value, index, collection) { + return func.call(thisArg, accumulator, value, index, collection); + }; + } + return function(value, index, collection) { + return func.call(thisArg, value, index, collection); + }; + } + + /** + * Creates a function that will delay the execution of `func` until after + * `wait` milliseconds have elapsed since the last time it was invoked. Pass + * an `options` object to indicate that `func` should be invoked on the leading + * and/or trailing edge of the `wait` timeout. Subsequent calls to the debounced + * function will return the result of the last `func` call. + * + * Note: If `leading` and `trailing` options are `true`, `func` will be called + * on the trailing edge of the timeout only if the the debounced function is + * invoked more than once during the `wait` timeout. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to debounce. + * @param {Number} wait The number of milliseconds to delay. + * @param {Object} options The options object. + * [leading=false] A boolean to specify execution on the leading edge of the timeout. + * [maxWait] The maximum time `func` is allowed to be delayed before it's called. + * [trailing=true] A boolean to specify execution on the trailing edge of the timeout. + * @returns {Function} Returns the new debounced function. + * @example + * + * var lazyLayout = _.debounce(calculateLayout, 300); + * jQuery(window).on('resize', lazyLayout); + * + * jQuery('#postbox').on('click', _.debounce(sendMail, 200, { + * 'leading': true, + * 'trailing': false + * }); + */ + function debounce(func, wait, options) { + var args, + result, + thisArg, + callCount = 0, + lastCalled = 0, + maxWait = false, + maxTimeoutId = null, + timeoutId = null, + trailing = true; + + function clear() { + clearTimeout(maxTimeoutId); + clearTimeout(timeoutId); + callCount = 0; + maxTimeoutId = timeoutId = null; + } + + function delayed() { + var isCalled = trailing && (!leading || callCount > 1); + clear(); + if (isCalled) { + if (maxWait !== false) { + lastCalled = new Date; + } + result = func.apply(thisArg, args); + } + } + + function maxDelayed() { + clear(); + if (trailing || (maxWait !== wait)) { + lastCalled = new Date; + result = func.apply(thisArg, args); + } + } + + wait = nativeMax(0, wait || 0); + if (options === true) { + var leading = true; + trailing = false; + } else if (isObject(options)) { + leading = options.leading; + maxWait = 'maxWait' in options && nativeMax(wait, options.maxWait || 0); + trailing = 'trailing' in options ? options.trailing : trailing; + } + return function() { + args = arguments; + thisArg = this; + callCount++; + + // avoid issues with Titanium and `undefined` timeout ids + // https://github.com/appcelerator/titanium_mobile/blob/3_1_0_GA/android/titanium/src/java/ti/modules/titanium/TitaniumModule.java#L185-L192 + clearTimeout(timeoutId); + + if (maxWait === false) { + if (leading && callCount < 2) { + result = func.apply(thisArg, args); + } + } else { + var now = new Date; + if (!maxTimeoutId && !leading) { + lastCalled = now; + } + var remaining = maxWait - (now - lastCalled); + if (remaining <= 0) { + clearTimeout(maxTimeoutId); + maxTimeoutId = null; + lastCalled = now; + result = func.apply(thisArg, args); + } + else if (!maxTimeoutId) { + maxTimeoutId = setTimeout(maxDelayed, remaining); + } + } + if (wait !== maxWait) { + timeoutId = setTimeout(delayed, wait); + } + return result; + }; + } + + /** + * Defers executing the `func` function until the current call stack has cleared. + * Additional arguments will be passed to `func` when it is invoked. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to defer. + * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. + * @returns {Number} Returns the timer id. + * @example + * + * _.defer(function() { alert('deferred'); }); + * // returns from the function before `alert` is called + */ + function defer(func) { + var args = nativeSlice.call(arguments, 1); + return setTimeout(function() { func.apply(undefined, args); }, 1); + } + // use `setImmediate` if it's available in Node.js + if (isV8 && freeModule && typeof setImmediate == 'function') { + defer = bind(setImmediate, context); + } + + /** + * Executes the `func` function after `wait` milliseconds. Additional arguments + * will be passed to `func` when it is invoked. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to delay. + * @param {Number} wait The number of milliseconds to delay execution. + * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. + * @returns {Number} Returns the timer id. + * @example + * + * var log = _.bind(console.log, console); + * _.delay(log, 1000, 'logged later'); + * // => 'logged later' (Appears after one second.) + */ + function delay(func, wait) { + var args = nativeSlice.call(arguments, 2); + return setTimeout(function() { func.apply(undefined, args); }, wait); + } + + /** + * Creates a function that memoizes the result of `func`. If `resolver` is + * passed, it will be used to determine the cache key for storing the result + * based on the arguments passed to the memoized function. By default, the first + * argument passed to the memoized function is used as the cache key. The `func` + * is executed with the `this` binding of the memoized function. The result + * cache is exposed as the `cache` property on the memoized function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to have its output memoized. + * @param {Function} [resolver] A function used to resolve the cache key. + * @returns {Function} Returns the new memoizing function. + * @example + * + * var fibonacci = _.memoize(function(n) { + * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); + * }); + */ + function memoize(func, resolver) { + function memoized() { + var cache = memoized.cache, + key = keyPrefix + (resolver ? resolver.apply(this, arguments) : arguments[0]); + + return hasOwnProperty.call(cache, key) + ? cache[key] + : (cache[key] = func.apply(this, arguments)); + } + memoized.cache = {}; + return memoized; + } + + /** + * Creates a function that is restricted to execute `func` once. Repeat calls to + * the function will return the value of the first call. The `func` is executed + * with the `this` binding of the created function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. + * @example + * + * var initialize = _.once(createApplication); + * initialize(); + * initialize(); + * // `initialize` executes `createApplication` once + */ + function once(func) { + var ran, + result; + + return function() { + if (ran) { + return result; + } + ran = true; + result = func.apply(this, arguments); + + // clear the `func` variable so the function may be garbage collected + func = null; + return result; + }; + } + + /** + * Creates a function that, when called, invokes `func` with any additional + * `partial` arguments prepended to those passed to the new function. This + * method is similar to `_.bind`, except it does **not** alter the `this` binding. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to partially apply arguments to. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new partially applied function. + * @example + * + * var greet = function(greeting, name) { return greeting + ' ' + name; }; + * var hi = _.partial(greet, 'hi'); + * hi('moe'); + * // => 'hi moe' + */ + function partial(func) { + return createBound(func, nativeSlice.call(arguments, 1)); + } + + /** + * This method is similar to `_.partial`, except that `partial` arguments are + * appended to those passed to the new function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to partially apply arguments to. + * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @returns {Function} Returns the new partially applied function. + * @example + * + * var defaultsDeep = _.partialRight(_.merge, _.defaults); + * + * var options = { + * 'variable': 'data', + * 'imports': { 'jq': $ } + * }; + * + * defaultsDeep(options, _.templateSettings); + * + * options.variable + * // => 'data' + * + * options.imports + * // => { '_': _, 'jq': $ } + */ + function partialRight(func) { + return createBound(func, nativeSlice.call(arguments, 1), null, indicatorObject); + } + + /** + * Creates a function that, when executed, will only call the `func` function + * at most once per every `wait` milliseconds. Pass an `options` object to + * indicate that `func` should be invoked on the leading and/or trailing edge + * of the `wait` timeout. Subsequent calls to the throttled function will + * return the result of the last `func` call. + * + * Note: If `leading` and `trailing` options are `true`, `func` will be called + * on the trailing edge of the timeout only if the the throttled function is + * invoked more than once during the `wait` timeout. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to throttle. + * @param {Number} wait The number of milliseconds to throttle executions to. + * @param {Object} options The options object. + * [leading=true] A boolean to specify execution on the leading edge of the timeout. + * [trailing=true] A boolean to specify execution on the trailing edge of the timeout. + * @returns {Function} Returns the new throttled function. + * @example + * + * var throttled = _.throttle(updatePosition, 100); + * jQuery(window).on('scroll', throttled); + * + * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, { + * 'trailing': false + * })); + */ + function throttle(func, wait, options) { + var leading = true, + trailing = true; + + if (options === false) { + leading = false; + } else if (isObject(options)) { + leading = 'leading' in options ? options.leading : leading; + trailing = 'trailing' in options ? options.trailing : trailing; + } + options = getObject(); + options.leading = leading; + options.maxWait = wait; + options.trailing = trailing; + + var result = debounce(func, wait, options); + releaseObject(options); + return result; + } + + /** + * Creates a function that passes `value` to the `wrapper` function as its + * first argument. Additional arguments passed to the function are appended + * to those passed to the `wrapper` function. The `wrapper` is executed with + * the `this` binding of the created function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Mixed} value The value to wrap. + * @param {Function} wrapper The wrapper function. + * @returns {Function} Returns the new function. + * @example + * + * var hello = function(name) { return 'hello ' + name; }; + * hello = _.wrap(hello, function(func) { + * return 'before, ' + func('moe') + ', after'; + * }); + * hello(); + * // => 'before, hello moe, after' + */ + function wrap(value, wrapper) { + return function() { + var args = [value]; + push.apply(args, arguments); + return wrapper.apply(this, args); + }; + } + + /*--------------------------------------------------------------------------*/ + + /** + * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their + * corresponding HTML entities. + * + * @static + * @memberOf _ + * @category Utilities + * @param {String} string The string to escape. + * @returns {String} Returns the escaped string. + * @example + * + * _.escape('Moe, Larry & Curly'); + * // => 'Moe, Larry & Curly' + */ + function escape(string) { + return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar); + } + + /** + * This method returns the first argument passed to it. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Mixed} value Any value. + * @returns {Mixed} Returns `value`. + * @example + * + * var moe = { 'name': 'moe' }; + * moe === _.identity(moe); + * // => true + */ + function identity(value) { + return value; + } + + /** + * Adds functions properties of `object` to the `lodash` function and chainable + * wrapper. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Object} object The object of function properties to add to `lodash`. + * @example + * + * _.mixin({ + * 'capitalize': function(string) { + * return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); + * } + * }); + * + * _.capitalize('moe'); + * // => 'Moe' + * + * _('moe').capitalize(); + * // => 'Moe' + */ + function mixin(object) { + forEach(functions(object), function(methodName) { + var func = lodash[methodName] = object[methodName]; + + lodash.prototype[methodName] = function() { + var value = this.__wrapped__, + args = [value]; + + push.apply(args, arguments); + var result = func.apply(lodash, args); + return (value && typeof value == 'object' && value === result) + ? this + : new lodashWrapper(result); + }; + }); + } + + /** + * Reverts the '_' variable to its previous value and returns a reference to + * the `lodash` function. + * + * @static + * @memberOf _ + * @category Utilities + * @returns {Function} Returns the `lodash` function. + * @example + * + * var lodash = _.noConflict(); + */ + function noConflict() { + context._ = oldDash; + return this; + } + + /** + * Converts the given `value` into an integer of the specified `radix`. + * If `radix` is `undefined` or `0`, a `radix` of `10` is used unless the + * `value` is a hexadecimal, in which case a `radix` of `16` is used. + * + * Note: This method avoids differences in native ES3 and ES5 `parseInt` + * implementations. See http://es5.github.com/#E. + * + * @static + * @memberOf _ + * @category Utilities + * @param {String} value The value to parse. + * @param {Number} [radix] The radix used to interpret the value to parse. + * @returns {Number} Returns the new integer value. + * @example + * + * _.parseInt('08'); + * // => 8 + */ + var parseInt = nativeParseInt(whitespace + '08') == 8 ? nativeParseInt : function(value, radix) { + // Firefox and Opera still follow the ES3 specified implementation of `parseInt` + return nativeParseInt(isString(value) ? value.replace(reLeadingSpacesAndZeros, '') : value, radix || 0); + }; + + /** + * Produces a random number between `min` and `max` (inclusive). If only one + * argument is passed, a number between `0` and the given number will be returned. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Number} [min=0] The minimum possible value. + * @param {Number} [max=1] The maximum possible value. + * @returns {Number} Returns a random number. + * @example + * + * _.random(0, 5); + * // => a number between 0 and 5 + * + * _.random(5); + * // => also a number between 0 and 5 + */ + function random(min, max) { + if (min == null && max == null) { + max = 1; + } + min = +min || 0; + if (max == null) { + max = min; + min = 0; + } else { + max = +max || 0; + } + var rand = nativeRandom(); + return (min % 1 || max % 1) + ? min + nativeMin(rand * (max - min + parseFloat('1e-' + ((rand +'').length - 1))), max) + : min + floor(rand * (max - min + 1)); + } + + /** + * Resolves the value of `property` on `object`. If `property` is a function, + * it will be invoked with the `this` binding of `object` and its result returned, + * else the property value is returned. If `object` is falsey, then `undefined` + * is returned. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Object} object The object to inspect. + * @param {String} property The property to get the value of. + * @returns {Mixed} Returns the resolved value. + * @example + * + * var object = { + * 'cheese': 'crumpets', + * 'stuff': function() { + * return 'nonsense'; + * } + * }; + * + * _.result(object, 'cheese'); + * // => 'crumpets' + * + * _.result(object, 'stuff'); + * // => 'nonsense' + */ + function result(object, property) { + var value = object ? object[property] : undefined; + return isFunction(value) ? object[property]() : value; + } + + /** + * A micro-templating method that handles arbitrary delimiters, preserves + * whitespace, and correctly escapes quotes within interpolated code. + * + * Note: In the development build, `_.template` utilizes sourceURLs for easier + * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl + * + * For more information on precompiling templates see: + * http://lodash.com/#custom-builds + * + * For more information on Chrome extension sandboxes see: + * http://developer.chrome.com/stable/extensions/sandboxingEval.html + * + * @static + * @memberOf _ + * @category Utilities + * @param {String} text The template text. + * @param {Object} data The data object used to populate the text. + * @param {Object} options The options object. + * escape - The "escape" delimiter regexp. + * evaluate - The "evaluate" delimiter regexp. + * interpolate - The "interpolate" delimiter regexp. + * sourceURL - The sourceURL of the template's compiled source. + * variable - The data object variable name. + * @returns {Function|String} Returns a compiled function when no `data` object + * is given, else it returns the interpolated text. + * @example + * + * // using a compiled template + * var compiled = _.template('hello <%= name %>'); + * compiled({ 'name': 'moe' }); + * // => 'hello moe' + * + * var list = '<% _.forEach(people, function(name) { %>
  • <%= name %>
  • <% }); %>'; + * _.template(list, { 'people': ['moe', 'larry'] }); + * // => '
  • moe
  • larry
  • ' + * + * // using the "escape" delimiter to escape HTML in data property values + * _.template('<%- value %>', { 'value': '