<commit_before>/**
 * The file cache class.
 *
 * @class Cache
 */
var fs = require('fs');
var zlib = require('zlib');
var mime = require('mime');
var crypto = require('crypto');
var minifyHtml = require('html-minifier').minify;
var minifyCSS =  require('clean-css');
var UglifyJS = require('uglify-js');
var watch = require("fwatcher");
var html_minifier_options = {
    removeComments: true,
    removeCommentsFromCDATA: true,
    removeCDATASectionsFromCDATA: true,
    removeAttributeQuotes: true,
    removeRedundantAttributes: true,
    removeEmptyAttributes: true,
    removeOptionalTags: true,
    collapseBooleanAttributes: true,
    collapseWhitespace: true
};

// regular expressions
//var removeFingerprint = /\.[^\.]+\.js$/;
var suffix = /\.\w+$/;
var replaceDoubleSlash = /\/{2,}/g;
var checkFp = /\.@[a-z0-9]{7}\.(?:js|css|html)$/i;

// global file cache
var fileCache = engine.pojo('files');

// this is not quaranteed to be stable! why?
zlib.Z_DEFAULT_COMPRESSION = 9;

/**
 * Read a file or get it from the cache.
 *
 * @public
 * @param {string} The file path.
 * @param {function} The callback function.
 * @param {array} Give an array with allowed paths. (optional)
 * @param {string} A string, which is prepended to a path.
 */
var getFile = exports.get = function getFile (file, callback, index) {

    if (typeof file === 'string') {
        file = {path: file};
    }

    // path must be absolute
    if (!file || !file.path || file.path.indexOf('../') !== -1) {
        var err = new Error('Invalid path.');
        console.error('lib/file.js#51', err.toString());
        return callback(err);
    }

    // set default base path to public app folder
    file.base = file.base || engine.paths.app_public;

    // create a clean path without the fingerprint
    file.clean = removeFingerprint(file.path);

    // create absolute path
    file.read = (file.base + file.clean).replace(replaceDoubleSlash, '/');

    // check if file is already in cache
    var cachedFile = fileCache.get(file.read);
    if (cachedFile) {
        return callback(null, cachedFile, index);
    }

    // check if path had fingerprint
    file.fpInPath = file.fpInPath || (file.clean === file.path ? false : true);

    // get file type
    file.type = mime.lookup(file.clean);

    // read the file from the file system
    fs.readFile(file.read, function (err, data) {

        if (err) {
            console.error('lib/file.js#83', err.toString());
            return callback(err);
        }

        // watch for file changes
        if (cachedFile !== null) {
            watch(file.read, function (err) {
                if (err) {
                    return console.error(err);
                }

                // remove cached data on file change, to force reload
                console.log('FILE CHANGE:', file.read);

                // set file item to null
                fileCache.set(file.read, null);
            });
        }

        // minify js, html or css
        switch (file.type) {
            case 'text/html':

                // TODO don't compress as long html imports or binary ws data is available
                file.noCompression = true;
                data = minifyHtml(data.toString(), html_minifier_options);
                break;

            case 'text/css':
                data = new minifyCSS({processImport: false}).minify(data.toString()).styles;
                break;
            case 'application/javascript':

                // wrap javascript files, except the engine client
                if (file.clean !== 'engine.js') {
                    data = "E('" + ((file.wrap || '')  + file.clean).replace(replaceDoubleSlash, '/') + "',function(require,module,exports,global,engine){\n" + data.toString() + "\nreturn module});";
                }

                if (engine.production && (data = UglifyJS.minify(data.toString(), {fromString: true}))) {
                    data = data.code;
                }
                break;
        }

        // create fingerprint
        file.fp = crypto.createHash('md5').update(data).digest('hex').substr(0, 7);

        // add the data to the file object
        file.data = data;

        // don't compress data
        if (file.noCompression) {
            return createCacheObject(file, callback, index);
        }

        zlib.gzip(file.data, function (err, data) {

            if (err) {
                console.error('lib/file.js#218', err.toString());
                return callback(err);
            }

            // update file data with compressed data
            file.data = data;

            createCacheObject(file, callback, index);
        });
    });
};

/**
 * Create an cache object with the data buffer and the http headers.
 *
 * @private
 * @param {string} The file name without the fingerprint.
 * @param {string} The file path wihtout the fingerprint.
 * @param {blob} The file data.
 * @param {string} The mime type.
 * @param {string} The fingerprint.
 * @param {function} The callback function.
 */
function createCacheObject (file, callback, index) {

    // get the modification time
    fs.stat(file.read, function (err, stats) {

        if (err) {
            console.error('lib/file.js#245', err.toString());
            return callback(err);
        }

        // create http headers
        // TODO send not modified for non scrips and css
        file.http = {
            'Vary': 'Accept-Encoding',
            'Cache-Control': 'public, max-age=' + (file.fpInPath ? engine.http.maxAge_fingerprint : engine.http.maxAge),
            'Content-Encoding': 'gzip',
            'Content-Length': file.data.length,
            'Content-Type': file.type,
            'Last-Modified': stats.mtime,
            'Etag': file.fp
        };

        // TODO don't append fingerprints to the markup until it's loaded via html import
        if (file.type !== 'text/html') {

            // handle files with suffix
            if (suffix.test(file.clean)) {

                file.ext = file.path.split('.');
                file.ext.splice(file.ext.length - 1, 0, '@' + file.fp);
                file.ext = file.ext.join('.');

            // handle files without suffix
            } else {
                file.ext = file.path + '.' + '@' + file.fp;
            }
        }

        // add module id to module paths
        file.ext = (file.path[0] !== '/' ? file.wrap : '') + (file.ext || file.path);

        // save zipped data in cache
        fileCache.set(file.read, file);

        // return data
        callback(null, file, index);
    });
}

/**
 * Prepare external component file paths.
 *
 * @public
 * @param {object} The load context, ex a module package.
 * @param {function} The callback function.
 */
exports.prepareComponents = function (loadContext, callback) {

    if (!loadContext || !loadContext.components) {
        var err = new Error('Invalid load context.');
        console.error('lib/file.js#227', err.toString());
        return callback(err);
    }

    // check if there are components to load
    if (
        (!loadContext.components.scripts || !loadContext.components.scripts.length) &&
        (!loadContext.components.markup || !loadContext.components.markup.length) &&
        (!loadContext.components.styles || !loadContext.components.styles.length)
    ) {
        return callback(null, loadContext);
    }

    var count = 0;

    // the prepend path for external module resource urls
    var modulePath = loadContext._id  + '/';

    var handler = function (err, file, index) {

        if (err) {
            console.error('lib/file.js#221', err.toString());
            if (--count === 0) {
                callback(null, loadContext);
            }
            return;
        }

        // update module components paths
        if (loadContext.components[file.compType] && loadContext.components[file.compType][index]) {
            loadContext.components[file.compType][index] = file.ext;
        }

        if (--count === 0) {
            callback(null, loadContext);
        }
    }

    for (var type in loadContext.components) {

        // check if type is allowed
        switch (type) {
            case 'scripts':
            case 'markup':
            case 'styles':
                break;
            default:
              continue;
        }

        // get file paths array
        var files = loadContext.components[type];

        // update count
        count += files.length;

        for (var i = 0, file; i < files.length; ++i) {

            // ignore external paths
            if (files[i].indexOf('//') > -1) {
                if (--count === 0) {
                    callback(null, loadContext);
                }
                continue;
            }

            // create file object
            file = {path: files[i]};

            // save component type to update the components later
            file.compType = type;

            // set a longer cache time for fingerprinted files
            file.fpInPath = true;

            // handle absolute files
            if (file.path[0] === '/') {
                file.base = type === 'markup' ? engine.paths.app_markup : engine.paths.app_public;

            // handle module files
            } else {
                file.base = loadContext._base;
                file.wrap = modulePath;
            }

            // read the file
            getFile(file, handler, i);
        }
    }
}

/**f
 * Remove fingerprint from path
 *
 * @private
 * @param {string} The file path.
 */
function removeFingerprint (path) {

    // remove fingerprint if it's found in path
    if (checkFp.test(path)) {

        // split path
        path = path.split('.');

        // get fingerprint and remove fingerprint from read path
        path.splice(path.length - 2, 1)[0];

        // create path without fingerprint
        path = path.join('.');
    }

    // return path
    return path;
}
<commit_msg>append public file id to external path<commit_after>/**
 * The file cache class.
 *
 * @class Cache
 */
var fs = require('fs');
var zlib = require('zlib');
var mime = require('mime');
var crypto = require('crypto');
var minifyHtml = require('html-minifier').minify;
var minifyCSS =  require('clean-css');
var UglifyJS = require('uglify-js');
var watch = require("fwatcher");
var html_minifier_options = {
    removeComments: true,
    removeCommentsFromCDATA: true,
    removeCDATASectionsFromCDATA: true,
    removeAttributeQuotes: true,
    removeRedundantAttributes: true,
    removeEmptyAttributes: true,
    removeOptionalTags: true,
    collapseBooleanAttributes: true,
    collapseWhitespace: true
};

// regular expressions
//var removeFingerprint = /\.[^\.]+\.js$/;
var suffix = /\.\w+$/;
var replaceDoubleSlash = /\/{2,}/g;
var checkFp = /\.@[a-z0-9]{7}\.(?:js|css|html)$/i;

// global file cache
var fileCache = engine.pojo('files');

// this is not quaranteed to be stable! why?
zlib.Z_DEFAULT_COMPRESSION = 9;

/**
 * Read a file or get it from the cache.
 *
 * @public
 * @param {string} The file path.
 * @param {function} The callback function.
 * @param {array} Give an array with allowed paths. (optional)
 * @param {string} A string, which is prepended to a path.
 */
var getFile = exports.get = function getFile (file, callback, index) {

    if (typeof file === 'string') {
        file = {path: file};
    }

    // path must be absolute
    if (!file || !file.path || file.path.indexOf('../') !== -1) {
        var err = new Error('Invalid path.');
        console.error('lib/file.js#51', err.toString());
        return callback(err);
    }

    // set default base path to public app folder
    file.base = file.base || engine.paths.app_public;

    // create a clean path without the fingerprint
    file.clean = removeFingerprint(file.path);

    // create absolute path
    file.read = (file.base + file.clean).replace(replaceDoubleSlash, '/');

    // check if file is already in cache
    var cachedFile = fileCache.get(file.read);
    if (cachedFile) {
        return callback(null, cachedFile, index);
    }

    // check if path had fingerprint
    file.fpInPath = file.fpInPath || (file.clean === file.path ? false : true);

    // get file type
    file.type = mime.lookup(file.clean);

    // read the file from the file system
    fs.readFile(file.read, function (err, data) {

        if (err) {
            console.error('lib/file.js#83', err.toString());
            return callback(err);
        }

        // watch for file changes
        if (cachedFile !== null) {
            watch(file.read, function (err) {
                if (err) {
                    return console.error(err);
                }

                // remove cached data on file change, to force reload
                console.log('FILE CHANGE:', file.read);

                // set file item to null
                fileCache.set(file.read, null);
            });
        }

        // minify js, html or css
        switch (file.type) {
            case 'text/html':

                // TODO don't compress as long html imports or binary ws data is available
                file.noCompression = true;
                data = minifyHtml(data.toString(), html_minifier_options);
                break;

            case 'text/css':
                data = new minifyCSS({processImport: false}).minify(data.toString()).styles;
                break;
            case 'application/javascript':

                // wrap javascript files, except the engine client
                if (file.clean !== 'engine.js') {
                    data = "E('" + ((file.wrap || '')  + file.clean).replace(replaceDoubleSlash, '/') + "',function(require,module,exports,global,engine){\n" + data.toString() + "\nreturn module});";
                }

                if (engine.production && (data = UglifyJS.minify(data.toString(), {fromString: true}))) {
                    data = data.code;
                }
                break;
        }

        // create fingerprint
        file.fp = crypto.createHash('md5').update(data).digest('hex').substr(0, 7);

        // add the data to the file object
        file.data = data;

        // don't compress data
        if (file.noCompression) {
            return createCacheObject(file, callback, index);
        }

        zlib.gzip(file.data, function (err, data) {

            if (err) {
                console.error('lib/file.js#218', err.toString());
                return callback(err);
            }

            // update file data with compressed data
            file.data = data;

            createCacheObject(file, callback, index);
        });
    });
};

