bowling-analysis-system
Version:
A comprehensive system for analyzing bowling techniques using video processing and metrics calculation
290 lines (249 loc) • 8.8 kB
JavaScript
/**
* Factory for creating processor instances
* @class ProcessorFactory
*/
class ProcessorFactory {
/**
* Creates an instance of ProcessorFactory
* @param {Object} options - Factory options
* @param {Object} [options.logger] - Logger instance
* @param {Object} [options.config] - Configuration object
* @param {Object} [options.serviceRegistry] - Service registry
*/
constructor(options = {}) {
if (!options.logger) {
throw new Error('Logger is required for ProcessorFactory');
}
this.logger = options.logger;
this.config = options.config || {};
this.serviceRegistry = options.serviceRegistry;
this.processorClasses = new Map();
this.processorInstances = new Map();
this.defaultOptions = {};
// Register built-in processors
this._registerBuiltInProcessors();
}
/**
* Register built-in processors
* @private
*/
_registerBuiltInProcessors() {
try {
const ProcessorBase = require('./ProcessorBase');
const GenericProcessor = require('./GenericProcessor');
this.register('base', ProcessorBase);
this.register('generic', GenericProcessor);
// Register any additional built-in processors here
} catch (error) {
this.logger.warn('Failed to register built-in processors:', error);
}
}
/**
* Register a processor class
* @param {String} type - Processor type identifier
* @param {Class} processorClass - Processor class
* @param {Object} [defaultOptions] - Default options for this processor type
* @returns {ProcessorFactory} - For method chaining
*/
register(type, processorClass, defaultOptions = {}) {
if (!type || typeof type !== 'string') {
throw new Error('Processor type must be a non-empty string');
}
if (!processorClass || typeof processorClass !== 'function') {
throw new Error('Processor class must be a constructor function');
}
if (!processorClass.prototype.process || typeof processorClass.prototype.process !== 'function') {
throw new Error(`Processor class for ${type} must implement a process method`);
}
this.processorClasses.set(type, {
class: processorClass,
defaultOptions: { ...defaultOptions }
});
this.logger.debug(`Registered processor type: ${type}`);
return this;
}
/**
* Create a processor instance
* @param {String} type - Processor type
* @param {Object} [options] - Processor options
* @param {String} [options.name] - Processor name (defaults to type)
* @param {Boolean} [options.singleton=false] - Whether to reuse instances
* @returns {Object} - Processor instance
*/
create(type, options = {}) {
if (!this.processorClasses.has(type)) {
throw new Error(`Unknown processor type: ${type}`);
}
const { class: ProcessorClass, defaultOptions } = this.processorClasses.get(type);
const name = options.name || type;
const isSingleton = options.singleton === true;
// For singletons, check if we already have an instance
const instanceKey = isSingleton ? `${type}:${name}` : null;
if (isSingleton && this.processorInstances.has(instanceKey)) {
return this.processorInstances.get(instanceKey);
}
// Merge options
const mergedOptions = {
...this.defaultOptions,
...defaultOptions,
...options,
name,
logger: options.logger || this.logger,
config: options.config || this.config,
serviceRegistry: options.serviceRegistry || this.serviceRegistry
};
// Create instance
const processor = new ProcessorClass(mergedOptions);
// Store singleton instances
if (isSingleton) {
this.processorInstances.set(instanceKey, processor);
}
return processor;
}
/**
* Create a processor instance and initialize it
* @param {String} type - Processor type
* @param {Object} [options] - Processor options
* @param {Object} [initOptions] - Initialization options
* @returns {Promise<Object>} - Initialized processor instance
*/
async createAndInitialize(type, options = {}, initOptions = {}) {
const processor = this.create(type, options);
await processor.initialize(initOptions);
return processor;
}
/**
* Check if a processor type is registered
* @param {String} type - Processor type
* @returns {Boolean} - Whether the type is registered
*/
hasType(type) {
return this.processorClasses.has(type);
}
/**
* Get all registered processor types
* @returns {Array<String>} - Array of processor types
*/
getTypes() {
return Array.from(this.processorClasses.keys());
}
/**
* Set default options for all processors
* @param {Object} options - Default options
* @returns {ProcessorFactory} - For method chaining
*/
setDefaultOptions(options) {
this.defaultOptions = { ...options };
return this;
}
/**
* Get a singleton processor instance
* @param {String} type - Processor type
* @param {String} name - Processor name
* @returns {Object|null} - Processor instance or null if not found
*/
getSingleton(type, name) {
const instanceKey = `${type}:${name || type}`;
return this.processorInstances.get(instanceKey) || null;
}
/**
* Clear all singleton instances
* @returns {ProcessorFactory} - For method chaining
*/
clearSingletons() {
this.processorInstances.clear();
return this;
}
/**
* Create a processor from a configuration object
* @param {Object} config - Processor configuration
* @param {String} config.type - Processor type
* @param {String} [config.name] - Processor name
* @param {Object} [config.options] - Processor options
* @param {Boolean} [config.singleton] - Whether to use a singleton
* @param {Boolean} [config.initialize=true] - Whether to initialize the processor
* @returns {Promise<Object>} - Processor instance
*/
async createFromConfig(config) {
if (!config || typeof config !== 'object') {
throw new Error('Processor configuration must be an object');
}
if (!config.type) {
throw new Error('Processor configuration must specify a type');
}
if (!this.hasType(config.type)) {
throw new Error(`Unknown processor type: ${config.type}`);
}
const options = {
name: config.name || config.type,
singleton: config.singleton === true,
...config.options
};
const processor = this.create(config.type, options);
if (config.initialize !== false) {
await processor.initialize(config.initOptions || {});
}
return processor;
}
/**
* Create multiple processors from configuration
* @param {Array<Object>} configs - Array of processor configurations
* @returns {Promise<Array<Object>>} - Array of processor instances
*/
async createMultiple(configs) {
if (!Array.isArray(configs)) {
throw new Error('Processor configurations must be an array');
}
const processors = [];
for (const config of configs) {
const processor = await this.createFromConfig(config);
processors.push(processor);
}
return processors;
}
/**
* Load processor configurations from a config object
* @param {Object} configObject - Configuration object
* @returns {ProcessorFactory} - For method chaining
*/
loadFromConfig(configObject = {}) {
if (!configObject || typeof configObject !== 'object') {
throw new Error('Invalid configuration object');
}
if (configObject.processors) {
for (const [id, config] of Object.entries(configObject.processors)) {
try {
const processorClass = this._loadProcessorClass(config);
if (processorClass) {
this.register(id, processorClass, config.defaultOptions || {});
} else {
this.logger.error(`Failed to load processor class for ${id}: Class not found`);
}
} catch (error) {
this.logger.error(`Failed to load processor ${id}: ${error.message}`);
throw error;
}
}
}
return this;
}
/**
* Load a processor class from configuration
* @param {Object} config - Processor configuration
* @returns {Class} - Processor class
* @private
*/
_loadProcessorClass(config) {
if (!config.classPath) {
throw new Error('Processor configuration must specify a classPath');
}
try {
// Resolve the class path
return require(config.classPath);
} catch (error) {
this.logger.error(`Failed to load processor class from ${config.classPath}: ${error.message}`);
throw error;
}
}
}
module.exports = ProcessorFactory;