descriptor-builder.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. import {
  2. pick,
  3. assign,
  4. forEach,
  5. bind
  6. } from 'min-dash';
  7. import {
  8. parseName as parseNameNs
  9. } from './ns';
  10. /**
  11. * A utility to build element descriptors.
  12. */
  13. export default function DescriptorBuilder(nameNs) {
  14. this.ns = nameNs;
  15. this.name = nameNs.name;
  16. this.allTypes = [];
  17. this.allTypesByName = {};
  18. this.properties = [];
  19. this.propertiesByName = {};
  20. }
  21. DescriptorBuilder.prototype.build = function() {
  22. return pick(this, [
  23. 'ns',
  24. 'name',
  25. 'allTypes',
  26. 'allTypesByName',
  27. 'properties',
  28. 'propertiesByName',
  29. 'bodyProperty',
  30. 'idProperty'
  31. ]);
  32. };
  33. /**
  34. * Add property at given index.
  35. *
  36. * @param {Object} p
  37. * @param {Number} [idx]
  38. * @param {Boolean} [validate=true]
  39. */
  40. DescriptorBuilder.prototype.addProperty = function(p, idx, validate) {
  41. if (typeof idx === 'boolean') {
  42. validate = idx;
  43. idx = undefined;
  44. }
  45. this.addNamedProperty(p, validate !== false);
  46. var properties = this.properties;
  47. if (idx !== undefined) {
  48. properties.splice(idx, 0, p);
  49. } else {
  50. properties.push(p);
  51. }
  52. };
  53. DescriptorBuilder.prototype.replaceProperty = function(oldProperty, newProperty, replace) {
  54. var oldNameNs = oldProperty.ns;
  55. var props = this.properties,
  56. propertiesByName = this.propertiesByName,
  57. rename = oldProperty.name !== newProperty.name;
  58. if (oldProperty.isId) {
  59. if (!newProperty.isId) {
  60. throw new Error(
  61. 'property <' + newProperty.ns.name + '> must be id property ' +
  62. 'to refine <' + oldProperty.ns.name + '>');
  63. }
  64. this.setIdProperty(newProperty, false);
  65. }
  66. if (oldProperty.isBody) {
  67. if (!newProperty.isBody) {
  68. throw new Error(
  69. 'property <' + newProperty.ns.name + '> must be body property ' +
  70. 'to refine <' + oldProperty.ns.name + '>');
  71. }
  72. // TODO: Check compatibility
  73. this.setBodyProperty(newProperty, false);
  74. }
  75. // validate existence and get location of old property
  76. var idx = props.indexOf(oldProperty);
  77. if (idx === -1) {
  78. throw new Error('property <' + oldNameNs.name + '> not found in property list');
  79. }
  80. // remove old property
  81. props.splice(idx, 1);
  82. // replacing the named property is intentional
  83. //
  84. // * validate only if this is a "rename" operation
  85. // * add at specific index unless we "replace"
  86. //
  87. this.addProperty(newProperty, replace ? undefined : idx, rename);
  88. // make new property available under old name
  89. propertiesByName[oldNameNs.name] = propertiesByName[oldNameNs.localName] = newProperty;
  90. };
  91. DescriptorBuilder.prototype.redefineProperty = function(p, targetPropertyName, replace) {
  92. var nsPrefix = p.ns.prefix;
  93. var parts = targetPropertyName.split('#');
  94. var name = parseNameNs(parts[0], nsPrefix);
  95. var attrName = parseNameNs(parts[1], name.prefix).name;
  96. var redefinedProperty = this.propertiesByName[attrName];
  97. if (!redefinedProperty) {
  98. throw new Error('refined property <' + attrName + '> not found');
  99. } else {
  100. this.replaceProperty(redefinedProperty, p, replace);
  101. }
  102. delete p.redefines;
  103. };
  104. DescriptorBuilder.prototype.addNamedProperty = function(p, validate) {
  105. var ns = p.ns,
  106. propsByName = this.propertiesByName;
  107. if (validate) {
  108. this.assertNotDefined(p, ns.name);
  109. this.assertNotDefined(p, ns.localName);
  110. }
  111. propsByName[ns.name] = propsByName[ns.localName] = p;
  112. };
  113. DescriptorBuilder.prototype.removeNamedProperty = function(p) {
  114. var ns = p.ns,
  115. propsByName = this.propertiesByName;
  116. delete propsByName[ns.name];
  117. delete propsByName[ns.localName];
  118. };
  119. DescriptorBuilder.prototype.setBodyProperty = function(p, validate) {
  120. if (validate && this.bodyProperty) {
  121. throw new Error(
  122. 'body property defined multiple times ' +
  123. '(<' + this.bodyProperty.ns.name + '>, <' + p.ns.name + '>)');
  124. }
  125. this.bodyProperty = p;
  126. };
  127. DescriptorBuilder.prototype.setIdProperty = function(p, validate) {
  128. if (validate && this.idProperty) {
  129. throw new Error(
  130. 'id property defined multiple times ' +
  131. '(<' + this.idProperty.ns.name + '>, <' + p.ns.name + '>)');
  132. }
  133. this.idProperty = p;
  134. };
  135. DescriptorBuilder.prototype.assertNotDefined = function(p, name) {
  136. var propertyName = p.name,
  137. definedProperty = this.propertiesByName[propertyName];
  138. if (definedProperty) {
  139. throw new Error(
  140. 'property <' + propertyName + '> already defined; ' +
  141. 'override of <' + definedProperty.definedBy.ns.name + '#' + definedProperty.ns.name + '> by ' +
  142. '<' + p.definedBy.ns.name + '#' + p.ns.name + '> not allowed without redefines');
  143. }
  144. };
  145. DescriptorBuilder.prototype.hasProperty = function(name) {
  146. return this.propertiesByName[name];
  147. };
  148. DescriptorBuilder.prototype.addTrait = function(t, inherited) {
  149. var typesByName = this.allTypesByName,
  150. types = this.allTypes;
  151. var typeName = t.name;
  152. if (typeName in typesByName) {
  153. return;
  154. }
  155. forEach(t.properties, bind(function(p) {
  156. // clone property to allow extensions
  157. p = assign({}, p, {
  158. name: p.ns.localName,
  159. inherited: inherited
  160. });
  161. Object.defineProperty(p, 'definedBy', {
  162. value: t
  163. });
  164. var replaces = p.replaces,
  165. redefines = p.redefines;
  166. // add replace/redefine support
  167. if (replaces || redefines) {
  168. this.redefineProperty(p, replaces || redefines, replaces);
  169. } else {
  170. if (p.isBody) {
  171. this.setBodyProperty(p);
  172. }
  173. if (p.isId) {
  174. this.setIdProperty(p);
  175. }
  176. this.addProperty(p);
  177. }
  178. }, this));
  179. types.push(t);
  180. typesByName[typeName] = t;
  181. };