/**
 * Create an cache object with the data buffer and the http headers.
 *
 * @private
 * @param {string} The file name without the fingerprint.
 * @param {string} The file path wihtout the fingerprint.
 * @param {blob} The file data.
 * @param {string} The mime type.
 * @param {string} The fingerprint.
 * @param {function} The callback function.
 */
function createCacheObject (file, callback, index) {

    // get the modification time
    fs.stat(file.read, function (err, stats) {

        if (err) {
            console.error('lib/file.js#245', err.toString());
            return callback(err);
        }

        // create http headers
        // TODO send not modified for non scrips and css
        file.http = {
            'Vary': 'Accept-Encoding',
            'Cache-Control': 'public, max-age=' + (file.fpInPath ? engine.http.maxAge_fingerprint : engine.http.maxAge),
            'Content-Encoding': 'gzip',
            'Content-Length': file.data.length,
            'Content-Type': file.type,
            'Last-Modified': stats.mtime,
            'Etag': file.fp
        };

        // TODO don't append fingerprints to the markup until it's loaded via html import
        if (file.type !== 'text/html') {

            // handle files with suffix
            if (suffix.test(file.clean)) {

                file.ext = file.path.split('.');

                // add public file identifier to path
                if (file.ext[0][0] === '/') {
                    file.ext[0] = '/!' + file.ext[0];
                }

                file.ext.splice(file.ext.length - 1, 0, '@' + file.fp);
                file.ext = file.ext.join('.');

            // handle files without suffix
            } else {
                file.ext = file.path + '.' + '@' + file.fp;
            }
        }

        // add module id to module paths
        file.ext = (file.path[0] !== '/' ? file.wrap : '') + (file.ext || file.path);

        // save zipped data in cache
        fileCache.set(file.read, file);

        // return data
        callback(null, file, index);
    });
}

/**
 * Prepare external component file paths.
 *
 * @public
 * @param {object} The load context, ex a module package.
 * @param {function} The callback function.
 */
exports.prepareComponents = function (loadContext, callback) {

    if (!loadContext || !loadContext.components) {
        var err = new Error('Invalid load context.');
        console.error('lib/file.js#227', err.toString());
        return callback(err);
    }

    // check if there are components to load
    if (
        (!loadContext.components.scripts || !loadContext.components.scripts.length) &&
        (!loadContext.components.markup || !loadContext.components.markup.length) &&
        (!loadContext.components.styles || !loadContext.components.styles.length)
    ) {
        return callback(null, loadContext);
    }

    var count = 0;

    // the prepend path for external module resource urls
    var modulePath = loadContext._id  + '/';

    var handler = function (err, file, index) {

        if (err) {
            console.error('lib/file.js#247', err.toString());
            if (--count === 0) {
                callback(null, loadContext);
            }
            return;
        }

        // update module components paths
        if (loadContext.components[file.compType] && loadContext.components[file.compType][index]) {
            loadContext.components[file.compType][index] = file.ext;
        }

        if (--count === 0) {
            callback(null, loadContext);
        }
    }

    for (var type in loadContext.components) {

        // check if type is allowed
        switch (type) {
            case 'scripts':
            case 'markup':
            case 'styles':
                break;
            default:
              continue;
        }

        // get file paths array
        var files = loadContext.components[type];

        // update count
        count += files.length;

        for (var i = 0, file; i < files.length; ++i) {

            // ignore external paths
            if (files[i].indexOf('//') > -1) {
                if (--count === 0) {
                    callback(null, loadContext);
                }
                continue;
            }

            // create file object
            file = {path: files[i]};

            // save component type to update the components later
            file.compType = type;

            // set a longer cache time for fingerprinted files
            file.fpInPath = true;

            // handle absolute files
            if (file.path[0] === '/') {
                file.base = type === 'markup' ? engine.paths.app_markup : engine.paths.app_public;

            // handle module files
            } else {
                file.base = loadContext._base;
                file.wrap = modulePath;
            }

            // read the file
            getFile(file, handler, i);
        }
    }
}

/**f
 * Remove fingerprint from path
 *
 * @private
 * @param {string} The file path.
 */
function removeFingerprint (path) {

    // remove fingerprint if it's found in path
    if (checkFp.test(path)) {

        // split path
        path = path.split('.');

        // get fingerprint and remove fingerprint from read path
        path.splice(path.length - 2, 1)[0];

        // create path without fingerprint
        path = path.join('.');
    }

    // return path
    return path;
}
<|endoftext|>


<commit_before>/* jslint node: true, esnext: true */
"use strict";

const BaseStep = require('kronos-step').Step;

const ATTRIBUTES = ['description'];
/**
 * This is the flow implementation.
 * It holds all the steps.
 */
const Flow = {
	"name": "kronos-flow",
	"description": "General step collection",

	/**
	 * Declares the following properties:
	 * steps
	 * autostart
	 * @param manager {object} The kronos-service-manager
	 * @param name {string} The name of this step
	 * @param stepDefinition {object} The definition used to create the step
	 * @param props {object} The properties of the new object
	 */
	initialize(manager, name, stepDefinition, props) {

		props.autostart = {
			value: stepDefinition.autostart || false
		};

		// All steps
		const steps = new Map();

		//----------------------------------------------
		//-- Create the sub steps
		//----------------------------------------------
		for (const subStepName in stepDefinition.steps) {
			const subStepDefinition = stepDefinition.steps[subStepName];
			subStepDefinition.name = subStepName;
			const createdStep = manager.createStepInstanceFromConfig(subStepDefinition, manager);
			if (!createdStep) {
				throw new Error(`The step '${subStepName}' in the flow '${this.name}' could not been ceated.`);
			}
			steps.set(subStepName, createdStep);
		}

		props.steps = {
			value: steps
		};
	},

	/**
	 * The flow has no real endpoints. It only has proxies.
	 * So just return the configuration
	 * @param {Object} stepDefinition The step configuration
	 */
	createEndpoints(stepDefinition) {
		if (stepDefinition && stepDefinition.endpoints) {
			for (const endpointName in stepDefinition.endpoints) {
				this.endpoints[endpointName] = stepDefinition.endpoints[endpointName];
			}
		}
		this._connectEndpoints(stepDefinition);
		this._assignLogger(stepDefinition);
	},

	/**
	 * Find endpoint for given expression
	 * @param {string} expression
	 * @param {array[string]} problems
	 * @return {Endpoint} found endpoint
	 */
	endpointFor(expression, problems) {
		let res = expression.match(/^(.+):(.+)/); // service:endpoint

		if (res) {
			const service = this.manager.services[res[1]];
			if (service) {
				const target = service.endpoints[res[2]];
				if (target) {
					return target;
				}
				problems.push(`Endpoint '${res[2]}' of service ${res[1]} not found`);
			} else {
				problems.push(`Service '${res[1]}' not found`);
			}
		} else {
			res = expression.match(/^(.+)\/(.+)/);
			if (res) { // step/endpoint
				const step = this.steps.get(res[1]);
				if (step) {
					const target = step.endpoints[res[2]];
					if (target) {
						return target;
					}
					problems.push(`Endpoint '${res[2]}' of step ${res[1]} not found`);
				} else {
					problems.push(`Step '${res[1]}' not found`);
				}
			}
		}

		return undefined;
	},

	_connectEndpoints(stepDefinition) {
		const steps = this.steps;
		const name = this.name;
		const endpoints = this.endpoints;
		//----------------------------------------------
		//-- set the target endpoints
		//----------------------------------------------
		for (const subStepName in stepDefinition.steps) {
			const step = steps.get(subStepName);

			const subStepDefinition = stepDefinition.steps[subStepName];

			if (subStepDefinition.endpoints) {
				for (const endpointName in subStepDefinition.endpoints) {
					const endpointConfig = subStepDefinition.endpoints[endpointName];

					const endpoint = step.endpoints[endpointName];
					const target = typeof endpointConfig === 'string' ? endpointConfig : endpointConfig.target;

					if (typeof target === 'string') {
						const problems = [];
						const targetEndpoint = this.endpointFor(target, problems);

						if (targetEndpoint) {
							endpoint.connected = targetEndpoint;
						} else {
							if (step.type === 'kronos-flow') {
								// The target is a step of the flow itself. This has been handled
								// before as the flow was created. ???
							} else {
								const mandatory = endpointConfig === 'string' ? true : endpointConfig.mandatory === undefined ? true : false;
								if (mandatory) {
									throw new Error(`While evaluating '${target}' ${problems.join(',')}`);
								}
								this.info(level =>
									`Remove optional endpoint ${endpointName} since it can't be connected: ${problems.join(',')}`);
								step.removeEndpoint(endpointName);
							}
						}
					}
				}
			}
		}

		//----------------------------------------------
		//-- get the original endpoints for the Flow.
		//----------------------------------------------
		/*
		 * A flow has only endpoint proxies. These will be replaced by the original endpoints
		 * of the sub steps
		 */
		let res;
		for (const endpointName in endpoints) {

			const endpoint = endpoints[endpointName];
			if (typeof endpoint === 'string') {
				if (res = endpoint.match(/^(.+)\/(.+)/)) { // optional step:
					const targetStepName = res[1];
					const targetEndpointName = res[2];
					let targetStep = steps.get(targetStepName);

					if (!targetStep) {
						// test if the step refernces this current flow.
						if (targetStepName === name) {
							targetStep = this;
						}
					}

					if (targetStep) {
						const targetEndpoint = targetStep.endpoints[targetEndpointName];
						if (targetEndpoint) {
							endpoints[endpointName] = targetEndpoint;
							targetEndpoint.step = targetStep;
						} else {
							throw new Error(`Target endpoint '${targetEndpointName}' not found in step '${targetStepName}'`);
						}
					} else {
						throw new Error(`Target step '${targetStepName}' not found`);
					}
				} else {
					throw new Error(`Endpoint target '${endpoint}' of endpoint '${endpointName}' is not of right format`);
				}
			} else {
				if (!endpoint.default) {
					throw new Error(`Flow endpoint '${endpointName}' in flow '${name}' is not of type 'string'`);
				}
			}
		}

	},

	/**
	 * Assign a logger endpoint to each of the steps in the flow.
	 * It will only assign the default logger if the log endPoint is not
	 * already connected.
	 */
	_assignLogger() {
		// Check if there is a logger services
		if (this.manager.services.logger) {
			const logEndpoint = this.manager.services.logger.log;

			for (const step of this.steps.values()) {
				if (!step.endpoints.log.isConnected) {
					step.endpoints.log.connected = logEndpoint;
				}
			}
		}
	},

	_start() {
		const sbt = stepsByType(this.steps);
		return Promise.all(sbt.normalSteps.map(s => s.start())).then(() =>
			Promise.all(sbt.inboundSteps.map(s => s.start()))
		);
	},

	_stop() {
		const sbt = stepsByType(this.steps);
		return Promise.all(sbt.inboundSteps.map(s => s.stop())).then(() =>
			Promise.all(sbt.normalSteps.map(s => s.stop()))
		);
	},

	_remove() {
		return Promise.all(mapForEachValue(this.steps, s => s.remove()));
	},

	toJSONWithOptions(options) {
		const json = {
			type: this.type,
			endpoints: {}
		};

		if (options.includeName) {
			json.name = this.name;
		}

		if (options.includeRuntimeInfo) {
			json.state = this.state;
		}

		for (const endpointName in this.endpoints) {
			const currentEndpoint = this.endpoints[endpointName];
			if (!currentEndpoint.default || options.includeDefaults) {

				//json.endpoints[endpointName] = `${currentEndpoint.step.name/${currentEndpoint.name}`;
			}
		}

		// if (options.includeDefaults) {
		ATTRIBUTES.forEach(a => json[a] = this[a]);

		json.steps = {};
		for (const subStepName of this.steps.values()) {
			json.steps[subStepName.name] = subStepName.toJSONWithOptions(options);
		}

		return json;
	}
};

