Source: accessor.js

define(['functions', 'assert'], /** @exports accessor */ function (Fn, Assert) {
	'use strict';

	function ensureAccessorLength(get, set) {
		var getLen = get.length;
		var setLen = set.length;
		Assert.assert(getLen >= 1, Assert.GETTER_AT_LEAST_1_ARG);
		Assert.assert(setLen >= 2, Assert.SETTER_1_MORE_THAN_GETTER);
		Assert.assert(getLen === setLen - 1, Assert.SETTER_1_MORE_THAN_GETTER);
		return getLen;
	}

	function accessorFn(get, set, getLen) {
		return function () {
			return (arguments.length - getLen > 0
			        ? set.apply(null, arguments)
			        : get.apply(null, arguments));
		};
	}

	/**
	 * Creates an accessor from a getter and a setter.
	 *
	 * An accessor is a function that is composed of the getter and the
	 * setter, and which, if called with one more argument than the
	 * number of arguments acceptable by the getter function, calls the
	 * setter function, or otherwise calls the getter function.
	 *
	 * A getter must have at least one argument, the object something is
	 * being gotten from. The setter must have at least one more
	 * argument than the getter, which is presumably the value that is
	 * to be set.
	 *
	 * The getter and setter the accessor is composed of are available
	 * as the accessor.get and accessor.set properties on the accessor
	 * function. Using the getter and setter directly should only be
	 * done for optimizing accesses when it is really needed.
	 *
	 * The getter and setter function must not be wrapped by a function
	 * that doesn't have the proper number of arguments. Fn.partial()
	 * for example returns functions that have a length of 0, and we
	 * conciously reject such functions which reduces the potential of
	 * difficult to find errors.
	 *
	 * @param get {function}
	 * @param set {function}
	 * @return {!Accessor}
	 */
	function Accessor(get, set) {
		var getLen = ensureAccessorLength(get, set);
		// Optimize the common case of getLen <= 2
		var accessor = (1 === getLen) ? function (obj, value) {
			return (arguments.length > 1
			        ? set(obj, value)
			        : get(obj));
		} : (2 === getLen) ? function (obj, arg, value) {
			return (arguments.length > 2
			        ? set(obj, arg, value)
			        : get(obj, arg));
		} : accessorFn(get, set, getLen);
		accessor.get = get;
		accessor.set = set;
		return accessor;
	}

	/**
	 * Creates a method accessor from an existing accessor.
	 *
	 * A method accessor is an acccessor where the accessor function
	 * takes one less argument than the acesssor function of a normal
	 * accessor, and which uses the `this` special variable in place of
	 * the missing argument to pass to the getter and setter.
	 */
	function asMethod(accessor) {
		var get = accessor.get;
		var set = accessor.set;
		var getLen = ensureAccessorLength(get, set);
		// Optimize the common case of getLen <= 2
		var method = (1 === getLen) ? function (value) {
			return (arguments.length
			        ? set(this, value)
			        : get(this));
		} : (2 === getLen) ? function (arg, value) {
			return (arguments.length > 1
			        ? set(this, arg, value)
			        : get(this, arg));
		} : Fn.asMethod(accessorFn(get, set, getLen));
		method.get = get;
		method.set = set;
		return method;
	}

	/**
	 * Creates an accessor from a string.
	 */
	function fromString(key) {
		return Accessor(function (m) {
			return m[key];
		}, function (m, value) {
			m[key] = value;
			return m;
		});
	}

	/**
	 * Creates an accessor from a symbol.
	 */
	function fromSymbol(obj) {
		for (var symbol in obj) {
			if (obj.hasOwnProperty(symbol)) {
				return fromString(symbol);
			}
		}
		Assert.error(Assert.MISSING_SYMBOL);
	}

	Accessor.fromString = fromString;
	Accessor.fromSymbol = fromSymbol;
	Accessor.asMethod = asMethod;
	return Accessor;
});