Source: lib/binding/Factory.js

'use strict';

const deprecated = require('../util/deprecated');

function extend(target) {
  for (let i = 1, sourceLen = arguments.length; i < sourceLen; i += 1) {
    const source = arguments[i];
    const properties = Object.getOwnPropertyNames(source);
    for (let j = 0, len = properties.length; j < len; j += 1) {
      const prop = properties[j];
      Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop));
    }
  }
  return target;
}

/**
 * This factory creates instances of type T, by invoking the {@link #new()} method
 * or by instantiating this factory directly
 * @class binding.Factory<T>
 *
 * @param {...*} args constructor params passed to the type constructor
 * @return {T} The new instance
 */
const Factory = extend(/** @lends binding.Factory<T>.prototype */ {

  /**
   * Creates a child factory of this factory
   * @param {Object} factoryMembers additional members applied to the child factory
   * @return {Object} The new created child Factory
   * @static
   * @ignore
   */
  extend(factoryMembers) {
    // copy all factory members to the child factory
    return extend({}, this, factoryMembers);
  },

  /**
   * Creates a new Factory for the given type
   * @param {Class<*>} type the type constructor of T
   * @return {binding.Factory} A new object factory to created instances of T
   * @static
   * @ignore
   */
  create(type) {
    // We want te explicitly name the created factory and give the constructor a properly argument name
    // eslint-disable-next-line no-shadow, no-unused-vars
    const factory = function Factory(properties) {
      return factory.newInstance(arguments);
    };

    extend(factory, this);

    // lets instanceof work properly
    factory.prototype = type.prototype;
    factory.type = type;

    return factory;
  },

  /**
   * Creates a new instance of the factory type
   * @param {...*} args Constructor arguments used for instantiation
   * @return {T} A new created instance of *
   * @instance
   */
  new(/* ...args */) {
    return this.newInstance(arguments);
  },

  /**
   * Creates a new instance of the factory type
   * @param {Array<*>=} args Constructor arguments used for instantiation
   * @return {T} A new created instance of *
   * @instance
   */
  newInstance(args) {
    if (!args || args.length === 0) {
      // eslint-disable-next-line new-cap
      return new this.type();
    }

    // es6 constructors can't be called, therefore bind all arguemnts and invoke the constructor
    // then with the bounded parameters
    // The first argument is shift out by invocation with `new`.
    const a = [null];
    Array.prototype.push.apply(a, args);
    const boundConstructor = (Function.prototype.bind.apply(this.type, a));
    // eslint-disable-next-line new-cap
    return new boundConstructor();
  },
});

deprecated(Factory, '_type', 'type');

module.exports = Factory;