function stepsByType(steps) {
	// The inbound steps will be stopped first
	const normalSteps = [];
	const inboundSteps = [];

	// Sort the steps
	for (const subStep of steps.values()) {
		if (subStep.inbound) {
			inboundSteps.push(subStep);
		} else {
			normalSteps.push(subStep);
		}
	}

	return {
		normalSteps: normalSteps,
		inboundSteps: inboundSteps
	};
}

/**
 * Calls function on each map entry and delivers function reults as an iterable.
 * @param {Map} map
 * @param {Function} f
 * @return {Iterator} result of calling f on each map entry
 * @api private
 */
function mapForEachValue(map, f) {
	const r = [];

	for (const i of map.values()) {
		r.push(f(i));
	}
	return r;
}

/**
 * Load a flow json files and created the flows from it.
 * The flows will be registerd at the manager
 * @param manager {object} The kronos-service-manager
 * @param jsonFlowDefinition {object} The flow definition
 * @return promise
 */
function loadFlows(manager, jsonFlowDefinition) {
	if (!manager) {
		throw new Error("The manager is mandatory");
	}

	const promises = [];

	for (const flowName in jsonFlowDefinition) {
		const singleFlowDefinition = jsonFlowDefinition[flowName];

		singleFlowDefinition.type = "kronos-flow";
		singleFlowDefinition.name = flowName;

		const flow = manager.createStepInstanceFromConfig(singleFlowDefinition, manager);
		promises.push(manager.registerStep(flow));
		promises.push(manager.registerFlow(flow));
	}

	return Promise.all(promises);
}

module.exports.FlowFactory = Object.assign({}, BaseStep, Flow);
module.exports.loadFlows = loadFlows;
<commit_msg>refactor: reduce variable scope a bit<commit_after>/* jslint node: true, esnext: true */
"use strict";

const BaseStep = require('kronos-step').Step;

const ATTRIBUTES = ['description'];
/**
 * This is the flow implementation.
 * It holds all the steps.
 */
const Flow = {
	"name": "kronos-flow",
	"description": "General step collection",

	/**
	 * Declares the following properties:
	 * steps
	 * autostart
	 * @param manager {object} The kronos-service-manager
	 * @param name {string} The name of this step
	 * @param stepDefinition {object} The definition used to create the step
	 * @param props {object} The properties of the new object
	 */
	initialize(manager, name, stepDefinition, props) {

		props.autostart = {
			value: stepDefinition.autostart || false
		};

		// All steps
		const steps = new Map();

		//----------------------------------------------
		//-- Create the sub steps
		//----------------------------------------------
		for (const subStepName in stepDefinition.steps) {
			const subStepDefinition = stepDefinition.steps[subStepName];
			subStepDefinition.name = subStepName;
			const createdStep = manager.createStepInstanceFromConfig(subStepDefinition, manager);
			if (!createdStep) {
				throw new Error(`The step '${subStepName}' in the flow '${this.name}' could not been ceated.`);
			}
			steps.set(subStepName, createdStep);
		}

		props.steps = {
			value: steps
		};
	},

	/**
	 * The flow has no real endpoints. It only has proxies.
	 * So just return the configuration
	 * @param {Object} stepDefinition The step configuration
	 */
	createEndpoints(stepDefinition) {
		if (stepDefinition && stepDefinition.endpoints) {
			for (const endpointName in stepDefinition.endpoints) {
				this.endpoints[endpointName] = stepDefinition.endpoints[endpointName];
			}
		}
		this._connectEndpoints(stepDefinition);
		this._assignLogger(stepDefinition);
	},

	/**
	 * Find endpoint for given expression
	 * @param {string} expression
	 * @param {array[string]} problems
	 * @return {Endpoint} found endpoint
	 */
	endpointFor(expression, problems) {
		let res = expression.match(/^(.+):(.+)/); // service:endpoint

		if (res) {
			const service = this.manager.services[res[1]];
			if (service) {
				const target = service.endpoints[res[2]];
				if (target) {
					return target;
				}
				problems.push(`Endpoint '${res[2]}' of service ${res[1]} not found`);
			} else {
				problems.push(`Service '${res[1]}' not found`);
			}
		} else {
			res = expression.match(/^(.+)\/(.+)/);
			if (res) { // step/endpoint
				const step = this.steps.get(res[1]);
				if (step) {
					const target = step.endpoints[res[2]];
					if (target) {
						return target;
					}
					problems.push(`Endpoint '${res[2]}' of step ${res[1]} not found`);
				} else {
					problems.push(`Step '${res[1]}' not found`);
				}
			}
		}

		return undefined;
	},

	_connectEndpoints(stepDefinition) {
		const steps = this.steps;
		const name = this.name;
		const endpoints = this.endpoints;
		//----------------------------------------------
		//-- set the target endpoints
		//----------------------------------------------
		for (const subStepName in stepDefinition.steps) {
			const step = steps.get(subStepName);

			const subStepDefinition = stepDefinition.steps[subStepName];

			if (subStepDefinition.endpoints) {
				for (const endpointName in subStepDefinition.endpoints) {
					const endpointConfig = subStepDefinition.endpoints[endpointName];

					const target = typeof endpointConfig === 'string' ? endpointConfig : endpointConfig.target;

					if (typeof target === 'string') {
						const problems = [];
						const targetEndpoint = this.endpointFor(target, problems);

						if (targetEndpoint) {
							const endpoint = step.endpoints[endpointName];
							endpoint.connected = targetEndpoint;
						} else {
							if (step.type === 'kronos-flow') {
								// The target is a step of the flow itself. This has been handled
								// before as the flow was created. ???
							} else {
								const mandatory = endpointConfig === 'string' ? true : endpointConfig.mandatory === undefined ? true : false;
								if (mandatory) {
									throw new Error(`While evaluating '${target}' ${problems.join(',')}`);
								}
								this.info(level =>
									`Remove optional endpoint ${endpointName} since it can't be connected: ${problems.join(',')}`);
								step.removeEndpoint(endpointName);
							}
						}
					}
				}
			}
		}

		//----------------------------------------------
		//-- get the original endpoints for the Flow.
		//----------------------------------------------
		/*
		 * A flow has only endpoint proxies. These will be replaced by the original endpoints
		 * of the sub steps
		 */
		for (const endpointName in endpoints) {
			const endpoint = endpoints[endpointName];

			if (typeof endpoint === 'string') {
				const res = endpoint.match(/^(.+)\/(.+)/);
				if (res) { // optional step:
					const targetStepName = res[1];
					const targetEndpointName = res[2];
					let targetStep = steps.get(targetStepName);

					if (!targetStep) {
						// test if the step refernces this current flow.
						if (targetStepName === name) {
							targetStep = this;
						}
					}

					if (targetStep) {
						const targetEndpoint = targetStep.endpoints[targetEndpointName];
						if (targetEndpoint) {
							endpoints[endpointName] = targetEndpoint;
							targetEndpoint.step = targetStep;
						} else {
							throw new Error(`Target endpoint '${targetEndpointName}' not found in step '${targetStepName}'`);
						}
					} else {
						throw new Error(`Target step '${targetStepName}' not found`);
					}
				} else {
					throw new Error(`Endpoint target '${endpoint}' of endpoint '${endpointName}' is not of right format`);
				}
			} else {
				if (!endpoint.default) {
					throw new Error(`Flow endpoint '${endpointName}' in flow '${name}' is not of type 'string'`);
				}
			}
		}
	},

	/**
	 * Assign a logger endpoint to each of the steps in the flow.
	 * It will only assign the default logger if the log endPoint is not
	 * already connected.
	 */
	_assignLogger() {
		// Check if there is a logger services
		if (this.manager.services.logger) {
			const logEndpoint = this.manager.services.logger.log;

			for (const step of this.steps.values()) {
				if (!step.endpoints.log.isConnected) {
					step.endpoints.log.connected = logEndpoint;
				}
			}
		}
	},

	_start() {
		const sbt = stepsByType(this.steps);
		return Promise.all(sbt.normalSteps.map(s => s.start())).then(() =>
			Promise.all(sbt.inboundSteps.map(s => s.start()))
		);
	},

	_stop() {
		const sbt = stepsByType(this.steps);
		return Promise.all(sbt.inboundSteps.map(s => s.stop())).then(() =>
			Promise.all(sbt.normalSteps.map(s => s.stop()))
		);
	},

	_remove() {
		return Promise.all(mapForEachValue(this.steps, s => s.remove()));
	},

	toJSONWithOptions(options) {
		const json = {
			type: this.type,
			endpoints: {}
		};

		if (options.includeName) {
			json.name = this.name;
		}

		if (options.includeRuntimeInfo) {
			json.state = this.state;
		}

		for (const endpointName in this.endpoints) {
			const currentEndpoint = this.endpoints[endpointName];
			if (!currentEndpoint.default || options.includeDefaults) {

				//json.endpoints[endpointName] = `${currentEndpoint.step.name/${currentEndpoint.name}`;
			}
		}

		// if (options.includeDefaults) {
		ATTRIBUTES.forEach(a => json[a] = this[a]);

		json.steps = {};
		for (const subStepName of this.steps.values()) {
			json.steps[subStepName.name] = subStepName.toJSONWithOptions(options);
		}

		return json;
	}
};

function stepsByType(steps) {
	// The inbound steps will be stopped first
	const normalSteps = [];
	const inboundSteps = [];

	// Sort the steps
	for (const subStep of steps.values()) {
		if (subStep.inbound) {
			inboundSteps.push(subStep);
		} else {
			normalSteps.push(subStep);
		}
	}

	return {
		normalSteps: normalSteps,
		inboundSteps: inboundSteps
	};
}

/**
 * Calls function on each map entry and delivers function reults as an iterable.
 * @param {Map} map
 * @param {Function} f
 * @return {Iterator} result of calling f on each map entry
 * @api private
 */
function mapForEachValue(map, f) {
	const r = [];

	for (const i of map.values()) {
		r.push(f(i));
	}
	return r;
}

/**
 * Load a flow json files and created the flows from it.
 * The flows will be registerd at the manager
 * @param manager {object} The kronos-service-manager
 * @param jsonFlowDefinition {object} The flow definition
 * @return promise
 */
