--- /dev/null
+"use strict";
+module.exports = OneOf;
+
+// extends ReflectionObject
+var ReflectionObject = require("./object");
+((OneOf.prototype = Object.create(ReflectionObject.prototype)).constructor = OneOf).className = "OneOf";
+
+var Field = require("./field"),
+ util = require("./util");
+
+/**
+ * Constructs a new oneof instance.
+ * @classdesc Reflected oneof.
+ * @extends ReflectionObject
+ * @constructor
+ * @param {string} name Oneof name
+ * @param {string[]|Object.<string,*>} [fieldNames] Field names
+ * @param {Object.<string,*>} [options] Declared options
+ * @param {string} [comment] Comment associated with this field
+ */
+function OneOf(name, fieldNames, options, comment) {
+ if (!Array.isArray(fieldNames)) {
+ options = fieldNames;
+ fieldNames = undefined;
+ }
+ ReflectionObject.call(this, name, options);
+
+ /* istanbul ignore if */
+ if (!(fieldNames === undefined || Array.isArray(fieldNames)))
+ throw TypeError("fieldNames must be an Array");
+
+ /**
+ * Field names that belong to this oneof.
+ * @type {string[]}
+ */
+ this.oneof = fieldNames || []; // toJSON, marker
+
+ /**
+ * Fields that belong to this oneof as an array for iteration.
+ * @type {Field[]}
+ * @readonly
+ */
+ this.fieldsArray = []; // declared readonly for conformance, possibly not yet added to parent
+
+ /**
+ * Comment for this field.
+ * @type {string|null}
+ */
+ this.comment = comment;
+}
+
+/**
+ * Oneof descriptor.
+ * @interface IOneOf
+ * @property {Array.<string>} oneof Oneof field names
+ * @property {Object.<string,*>} [options] Oneof options
+ */
+
+/**
+ * Constructs a oneof from a oneof descriptor.
+ * @param {string} name Oneof name
+ * @param {IOneOf} json Oneof descriptor
+ * @returns {OneOf} Created oneof
+ * @throws {TypeError} If arguments are invalid
+ */
+OneOf.fromJSON = function fromJSON(name, json) {
+ return new OneOf(name, json.oneof, json.options, json.comment);
+};
+
+/**
+ * Converts this oneof to a oneof descriptor.
+ * @param {IToJSONOptions} [toJSONOptions] JSON conversion options
+ * @returns {IOneOf} Oneof descriptor
+ */
+OneOf.prototype.toJSON = function toJSON(toJSONOptions) {
+ var keepComments = toJSONOptions ? Boolean(toJSONOptions.keepComments) : false;
+ return util.toObject([
+ "options" , this.options,
+ "oneof" , this.oneof,
+ "comment" , keepComments ? this.comment : undefined
+ ]);
+};
+
+/**
+ * Adds the fields of the specified oneof to the parent if not already done so.
+ * @param {OneOf} oneof The oneof
+ * @returns {undefined}
+ * @inner
+ * @ignore
+ */
+function addFieldsToParent(oneof) {
+ if (oneof.parent)
+ for (var i = 0; i < oneof.fieldsArray.length; ++i)
+ if (!oneof.fieldsArray[i].parent)
+ oneof.parent.add(oneof.fieldsArray[i]);
+}
+
+/**
+ * Adds a field to this oneof and removes it from its current parent, if any.
+ * @param {Field} field Field to add
+ * @returns {OneOf} `this`
+ */
+OneOf.prototype.add = function add(field) {
+
+ /* istanbul ignore if */
+ if (!(field instanceof Field))
+ throw TypeError("field must be a Field");
+
+ if (field.parent && field.parent !== this.parent)
+ field.parent.remove(field);
+ this.oneof.push(field.name);
+ this.fieldsArray.push(field);
+ field.partOf = this; // field.parent remains null
+ addFieldsToParent(this);
+ return this;
+};
+
+/**
+ * Removes a field from this oneof and puts it back to the oneof's parent.
+ * @param {Field} field Field to remove
+ * @returns {OneOf} `this`
+ */
+OneOf.prototype.remove = function remove(field) {
+
+ /* istanbul ignore if */
+ if (!(field instanceof Field))
+ throw TypeError("field must be a Field");
+
+ var index = this.fieldsArray.indexOf(field);
+
+ /* istanbul ignore if */
+ if (index < 0)
+ throw Error(field + " is not a member of " + this);
+
+ this.fieldsArray.splice(index, 1);
+ index = this.oneof.indexOf(field.name);
+
+ /* istanbul ignore else */
+ if (index > -1) // theoretical
+ this.oneof.splice(index, 1);
+
+ field.partOf = null;
+ return this;
+};
+
+/**
+ * @override
+ */
+OneOf.prototype.onAdd = function onAdd(parent) {
+ ReflectionObject.prototype.onAdd.call(this, parent);
+ var self = this;
+ // Collect present fields
+ for (var i = 0; i < this.oneof.length; ++i) {
+ var field = parent.get(this.oneof[i]);
+ if (field && !field.partOf) {
+ field.partOf = self;
+ self.fieldsArray.push(field);
+ }
+ }
+ // Add not yet present fields
+ addFieldsToParent(this);
+};
+
+/**
+ * @override
+ */
+OneOf.prototype.onRemove = function onRemove(parent) {
+ for (var i = 0, field; i < this.fieldsArray.length; ++i)
+ if ((field = this.fieldsArray[i]).parent)
+ field.parent.remove(field);
+ ReflectionObject.prototype.onRemove.call(this, parent);
+};
+
+/**
+ * Decorator function as returned by {@link OneOf.d} (TypeScript).
+ * @typedef OneOfDecorator
+ * @type {function}
+ * @param {Object} prototype Target prototype
+ * @param {string} oneofName OneOf name
+ * @returns {undefined}
+ */
+
+/**
+ * OneOf decorator (TypeScript).
+ * @function
+ * @param {...string} fieldNames Field names
+ * @returns {OneOfDecorator} Decorator function
+ * @template T extends string
+ */
+OneOf.d = function decorateOneOf() {
+ var fieldNames = new Array(arguments.length),
+ index = 0;
+ while (index < arguments.length)
+ fieldNames[index] = arguments[index++];
+ return function oneOfDecorator(prototype, oneofName) {
+ util.decorateType(prototype.constructor)
+ .add(new OneOf(oneofName, fieldNames));
+ Object.defineProperty(prototype, oneofName, {
+ get: util.oneOfGetter(fieldNames),
+ set: util.oneOfSetter(fieldNames)
+ });
+ };
+};