function loadFlows(manager, jsonFlowDefinition) {
	if (!manager) {
		throw new Error("The manager is mandatory");
	}

	const promises = [];

	for (const flowName in jsonFlowDefinition) {
		const singleFlowDefinition = jsonFlowDefinition[flowName];

		singleFlowDefinition.type = "kronos-flow";
		singleFlowDefinition.name = flowName;

		const flow = manager.createStepInstanceFromConfig(singleFlowDefinition, manager);
		promises.push(manager.registerStep(flow));
		promises.push(manager.registerFlow(flow));
	}

	return Promise.all(promises);
}

module.exports.FlowFactory = Object.assign({}, BaseStep, Flow);
module.exports.loadFlows = loadFlows;
<|endoftext|>


<commit_before>var assert = process.env.HTTP2_ASSERT ? require('assert') : function noop() {};

// The Flow class
// ==============

// Flow is a [Duplex stream][1] subclass which implements HTTP/2 flow control. It is designed to be
// subclassed by [Connection](connection.html) and the `upstream` component of [Stream](stream.html).
// [1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex

var Duplex  = require('stream').Duplex;

exports.Flow = Flow;

// Public API
// ----------

// * **Event: 'error' (type)**: signals an error
//
// * **setInitialWindow(size)**: the initial flow control window size can be changed *any time*
//   ([as described in the standard][1]) using this method
//
// * **disableRemoteFlowControl()**: sends a WINDOW_UPDATE signaling that we don't want flow control
//
// * **disableLocalFlowControl()**: disables flow control for outgoing frames
//
// [1]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-04#section-6.9.2

// API for child classes
// ---------------------

// * **new Flow([flowControlId])**: creating a new flow that will listen for WINDOW_UPDATES frames
//   with the given `flowControlId` (or every update frame if not given)
//
// * **Event: 'receiving' (frame)**: there's an incoming frame
//
// * **Event: 'sending' (frame)**: a frame was added to the output queue
//
// * **_send()**: called when more frames should be pushed. The child class is expected to override
//   this (instead of the `_read` method of the Duplex class).
//
// * **_receive(frame, readyCallback)**: called when there's an incoming frame. The child class is
//   expected to override this (instead of the `_write` method of the Duplex class).
//
// * **push(frame): bool**: schedules `frame` for sending.
//
//   Returns `true` if it needs more frames in the output queue, `false` if the output queue is
//   full, and `null` if did not push the frame into the output queue (instead, it pushed it into
//   the flow control queue).
//
// * **read(limit): frame**: like the regular `read`, but the 'flow control size' (0 for non-DATA
//   frames, length of the payload for DATA frames) of the returned frame will be under `limit`.
//   Small exception: pass -1 as `limit` if the max. flow control size is 0. `read(0)` means the
//   same thing as [in the original API](http://nodejs.org/api/stream.html#stream_stream_read_0).
//
// * **getLastQueuedFrame(): frame**: returns the last frame in output buffers
//
// * **_log**: the Flow class uses the `_log` object of the parent

// Constructor
// -----------

// When a HTTP/2.0 connection is first established, new streams are created with an initial flow
// control window size of 65535 bytes.
var INITIAL_WINDOW_SIZE = 65535;

// `flowControlId` is needed if only specific WINDOW_UPDATEs should be watched.
function Flow(flowControlId) {
  Duplex.call(this, { objectMode: true });

  this._window = this._initialWindow = INITIAL_WINDOW_SIZE;
  this._flowControlId = flowControlId;
  this._queue = [];
  this._ended = false;
  this._received = 0;
  this._remoteFlowControlDisabled = false;

  this._queue.unshift = this._queue.unshift.bind(this._queue);
  this._queue.push = this._queue.push.bind(this._queue);
  this._read = this._read.bind(this);
}
Flow.prototype = Object.create(Duplex.prototype, { constructor: { value: Flow } });

// Incoming frames
// ---------------

// `_receive` is called when there's an incoming frame.
Flow.prototype._receive = function _receive(frame, callback) {
  throw new Error('The _receive(frame, callback) method has to be overridden by the child class!');
};

// `_receive` is called by `_write` which in turn is [called by Duplex][1] when someone `write()`s
// to the flow. It emits the 'receiving' event and notifies the window size tracking code if the
// incoming frame is a WINDOW_UPDATE.
// [1]: http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
Flow.prototype._write = function _write(frame, encoding, callback) {
  this.emit('receiving', frame);

  if (frame.flags.END_STREAM) {
    this._ended = true;
  }

  if ((frame.type === 'DATA') && (frame.data.length > 0) && !this._remoteFlowControlDisabled) {
    this._receive(frame, function() {
      this._received += frame.data.length;
      if (!this._restoreWindowTimer) {
        this._restoreWindowTimer = setImmediate(this._restoreWindow.bind(this));
      }
      callback();
    }.bind(this));
  }

  else {
    this._receive(frame, callback);
  }

  if ((frame.type === 'WINDOW_UPDATE') &&
      ((this._flowControlId === undefined) || (frame.stream === this._flowControlId))) {
    this._updateWindow(frame);
  }
};

// `_restoreWindow` basically acknowledges the DATA frames received since it's last call. It sends
// a WINDOW_UPDATE that restores the flow control window of the remote end.
Flow.prototype._restoreWindow = function _restoreWindow() {
  delete this._restoreWindowTimer;
  if (!this._ended && !this._remoteFlowControlDisabled && (this._received > 0)) {
    this.push({
      type: 'WINDOW_UPDATE',
      stream: this._flowControlId,
      window_size: this._received
    });
    this._received = 0;
  }
};

// Must be called after sending a SETTINGS frame that turns off flow control on the remote side.
Flow.prototype.disableRemoteFlowControl = function disableRemoteFlowControl() {
  this._log.debug('Turning off remote flow control');
  this._remoteFlowControlDisabled = true;
};

// Outgoing frames - sending procedure
// -----------------------------------

//                                         flow
//                +-------------------------------------------------+
//                |                                                 |
//                +--------+           +---------+                  |
//        read()  | output |  _read()  | flow    |  _send()         |
//     <----------|        |<----------| control |<-------------    |
//                | buffer |           | buffer  |                  |
//                +--------+           +---------+                  |
//                | input  |                                        |
//     ---------->|        |----------------------------------->    |
//       write()  | buffer |  _write()              _receive()      |
//                +--------+                                        |
//                |                                                 |
//                +-------------------------------------------------+

// `_send` is called when more frames should be pushed to the output buffer.
Flow.prototype._send = function _send() {
  throw new Error('The _send() method has to be overridden by the child class!');
};

// `_send` is called by `_read` which is in turn [called by Duplex][1] when it wants to have more
// items in the output queue.
// [1]: http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
Flow.prototype._read = function _read() {
  // * if the flow control queue is empty, then let the user push more frames
  if (this._queue.length === 0) {
    this._send();
  }

  // * if there are items in the flow control queue, then let's put them into the output queue (to
  //   the extent it is possible with respect to the window size and output queue feedback)
  else if (this._window > 0) {
    this._readableState.sync = true; // to avoid reentrant calls
    var frame;
    var moreNeeded = true;
    while (moreNeeded && (frame = this._queue.shift())) {
      moreNeeded = this._push(frame, this._queue.unshift);
    }
    this._readableState.sync = false;

    assert((moreNeeded == false) ||                              // * output queue is full
           (this._queue.length === 0) ||                         // * flow control queue is empty
           (!this._window && (this._queue[0].type === 'DATA'))); // * waiting for window update
  }

  // * otherwise, come back when the flow control window is positive
  else {
    this.once('window_update', this._read);
  }
};

var MAX_PAYLOAD_SIZE = 4096; // Must not be greater than MAX_HTTP_PAYLOAD_SIZE which is 16383

// `read(limit)` is like the `read` of the Readable class, but it guarantess that the 'flow control
// size' (0 for non-DATA frames, length of the payload for DATA frames) of the returned frame will
// be under `limit`.
Flow.prototype.read = function read(limit) {
  if (limit === 0) {
    return Duplex.prototype.read.call(this, 0);
  } else if (limit === -1) {
    limit = 0;
  } else if ((limit === undefined) || (limit > MAX_PAYLOAD_SIZE)) {
    limit = MAX_PAYLOAD_SIZE;
  }

  var frame = Duplex.prototype.read.call(this);

  if ((frame === null) || (frame.type !== 'DATA') || (frame.data.length <= limit)) {
    return frame;
  }

  else if (limit <= 0) {
    this.unshift(frame);
    return null;
  }

  else {
    this._log.trace({ frame: frame, size: frame.data.length, forwardable: limit },
                    'Splitting out forwardable part of a DATA frame.');
    var forwardable = {
      stream: frame.stream,
      type: 'DATA',
      flags: {},
      data: frame.data.slice(0, limit)
    };
    frame.data = frame.data.slice(limit);

    this.unshift(frame);
    return forwardable;
  }
};

// `_push(frame)` It pushes `frame` into the output queue and decreases the flow control window
// size. It is capable of splitting DATA frames into smaller parts, if the window size is not enough
// to push the whole frame. It calls `remainderCallback` synchronously with the frame it was not
// able to push to the output queue. The remainder may be the whole frame or the remaining part of a
// DATA frame. The return value is similar to `push` except that it returns `null` if it did not
// push anything to the output queue.

Flow.prototype._push = function _push(frame, remainderCallback) {
  var forwardable = undefined
  var remainder = undefined;

  if ((frame === null) || (frame.type !== 'DATA') || (frame.data.length <= this._window)) {
    forwardable = frame;
  }

  else if (this._window <= 0) {
    remainder = frame;
  }

  else {
    this._log.trace({ frame: frame, size: frame.data.length, forwardable: this._window },
                    'Splitting out forwardable part of a DATA frame.');
    forwardable = {
      stream: frame.stream,
      type: 'DATA',
      flags: {},
      data: frame.data.slice(0, this._window)
    };
    frame.data = frame.data.slice(this._window);
    remainder = frame;
  }

  var moreNeeded = null;
  if (forwardable !== undefined) {
    this._log.trace({ frame: forwardable }, 'Pushing frame into the output queue');
    if (forwardable && (forwardable.type === 'DATA') && (this._window !== Infinity)) {
      this._log.trace({ window: this._window, by: forwardable.data.length },
                       'Decreasing flow control window size.');
      this._window -= forwardable.data.length;
      assert(this._window >= 0);
    }
    moreNeeded = Duplex.prototype.push.call(this, forwardable);
  }

  if (remainder !== undefined) {
    remainderCallback(remainder);
  }

  return moreNeeded;
};

// Push `frame` into the flow control queue, or if it's empty, then directly into the output queue
Flow.prototype.push = function push(frame) {
  if (frame === null) {
    this._log.debug('Enqueueing outgoing End Of Stream');
  } else {
    frame.flags = frame.flags || {};
    this._log.debug({ frame: frame }, 'Enqueueing outgoing frame');
    this.emit('sending', frame);
  }

  if (this._queue.length === 0) {
    return this._push(frame, this._queue.push);
  } else {
    this._queue.push(frame);
    return null;
  }
};

// `getLastQueuedFrame` returns the last frame in output buffers. This is primarily used by the
// [Stream](stream.html) class to mark the last frame with END_STREAM flag.
Flow.prototype.getLastQueuedFrame = function getLastQueuedFrame() {
  var readableQueue = this._readableState.buffer;
  return this._queue[this._queue.length - 1] || readableQueue[readableQueue.length - 1];
};

// Outgoing frames - managing the window size
// ------------------------------------------

// Flow control window size is manipulated using the `_increaseWindow` method.
//
// * Invoking it with `Infinite` means turning off flow control. Flow control cannot be enabled
//   again once disabled. Any attempt to re-enable flow control MUST be rejected with a
//   FLOW_CONTROL_ERROR error code.
// * A sender MUST NOT allow a flow control window to exceed 2^31 - 1 bytes. The action taken
//   depends on it being a stream or the connection itself.

var WINDOW_SIZE_LIMIT = Math.pow(2, 31) - 1;

Flow.prototype._increaseWindow = function _increaseWindow(size) {
  if ((this._window === Infinity) && (size !== Infinity)) {
    this._log.error('Trying to increase flow control window after flow control was turned off.');
    this.emit('error', 'FLOW_CONTROL_ERROR');
  } else {
    this._log.trace({ window: this._window, by: size }, 'Increasing flow control window size.');
    this._window += size;
    if ((this._window !== Infinity) && (this._window > WINDOW_SIZE_LIMIT)) {
      this._log.error('Flow control window grew too large.');
      this.emit('error', 'FLOW_CONTROL_ERROR');
    } else {
      this.emit('window_update');
    }
  }
};

// The `_updateWindow` method gets called every time there's an incoming WINDOW_UPDATE frame. It
// modifies the flow control window:
//
// * Flow control can be disabled for an individual stream by sending a WINDOW_UPDATE with the
//   END_FLOW_CONTROL flag set. The payload of a WINDOW_UPDATE frame that has the END_FLOW_CONTROL
//   flag set is ignored.
// * A sender that receives a WINDOW_UPDATE frame updates the corresponding window by the amount
//   specified in the frame.
Flow.prototype._updateWindow = function _updateWindow(frame) {
  this._increaseWindow(frame.flags.END_FLOW_CONTROL ? Infinity : frame.window_size);
};

// A SETTINGS frame can alter the initial flow control window size for all current streams. When the
// value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream by
// calling the `setInitialWindow` method. The window size has to be modified by the difference
// between the new value and the old value.
Flow.prototype.setInitialWindow = function setInitialWindow(initialWindow) {
  this._increaseWindow(initialWindow - this._initialWindow);
  this._initialWindow = initialWindow;
};

// Flow control for outgoing frames can be disabled by the peer with various methods.
Flow.prototype.disableLocalFlowControl = function disableLocalFlowControl() {
  this._increaseWindow(Infinity);
};
<commit_msg>Flow: add missing semicolon.<commit_after>var assert = process.env.HTTP2_ASSERT ? require('assert') : function noop() {};

// The Flow class
// ==============

// Flow is a [Duplex stream][1] subclass which implements HTTP/2 flow control. It is designed to be
// subclassed by [Connection](connection.html) and the `upstream` component of [Stream](stream.html).
// [1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex

var Duplex  = require('stream').Duplex;

exports.Flow = Flow;

// Public API
// ----------

// * **Event: 'error' (type)**: signals an error
//
// * **setInitialWindow(size)**: the initial flow control window size can be changed *any time*
//   ([as described in the standard][1]) using this method
//
// * **disableRemoteFlowControl()**: sends a WINDOW_UPDATE signaling that we don't want flow control
//
// * **disableLocalFlowControl()**: disables flow control for outgoing frames
//
// [1]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-04#section-6.9.2

// API for child classes
// ---------------------

// * **new Flow([flowControlId])**: creating a new flow that will listen for WINDOW_UPDATES frames
//   with the given `flowControlId` (or every update frame if not given)
//
// * **Event: 'receiving' (frame)**: there's an incoming frame
//
// * **Event: 'sending' (frame)**: a frame was added to the output queue
//
// * **_send()**: called when more frames should be pushed. The child class is expected to override
//   this (instead of the `_read` method of the Duplex class).
//
// * **_receive(frame, readyCallback)**: called when there's an incoming frame. The child class is
//   expected to override this (instead of the `_write` method of the Duplex class).
//
// * **push(frame): bool**: schedules `frame` for sending.
//
//   Returns `true` if it needs more frames in the output queue, `false` if the output queue is
//   full, and `null` if did not push the frame into the output queue (instead, it pushed it into
//   the flow control queue).
//
// * **read(limit): frame**: like the regular `read`, but the 'flow control size' (0 for non-DATA
//   frames, length of the payload for DATA frames) of the returned frame will be under `limit`.
//   Small exception: pass -1 as `limit` if the max. flow control size is 0. `read(0)` means the
//   same thing as [in the original API](http://nodejs.org/api/stream.html#stream_stream_read_0).
//
// * **getLastQueuedFrame(): frame**: returns the last frame in output buffers
//
// * **_log**: the Flow class uses the `_log` object of the parent

// Constructor
// -----------

// When a HTTP/2.0 connection is first established, new streams are created with an initial flow
// control window size of 65535 bytes.
var INITIAL_WINDOW_SIZE = 65535;

// `flowControlId` is needed if only specific WINDOW_UPDATEs should be watched.
function Flow(flowControlId) {
  Duplex.call(this, { objectMode: true });

  this._window = this._initialWindow = INITIAL_WINDOW_SIZE;
  this._flowControlId = flowControlId;
  this._queue = [];
  this._ended = false;
  this._received = 0;
  this._remoteFlowControlDisabled = false;

  this._queue.unshift = this._queue.unshift.bind(this._queue);
  this._queue.push = this._queue.push.bind(this._queue);
  this._read = this._read.bind(this);
}
Flow.prototype = Object.create(Duplex.prototype, { constructor: { value: Flow } });

// Incoming frames
// ---------------

// `_receive` is called when there's an incoming frame.
Flow.prototype._receive = function _receive(frame, callback) {
  throw new Error('The _receive(frame, callback) method has to be overridden by the child class!');
};

// `_receive` is called by `_write` which in turn is [called by Duplex][1] when someone `write()`s
// to the flow. It emits the 'receiving' event and notifies the window size tracking code if the
// incoming frame is a WINDOW_UPDATE.
// [1]: http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
Flow.prototype._write = function _write(frame, encoding, callback) {
  this.emit('receiving', frame);

  if (frame.flags.END_STREAM) {
    this._ended = true;
  }

  if ((frame.type === 'DATA') && (frame.data.length > 0) && !this._remoteFlowControlDisabled) {
    this._receive(frame, function() {
      this._received += frame.data.length;
      if (!this._restoreWindowTimer) {
        this._restoreWindowTimer = setImmediate(this._restoreWindow.bind(this));
      }
      callback();
    }.bind(this));
  }

  else {
    this._receive(frame, callback);
  }

  if ((frame.type === 'WINDOW_UPDATE') &&
      ((this._flowControlId === undefined) || (frame.stream === this._flowControlId))) {
    this._updateWindow(frame);
  }
};

// `_restoreWindow` basically acknowledges the DATA frames received since it's last call. It sends
// a WINDOW_UPDATE that restores the flow control window of the remote end.
Flow.prototype._restoreWindow = function _restoreWindow() {
  delete this._restoreWindowTimer;
  if (!this._ended && !this._remoteFlowControlDisabled && (this._received > 0)) {
    this.push({
      type: 'WINDOW_UPDATE',
      stream: this._flowControlId,
      window_size: this._received
    });
    this._received = 0;
  }
};

// Must be called after sending a SETTINGS frame that turns off flow control on the remote side.
Flow.prototype.disableRemoteFlowControl = function disableRemoteFlowControl() {
  this._log.debug('Turning off remote flow control');
  this._remoteFlowControlDisabled = true;
};

// Outgoing frames - sending procedure
// -----------------------------------

//                                         flow
//                +-------------------------------------------------+
//                |                                                 |
//                +--------+           +---------+                  |
//        read()  | output |  _read()  | flow    |  _send()         |
//     <----------|        |<----------| control |<-------------    |
//                | buffer |           | buffer  |                  |
//                +--------+           +---------+                  |
//                | input  |                                        |
//     ---------->|        |----------------------------------->    |
//       write()  | buffer |  _write()              _receive()      |
//                +--------+                                        |
//                |                                                 |
//                +-------------------------------------------------+

// `_send` is called when more frames should be pushed to the output buffer.
Flow.prototype._send = function _send() {
  throw new Error('The _send() method has to be overridden by the child class!');
};

// `_send` is called by `_read` which is in turn [called by Duplex][1] when it wants to have more
// items in the output queue.
// [1]: http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
Flow.prototype._read = function _read() {
  // * if the flow control queue is empty, then let the user push more frames
  if (this._queue.length === 0) {
    this._send();
  }

  // * if there are items in the flow control queue, then let's put them into the output queue (to
  //   the extent it is possible with respect to the window size and output queue feedback)
  else if (this._window > 0) {
    this._readableState.sync = true; // to avoid reentrant calls
    var frame;
    var moreNeeded = true;
    while (moreNeeded && (frame = this._queue.shift())) {
      moreNeeded = this._push(frame, this._queue.unshift);
    }
    this._readableState.sync = false;

    assert((moreNeeded == false) ||                              // * output queue is full
           (this._queue.length === 0) ||                         // * flow control queue is empty
           (!this._window && (this._queue[0].type === 'DATA'))); // * waiting for window update
  }

  // * otherwise, come back when the flow control window is positive
  else {
    this.once('window_update', this._read);
  }
};

var MAX_PAYLOAD_SIZE = 4096; // Must not be greater than MAX_HTTP_PAYLOAD_SIZE which is 16383

// `read(limit)` is like the `read` of the Readable class, but it guarantess that the 'flow control
// size' (0 for non-DATA frames, length of the payload for DATA frames) of the returned frame will
// be under `limit`.
Flow.prototype.read = function read(limit) {
  if (limit === 0) {
    return Duplex.prototype.read.call(this, 0);
  } else if (limit === -1) {
    limit = 0;
  } else if ((limit === undefined) || (limit > MAX_PAYLOAD_SIZE)) {
    limit = MAX_PAYLOAD_SIZE;
  }

  var frame = Duplex.prototype.read.call(this);

  if ((frame === null) || (frame.type !== 'DATA') || (frame.data.length <= limit)) {
    return frame;
  }

  else if (limit <= 0) {
    this.unshift(frame);
    return null;
  }

  else {
    this._log.trace({ frame: frame, size: frame.data.length, forwardable: limit },
                    'Splitting out forwardable part of a DATA frame.');
    var forwardable = {
      stream: frame.stream,
      type: 'DATA',
      flags: {},
      data: frame.data.slice(0, limit)
    };
    frame.data = frame.data.slice(limit);

    this.unshift(frame);
    return forwardable;
  }
};

// `_push(frame)` It pushes `frame` into the output queue and decreases the flow control window
// size. It is capable of splitting DATA frames into smaller parts, if the window size is not enough
// to push the whole frame. It calls `remainderCallback` synchronously with the frame it was not
// able to push to the output queue. The remainder may be the whole frame or the remaining part of a
// DATA frame. The return value is similar to `push` except that it returns `null` if it did not
// push anything to the output queue.

Flow.prototype._push = function _push(frame, remainderCallback) {
  var forwardable = undefined;
  var remainder = undefined;

  if ((frame === null) || (frame.type !== 'DATA') || (frame.data.length <= this._window)) {
    forwardable = frame;
  }

  else if (this._window <= 0) {
    remainder = frame;
  }

  else {
    this._log.trace({ frame: frame, size: frame.data.length, forwardable: this._window },
                    'Splitting out forwardable part of a DATA frame.');
    forwardable = {
      stream: frame.stream,
      type: 'DATA',
      flags: {},
      data: frame.data.slice(0, this._window)
    };
    frame.data = frame.data.slice(this._window);
    remainder = frame;
  }

  var moreNeeded = null;
  if (forwardable !== undefined) {
    this._log.trace({ frame: forwardable }, 'Pushing frame into the output queue');
    if (forwardable && (forwardable.type === 'DATA') && (this._window !== Infinity)) {
      this._log.trace({ window: this._window, by: forwardable.data.length },
                       'Decreasing flow control window size.');
      this._window -= forwardable.data.length;
      assert(this._window >= 0);
    }
    moreNeeded = Duplex.prototype.push.call(this, forwardable);
  }

  if (remainder !== undefined) {
    remainderCallback(remainder);
  }

  return moreNeeded;
};

// Push `frame` into the flow control queue, or if it's empty, then directly into the output queue
Flow.prototype.push = function push(frame) {
  if (frame === null) {
    this._log.debug('Enqueueing outgoing End Of Stream');
  } else {
    frame.flags = frame.flags || {};
    this._log.debug({ frame: frame }, 'Enqueueing outgoing frame');
    this.emit('sending', frame);
  }

  if (this._queue.length === 0) {
    return this._push(frame, this._queue.push);
  } else {
    this._queue.push(frame);
    return null;
  }
};

// `getLastQueuedFrame` returns the last frame in output buffers. This is primarily used by the
// [Stream](stream.html) class to mark the last frame with END_STREAM flag.
Flow.prototype.getLastQueuedFrame = function getLastQueuedFrame() {
  var readableQueue = this._readableState.buffer;
  return this._queue[this._queue.length - 1] || readableQueue[readableQueue.length - 1];
};

// Outgoing frames - managing the window size
// ------------------------------------------

// Flow control window size is manipulated using the `_increaseWindow` method.
//
// * Invoking it with `Infinite` means turning off flow control. Flow control cannot be enabled
//   again once disabled. Any attempt to re-enable flow control MUST be rejected with a
//   FLOW_CONTROL_ERROR error code.
// * A sender MUST NOT allow a flow control window to exceed 2^31 - 1 bytes. The action taken
//   depends on it being a stream or the connection itself.

var WINDOW_SIZE_LIMIT = Math.pow(2, 31) - 1;

Flow.prototype._increaseWindow = function _increaseWindow(size) {
  if ((this._window === Infinity) && (size !== Infinity)) {
    this._log.error('Trying to increase flow control window after flow control was turned off.');
    this.emit('error', 'FLOW_CONTROL_ERROR');
  } else {
    this._log.trace({ window: this._window, by: size }, 'Increasing flow control window size.');
    this._window += size;
    if ((this._window !== Infinity) && (this._window > WINDOW_SIZE_LIMIT)) {
      this._log.error('Flow control window grew too large.');
      this.emit('error', 'FLOW_CONTROL_ERROR');
    } else {
      this.emit('window_update');
    }
  }
};

// The `_updateWindow` method gets called every time there's an incoming WINDOW_UPDATE frame. It
// modifies the flow control window:
//
// * Flow control can be disabled for an individual stream by sending a WINDOW_UPDATE with the
//   END_FLOW_CONTROL flag set. The payload of a WINDOW_UPDATE frame that has the END_FLOW_CONTROL
//   flag set is ignored.
// * A sender that receives a WINDOW_UPDATE frame updates the corresponding window by the amount
//   specified in the frame.
Flow.prototype._updateWindow = function _updateWindow(frame) {
  this._increaseWindow(frame.flags.END_FLOW_CONTROL ? Infinity : frame.window_size);
};

// A SETTINGS frame can alter the initial flow control window size for all current streams. When the
// value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream by
// calling the `setInitialWindow` method. The window size has to be modified by the difference
// between the new value and the old value.
Flow.prototype.setInitialWindow = function setInitialWindow(initialWindow) {
  this._increaseWindow(initialWindow - this._initialWindow);
  this._initialWindow = initialWindow;
};

// Flow control for outgoing frames can be disabled by the peer with various methods.
Flow.prototype.disableLocalFlowControl = function disableLocalFlowControl() {
  this._increaseWindow(Infinity);
};
<|endoftext|>


<commit_before>function Frog(x, y, r, sAngle, eAngle) {
  this.x = x || 310;
  this.y = y || 470;
  this.r = r || 15;
  this.sAngle = sAngle || 0;
  this.eAngle = eAngle || 2 * Math.PI;
  this.home = {x: 310, y: 470}
}

Frog.prototype = {
  draw: function(ctx) {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.r, this.sAngle, this.eAngle);
    ctx.fillStyle = '#99CC99';
    ctx.fill();
    ctx.lineWidth = 2;
    ctx.strokeStyle = '#A55999';
    ctx.stroke();
    return this;
  },

  moveDown: function(canvas) {
    if (this.y < canvas.height - 60) {
      this.y += 44;
    }
  },
  moveUp: function(canvas) {
    if (this.y > canvas.height - 460) {
      this.y -= 44;
    }
  },
  moveRight: function(canvas) {
    if (this.x < canvas.width - 60) {
      this.x += 80;
    }
  },
  moveLeft: function(canvas) {
    if (this.x > canvas.width - 530) {
      this.x -= 80;
    }
  },
  winner: function() {
    if (this.y < 50) {
      console.log('winner!');
      // wherever vehicle behavior is defined, add next level behvaiors and start over
    }
  },
  drowns: function() {
    if (this.y < 50 && this.x > 90 && this.x < 210 || this.y < 50 && this.x > 230 && this.x < 370 || this.y < 50 && this.x > 390 && this.x < 550 ) {
      return (this.x = 310, this.y = 470);
    }
  },

  detectCollision: function (vehicle) {
    var frogLeftEdge = {x: this.x-this.r, y: this.y};
    var frogRightEdge = {x: this.x+this.r, y: this.y};
    var frogTopEdge ={x: this.x, y: this.y-this.r};
    var frogBottomEdge = {x: this.x, y: this.y+this.r}

    var vehicleTopLeftCorner = {x: vehicle.x, y: vehicle.y};
    var vehicleTopRightCorner = {x: vehicle.x + vehicle.width, y: vehicle.y};
    var vehicleBottomRightCorner = {x: vehicle.x + vehicle.width, y: vehicle.y + vehicle.height};
    var vehicleBottomLeftCorner = {x: vehicle.x, y: vehicle.y + vehicle.height}

    if (frogLeftEdge.x <= vehicleBottomRightCorner.x && frogRightEdge.x >= vehicleBottomLeftCorner.x && frogBottomEdge.y >= vehicleTopRightCorner.y && frogTopEdge.y <= vehicleTopLeftCorner.y) { return this.x=310, this.y=470; }
  }
}
module.exports = Frog;
<commit_msg>refactoring frog speed<commit_after>function Frog(x, y, r, sAngle, eAngle, speedX, speedY) {
  this.x = x || 310;
  this.y = y || 470;
  this.r = r || 15;
  this.sAngle = sAngle || 0;
  this.eAngle = eAngle || 2 * Math.PI;
  this.speedX = speedX || 80;
  this.speedY = speedY || 44;
}

Frog.prototype = {
  draw: function(ctx) {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.r, this.sAngle, this.eAngle, this.speedX, this.speedY);
    ctx.fillStyle = '#99CC99';
    ctx.fill();
    ctx.lineWidth = 2;
    ctx.strokeStyle = '#A55999';
    ctx.stroke();
    return this;
  },

  moveDown: function(canvas) {
    if (this.y < canvas.height - 60) {
      this.y += this.speedY;
    }
  },
  moveUp: function(canvas) {
    if (this.y > canvas.height - 460) {
      this.y -= this.speedY;
    }
  },
  moveRight: function(canvas) {
    if (this.x < canvas.width - 60) {
      this.x += this.speedX;
    }
  },
  moveLeft: function(canvas) {
    if (this.x > canvas.width - 530) {
      this.x -= this.speedX;
    }
  },
  winner: function() {
    if (this.y < 50) {
      console.log('winner!');
      // wherever vehicle behavior is defined, add next level behvaiors and start over
    }
  },
  drowns: function() {
    if (this.y < 50 && this.x > 90 && this.x < 210 || this.y < 50 && this.x > 230 && this.x < 370 || this.y < 50 && this.x > 390 && this.x < 550 ) {
      return (this.x = 310, this.y = 470);
    }
  },

  detectCollision: function (vehicle) {
    var frogLeftEdge = {x: this.x-this.r, y: this.y};
    var frogRightEdge = {x: this.x+this.r, y: this.y};
    var frogTopEdge ={x: this.x, y: this.y-this.r};
    var frogBottomEdge = {x: this.x, y: this.y+this.r}

    var vehicleTopLeftCorner = {x: vehicle.x, y: vehicle.y};
    var vehicleTopRightCorner = {x: vehicle.x + vehicle.width, y: vehicle.y};
    var vehicleBottomRightCorner = {x: vehicle.x + vehicle.width, y: vehicle.y + vehicle.height};
    var vehicleBottomLeftCorner = {x: vehicle.x, y: vehicle.y + vehicle.height}

    if (frogLeftEdge.x <= vehicleBottomRightCorner.x && frogRightEdge.x >= vehicleBottomLeftCorner.x && frogBottomEdge.y >= vehicleTopRightCorner.y && frogTopEdge.y <= vehicleTopLeftCorner.y)
      return (this.x = 310, this.y = 470)
  }
}
module.exports = Frog;
<|endoftext|>


<commit_before>/*!
 * https://github.com/leonardosnt/java-class-tools
 *
 * Copyright (C) 2017 leonardosnt
 * Licensed under the MIT License. See LICENSE file in the project root for full license information.
 */

import Opcode from './opcode';

// -1 = variable length
// index = opcode
const opcodeOperandCount = [
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 2,
  2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 1, -1, -1, 0, 0, 0, 0, 0, 0, 2,
  2, 2, 2, 2, 2, 2, 4, 4, 2, 1, 2, 0, 0, 2, 2, 0, 0, -1, 3,
  2, 2, 4, 4, 0
];

// index = opcode
const opcodeMnemonics = [
  'nop', 'aconst_null', 'iconst_m1', 'iconst_0', 'iconst_1', 'iconst_2', 'iconst_3', 'iconst_4', 'iconst_5', 'lconst_0',
  'lconst_1', 'fconst_0', 'fconst_1', 'fconst_2', 'dconst_0', 'dconst_1', 'bipush', 'sipush', 'ldc', 'ldc_w',
  'ldc2_w', 'iload', 'lload', 'fload', 'dload', 'aload', 'iload_0', 'iload_1', 'iload_2', 'iload_3',
  'lload_0', 'lload_1', 'lload_2', 'lload_3', 'fload_0', 'fload_1', 'fload_2', 'fload_3', 'dload_0', 'dload_1',
  'dload_2', 'dload_3', 'aload_0', 'aload_1', 'aload_2', 'aload_3', 'iaload', 'laload', 'faload', 'daload',
  'aaload', 'baload', 'caload', 'saload', 'istore', 'lstore', 'fstore', 'dstore', 'astore', 'istore_0',
  'istore_1', 'istore_2', 'istore_3', 'lstore_0', 'lstore_1', 'lstore_2', 'lstore_3', 'fstore_0', 'fstore_1', 'fstore_2',
  'fstore_3', 'dstore_0', 'dstore_1', 'dstore_2', 'dstore_3', 'astore_0', 'astore_1', 'astore_2', 'astore_3', 'iastore',
  'lastore', 'fastore', 'dastore', 'aastore', 'bastore', 'castore', 'sastore', 'pop', 'pop2', 'dup',  'dup_x1', 'dup_x2',
  'dup2', 'dup2_x1', 'dup2_x2', 'swap', 'iadd', 'ladd', 'fadd', 'dadd',  'isub', 'lsub', 'fsub', 'dsub', 'imul', 'lmul', 'fmul',
  'dmul', 'idiv', 'ldiv',  'fdiv', 'ddiv', 'irem', 'lrem', 'frem', 'drem', 'ineg', 'lneg', 'fneg', 'dneg',  'ishl', 'lshl',
  'ishr', 'lshr', 'iushr', 'lushr', 'iand', 'land', 'ior', 'lor',  'ixor', 'lxor', 'iinc', 'i2l', 'i2f', 'i2d', 'l2i', 'l2f',
  'l2d', 'f2i', 'f2l', 'f2d', 'd2i', 'd2l', 'd2f', 'i2b', 'i2c', 'i2s', 'lcmp', 'fcmpl',  'fcmpg', 'dcmpl',  'dcmpg',
  'ifeq', 'ifne', 'iflt', 'ifge', 'ifgt', 'ifle', 'if_icmpeq',  'if_icmpne', 'if_icmplt', 'if_icmpge',  'if_icmpgt',
  'if_icmple', 'if_acmpeq', 'if_acmpne', 'goto', 'jsr', 'ret',  'tableswitch', 'lookupswitch', 'ireturn',  'lreturn',
  'freturn', 'dreturn', 'areturn', 'return', 'getstatic', 'putstatic', 'getfield', 'putfield',  'invokevirtual',
  'invokespecial', 'invokestatic', 'invokeinterface', 'invokedynamic', 'new', 'newarray', 'anewarray',  'arraylength',
  'athrow', 'checkcast', 'instanceof', 'monitorenter', 'monitorexit', 'wide', 'multianewarray', 'ifnull', 'ifnonnull',
  'goto_w', 'jsr_w', 'breakpoint'
];

class Instruction {
  constructor(opcode, operands) {
    if (typeof opcode !== 'number') throw TypeError('opcode must be a number');
    if (!Array.isArray(operands)) throw TypeError('operands must be an array');

    this.opcode = opcode;
    this.operands = operands;
  }

  toString() {
    const opcodeMnemonic = opcodeMnemonics[this.opcode] ||
      (this.opcode === 0xFE ? 'impdep1': this.opcode === 0xFF ? 'impdep2' : undefined);
    return `Instruction { opcode: ${opcodeMnemonic}, operands: [${this.operands}] }`
  }
}

class InstructionParser {
  /**
   * Converts Instruction objects into raw bytecode.
   *
   * @param {Instruction[]} instruction - Instructions to convert.
   * @see {@link https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5}
   */
  static toBytecode(instructions) {
    if (!Array.isArray(instructions)) {
      throw TypeError('instructions must be an array.');
    }

    const bytecode = [];
    let offset = 0;

    while (offset < instructions.length) {
      const current = instructions[offset];
      bytecode.push(current.opcode);

      switch (current.opcode) {
        case Opcode.LOOKUPSWITCH: {
          let padding = (bytecode.length % 4) ? 4 - (bytecode.length % 4) : 0;
          let operandOffset = 0;

          while (padding-- > 0) {
            bytecode.push(0);
          }

          const defaultOffset = current.operands[operandOffset++] - 1;

          bytecode.push((defaultOffset >> 24) & 0xFF, (defaultOffset >> 16) & 0xFF, (defaultOffset >> 8) & 0xFF, defaultOffset & 0xFF);

          let npairs = current.operands[operandOffset++];

          bytecode.push((npairs >> 24) & 0xFF, (npairs >> 16) & 0xFF, (npairs >> 8) & 0xFF, npairs & 0xFF);

          while (npairs-- > 0) {
            const npair = current.operands[operandOffset++];
            const matchOffset = current.operands[operandOffset++] - 1;

            bytecode.push((npair >> 24) & 0xFF, (npair >> 16) & 0xFF, (npair >> 8) & 0xFF, npair & 0xFF);
            bytecode.push((matchOffset >> 24) & 0xFF, (matchOffset >> 16) & 0xFF, (matchOffset >> 8) & 0xFF, matchOffset & 0xFF);
          }
          break;
        }

        case Opcode.TABLESWITCH: {
          let padding = (bytecode.length % 4) ? 4 - (bytecode.length % 4) : 0;
          let operandOffset = 0;

          while (padding-- > 0) {
            bytecode.push(0);
          }

          const defaultOffset = current.operands[operandOffset++] - 1;

          bytecode.push((defaultOffset >> 24) & 0xFF, (defaultOffset >> 16) & 0xFF, (defaultOffset >> 8) & 0xFF, defaultOffset & 0xFF);

          const low = current.operands[operandOffset++];
          const high = current.operands[operandOffset++];

          bytecode.push((low >> 24) & 0xFF, (low >> 16) & 0xFF, (low >> 8) & 0xFF, low & 0xFF);
          bytecode.push((high >> 24) & 0xFF, (high >> 16) & 0xFF, (high >> 8) & 0xFF, high & 0xFF);

          let jumpOffsets = (high - low) + 1;
          while (jumpOffsets-- > 0) {
            const jumpOffset = current.operands[operandOffset++] - 1;
            bytecode.push((jumpOffset >> 24) & 0xFF, (jumpOffset >> 16) & 0xFF, (jumpOffset >> 8) & 0xFF, jumpOffset & 0xFF);
          }
          break;
        }

        case Opcode.WIDE: {
          const targetOpcode = current.operands[0];

          switch (targetOpcode) {
            case Opcode.ILOAD:
            case Opcode.FLOAD:
            case Opcode.ALOAD:
            case Opcode.LLOAD:
            case Opcode.DLOAD:
            case Opcode.ISTORE:
            case Opcode.FSTORE:
            case Opcode.ASTORE:
            case Opcode.LSTORE:
            case Opcode.DSTORE:
            case Opcode.RET:
              bytecode.push(targetOpcode, current.operands[1], current.operands[2]);
              break;

            case Opcode.IINC:
              bytecode.push(targetOpcode, current.operands[1], current.operands[2], current.operands[3], current.operands[4]);
              break;

            default:
              throw `Unexpected wide opcode: ${targetOpcode}`;
          }
          break;
        }

        default: {
          let operandCount = opcodeOperandCount[current.opcode];

          if (operandCount === undefined) {
            throw Error(`Unexpected opcode: ${current}`);
          }

          if (current.operands.length > operandCount) {
            throw Error(`The number of operands in instruction: ${current} is greater than the allowed.`);
          }

          for (let i = 0; i < operandCount; i++) {
            bytecode.push(current.operands[i]);
          }
          break;
        }
      }

      offset++;
    }

    return bytecode;
  }

  /**
   * Converts raw bytecode into Instruction objects.
   *
   * @param {number[]} bytecode - An array of bytes containing the jvm bytecode.
   * @see {@link https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5}
   */
  static fromBytecode(bytecode) {
    if (!Array.isArray(bytecode)) {
      throw TypeError('bytecode must be an array of bytes.');
    }

    const parsed = [];
    let offset = 0;

    while (offset < bytecode.length) {
      const current = bytecode[offset++];
      const instruction = new Instruction(current, []);

      switch (current) {
        // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.lookupswitch
        case Opcode.LOOKUPSWITCH: {
          const padding = (offset % 4) ? 4 - (offset % 4) : 0;
          offset += padding; // Skip padding

          // the "default" case offset
          const defaultOffset = (bytecode[offset++] << 24) | (bytecode[offset++] << 16) | (bytecode[offset++] << 8) | (bytecode[offset++]);
          instruction.operands.push(defaultOffset + 1);

          // number of "cases"
          let npairs = (bytecode[offset++] << 24) | (bytecode[offset++] << 16) | (bytecode[offset++] << 8) | (bytecode[offset++]);
          instruction.operands.push(npairs);

          while (npairs-- > 0) {
            const npair = (bytecode[offset++] << 24) | (bytecode[offset++] << 16) | (bytecode[offset++] << 8) | (bytecode[offset++]);
            const matchOffset = (bytecode[offset++] << 24) | (bytecode[offset++] << 16) | (bytecode[offset++] << 8) | (bytecode[offset++]);
            instruction.operands.push(npair);
            instruction.operands.push(matchOffset + 1);
          }
          break;
        }

        // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.tableswitch
        case Opcode.TABLESWITCH: {
          const padding = (offset % 4) ? 4 - (offset % 4) : 0;
          offset += padding; // Skip padding

          // the "default" case offset
          const defaultOffset = (bytecode[offset++] << 24) | (bytecode[offset++] << 16) | (bytecode[offset++] << 8) | (bytecode[offset++]);
          instruction.operands.push(defaultOffset + 1);

          const low = (bytecode[offset++] << 24) | (bytecode[offset++] << 16) | (bytecode[offset++] << 8) | (bytecode[offset++]);
          const high = (bytecode[offset++] << 24) | (bytecode[offset++] << 16) | (bytecode[offset++] << 8) | (bytecode[offset++]);

          instruction.operands.push(low);
          instruction.operands.push(high);

          let jumpOffsets = (high - low) + 1;
          while (jumpOffsets-- > 0) {
            const jumpOffset = (bytecode[offset++] << 24) | (bytecode[offset++] << 16) | (bytecode[offset++] << 8) | (bytecode[offset++]);
            instruction.operands.push(jumpOffset + 1);
          }
          break;
        }

        // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.wide
        case Opcode.WIDE: {
          const targetOpcode = bytecode[offset];
          switch (targetOpcode) {
            case Opcode.ILOAD:
            case Opcode.FLOAD:
            case Opcode.ALOAD:
            case Opcode.LLOAD:
            case Opcode.DLOAD:
            case Opcode.ISTORE:
            case Opcode.FSTORE:
            case Opcode.ASTORE:
            case Opcode.LSTORE:
            case Opcode.DSTORE:
            case Opcode.RET:
              instruction.operands.push(bytecode[offset++], bytecode[offset++], bytecode[offset++]);
              break;

            case Opcode.IINC:
              instruction.operands.push(bytecode[offset++], bytecode[offset++], bytecode[offset++], bytecode[offset++], bytecode[offset++]);
              break;

            default:
              throw `Unexpected wide opcode: ${targetOpcode}`;
          }
          break;
        }

        default: {
          let operandCount = opcodeOperandCount[current];

          if (operandCount === undefined) {
            throw Error(`Unexpected opcode: ${current}`);
          }

          while (operandCount-- > 0) {
            instruction.operands.push(bytecode[offset++]);
          }
          break;
        }
      }

      parsed.push(instruction);
    }
    return parsed;
  }
}

module.exports = {
  Instruction,
  InstructionParser
};
<commit_msg>don't convert tableswitch/lookupswitch operands<commit_after>/*!
 * https://github.com/leonardosnt/java-class-tools
 *
 * Copyright (C) 2017 leonardosnt
 * Licensed under the MIT License. See LICENSE file in the project root for full license information.
 */

import Opcode from './opcode';

// -1 = variable length
// index = opcode
const opcodeOperandCount = [
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 2,
  2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 1, -1, -1, 0, 0, 0, 0, 0, 0, 2,
  2, 2, 2, 2, 2, 2, 4, 4, 2, 1, 2, 0, 0, 2, 2, 0, 0, -1, 3,
  2, 2, 4, 4, 0
];

// index = opcode
const opcodeMnemonics = [
  'nop', 'aconst_null', 'iconst_m1', 'iconst_0', 'iconst_1', 'iconst_2', 'iconst_3', 'iconst_4', 'iconst_5', 'lconst_0',
  'lconst_1', 'fconst_0', 'fconst_1', 'fconst_2', 'dconst_0', 'dconst_1', 'bipush', 'sipush', 'ldc', 'ldc_w',
  'ldc2_w', 'iload', 'lload', 'fload', 'dload', 'aload', 'iload_0', 'iload_1', 'iload_2', 'iload_3',
  'lload_0', 'lload_1', 'lload_2', 'lload_3', 'fload_0', 'fload_1', 'fload_2', 'fload_3', 'dload_0', 'dload_1',
  'dload_2', 'dload_3', 'aload_0', 'aload_1', 'aload_2', 'aload_3', 'iaload', 'laload', 'faload', 'daload',
  'aaload', 'baload', 'caload', 'saload', 'istore', 'lstore', 'fstore', 'dstore', 'astore', 'istore_0',
  'istore_1', 'istore_2', 'istore_3', 'lstore_0', 'lstore_1', 'lstore_2', 'lstore_3', 'fstore_0', 'fstore_1', 'fstore_2',
  'fstore_3', 'dstore_0', 'dstore_1', 'dstore_2', 'dstore_3', 'astore_0', 'astore_1', 'astore_2', 'astore_3', 'iastore',
  'lastore', 'fastore', 'dastore', 'aastore', 'bastore', 'castore', 'sastore', 'pop', 'pop2', 'dup',  'dup_x1', 'dup_x2',
  'dup2', 'dup2_x1', 'dup2_x2', 'swap', 'iadd', 'ladd', 'fadd', 'dadd',  'isub', 'lsub', 'fsub', 'dsub', 'imul', 'lmul', 'fmul',
  'dmul', 'idiv', 'ldiv',  'fdiv', 'ddiv', 'irem', 'lrem', 'frem', 'drem', 'ineg', 'lneg', 'fneg', 'dneg',  'ishl', 'lshl',
  'ishr', 'lshr', 'iushr', 'lushr', 'iand', 'land', 'ior', 'lor',  'ixor', 'lxor', 'iinc', 'i2l', 'i2f', 'i2d', 'l2i', 'l2f',
  'l2d', 'f2i', 'f2l', 'f2d', 'd2i', 'd2l', 'd2f', 'i2b', 'i2c', 'i2s', 'lcmp', 'fcmpl',  'fcmpg', 'dcmpl',  'dcmpg',
  'ifeq', 'ifne', 'iflt', 'ifge', 'ifgt', 'ifle', 'if_icmpeq',  'if_icmpne', 'if_icmplt', 'if_icmpge',  'if_icmpgt',
  'if_icmple', 'if_acmpeq', 'if_acmpne', 'goto', 'jsr', 'ret',  'tableswitch', 'lookupswitch', 'ireturn',  'lreturn',
  'freturn', 'dreturn', 'areturn', 'return', 'getstatic', 'putstatic', 'getfield', 'putfield',  'invokevirtual',
  'invokespecial', 'invokestatic', 'invokeinterface', 'invokedynamic', 'new', 'newarray', 'anewarray',  'arraylength',
  'athrow', 'checkcast', 'instanceof', 'monitorenter', 'monitorexit', 'wide', 'multianewarray', 'ifnull', 'ifnonnull',
  'goto_w', 'jsr_w', 'breakpoint'
];

class Instruction {
  constructor(opcode, operands) {
    if (typeof opcode !== 'number') throw TypeError('opcode must be a number');
    if (!Array.isArray(operands)) throw TypeError('operands must be an array');

    this.opcode = opcode;
    this.operands = operands;
  }

  toString() {
    const opcodeMnemonic = opcodeMnemonics[this.opcode] ||
      (this.opcode === 0xFE ? 'impdep1': this.opcode === 0xFF ? 'impdep2' : undefined);
    return `Instruction { opcode: ${opcodeMnemonic}, operands: [${this.operands}] }`
  }
}

class InstructionParser {
  /**
   * Converts Instruction objects into raw bytecode.
   *
   * @param {Instruction[]} instruction - Instructions to convert.
   * @see {@link https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5}
   */
  static toBytecode(instructions) {
    if (!Array.isArray(instructions)) {
      throw TypeError('instructions must be an array.');
    }

    const bytecode = [];
    let offset = 0;

    while (offset < instructions.length) {
      const current = instructions[offset];
      bytecode.push(current.opcode);

      switch (current.opcode) {
        case Opcode.TABLESWITCH:
        case Opcode.LOOKUPSWITCH: {
          let padding = (bytecode.length % 4) ? 4 - (bytecode.length % 4) : 0;
          let operandOffset = 0;

          while (padding-- > 0) {
            bytecode.push(0);
          }

          bytecode.push(...current.operands);
          break;
        }

        case Opcode.WIDE: {
          const targetOpcode = current.operands[0];

          switch (targetOpcode) {
            case Opcode.ILOAD:
            case Opcode.FLOAD:
            case Opcode.ALOAD:
            case Opcode.LLOAD:
            case Opcode.DLOAD:
            case Opcode.ISTORE:
            case Opcode.FSTORE:
            case Opcode.ASTORE:
            case Opcode.LSTORE:
            case Opcode.DSTORE:
            case Opcode.RET:
              bytecode.push(targetOpcode, current.operands[1], current.operands[2]);
              break;

            case Opcode.IINC:
              bytecode.push(targetOpcode, current.operands[1], current.operands[2], current.operands[3], current.operands[4]);
              break;

            default:
              throw `Unexpected wide opcode: ${targetOpcode}`;
          }
          break;
        }

        default: {
          let operandCount = opcodeOperandCount[current.opcode];

          if (operandCount === undefined) {
            throw Error(`Unexpected opcode: ${current}`);
          }

          if (current.operands.length > operandCount) {
            throw Error(`The number of operands in instruction: ${current} is greater than the allowed.`);
          }

          for (let i = 0; i < operandCount; i++) {
            bytecode.push(current.operands[i]);
          }
          break;
        }
      }

      offset++;
    }

    return bytecode;
  }

  /**
   * Converts raw bytecode into Instruction objects.
   *
   * @param {number[]} bytecode - An array of bytes containing the jvm bytecode.
   * @see {@link https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5}
   */
  static fromBytecode(bytecode) {
    if (!Array.isArray(bytecode)) {
      throw TypeError('bytecode must be an array of bytes.');
    }

    const parsed = [];
    let offset = 0;

    while (offset < bytecode.length) {
      const current = bytecode[offset++];
      const instruction = new Instruction(current, []);

      switch (current) {
        // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.lookupswitch
        case Opcode.LOOKUPSWITCH: {
          const padding = (offset % 4) ? 4 - (offset % 4) : 0;
          offset += padding; // Skip padding

          // default case bytes + npair bytes
          for (var i = 0; i < 8; i++) {
            instruction.operands.push(bytecode[offset++]);
          }

          let npairs = (bytecode[offset-4] << 24) | (bytecode[offset-3] << 16) | (bytecode[offset-2] << 8) | (bytecode[offset-1]);

          // match-offset pairs
          for (var i = 0; i < npairs * 8; i++) {
            instruction.operands.push(bytecode[offset++]);
          }
          break;
        }

        // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.tableswitch
        case Opcode.TABLESWITCH: {
          const padding = (offset % 4) ? 4 - (offset % 4) : 0;
          offset += padding; // Skip padding

          // default bytes (4) + low bytes (4) + high bytes (4)
          for (var i = 0; i < 12; i++) {
            instruction.operands.push(bytecode[offset++]);
          }

          const low = (bytecode[offset-8] << 24) | (bytecode[offset-7] << 16) | (bytecode[offset-6] << 8) | (bytecode[offset-5]);
          const high = (bytecode[offset-4] << 24) | (bytecode[offset-3] << 16) | (bytecode[offset-2] << 8) | (bytecode[offset-1]);
          const numJumpOffsets = (high - low) + 1;

          // jump offset's
          for (var i = 0; i < numJumpOffsets * 4; i++) {
            instruction.operands.push(bytecode[offset++]);
          }
          break;
        }

        // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.wide
        case Opcode.WIDE: {
          const targetOpcode = bytecode[offset];
          switch (targetOpcode) {
            case Opcode.ILOAD:
            case Opcode.FLOAD:
            case Opcode.ALOAD:
            case Opcode.LLOAD:
            case Opcode.DLOAD:
            case Opcode.ISTORE:
            case Opcode.FSTORE:
            case Opcode.ASTORE:
            case Opcode.LSTORE:
            case Opcode.DSTORE:
            case Opcode.RET:
              instruction.operands.push(bytecode[offset++], bytecode[offset++], bytecode[offset++]);
              break;

            case Opcode.IINC:
              instruction.operands.push(bytecode[offset++], bytecode[offset++], bytecode[offset++], bytecode[offset++], bytecode[offset++]);
              break;

            default:
              throw `Unexpected wide opcode: ${targetOpcode}`;
          }
          break;
        }

        default: {
          let operandCount = opcodeOperandCount[current];

          if (operandCount === undefined) {
            throw Error(`Unexpected opcode: ${current}`);
          }

          while (operandCount-- > 0) {
            instruction.operands.push(bytecode[offset++]);
          }
          break;
        }
      }

      parsed.push(instruction);
    }
    return parsed;
  }
}

module.exports = {
  Instruction,
  InstructionParser
};
<|endoftext|>


<commit_before>import { GltfLoader, Resource } from "../../Source/Cesium.js";
import { ModelExperimental } from "../../Source/Cesium.js";
import createScene from "../createScene.js";

describe("Scene/ModelExperimental", function () {
  var boxTexturedGlbUrl =
    "./Data/Models/GltfLoader/BoxTextured/glTF-Binary/BoxTextured.glb";

  var scene;

  beforeAll(function () {
    scene = createScene();
  });

  it("initializes from Uint8Array", function () {
    spyOn(GltfLoader.prototype, "load");

    var resource = Resource.createIfNeeded(boxTexturedGlbUrl);
    var loadPromise = resource.fetchArrayBuffer();
    return loadPromise.then(function (buffer) {
      var model = new ModelExperimental({
        gltf: new Uint8Array(buffer),
      });

      expect(GltfLoader.prototype.load).toHaveBeenCalled();
      model._readyPromise.then(function () {
        expect(model._sceneGraph).toBeDefined();
        expect(model._resourcesLoaded).toEqual(true);
      });
    });
  });
});
<commit_msg>Remove unused scene from unit test<commit_after>import { GltfLoader, Resource } from "../../Source/Cesium.js";
import { ModelExperimental } from "../../Source/Cesium.js";

describe("Scene/ModelExperimental", function () {
  var boxTexturedGlbUrl =
    "./Data/Models/GltfLoader/BoxTextured/glTF-Binary/BoxTextured.glb";

  it("initializes from Uint8Array", function () {
    spyOn(GltfLoader.prototype, "load");

    var resource = Resource.createIfNeeded(boxTexturedGlbUrl);
    var loadPromise = resource.fetchArrayBuffer();
    return loadPromise.then(function (buffer) {
      var model = new ModelExperimental({
        gltf: new Uint8Array(buffer),
      });

      expect(GltfLoader.prototype.load).toHaveBeenCalled();
      model._readyPromise.then(function () {
        expect(model._sceneGraph).toBeDefined();
        expect(model._resourcesLoaded).toEqual(true);
      });
    });
  });
});
<|endoftext|>