2
0

weka.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. // Experimental
  2. const weka = {};
  3. const java = {};
  4. weka.ModelFactory = class {
  5. async match(context) {
  6. try {
  7. const stream = context.stream;
  8. if (stream.length >= 5) {
  9. const buffer = stream.peek(2);
  10. if (buffer[0] === 0xAC && buffer[1] === 0xED) {
  11. const reader = new java.io.InputObjectStream(stream);
  12. const obj = reader.read();
  13. if (obj && obj.$class && obj.$class.name) {
  14. return context.set('weka', obj);
  15. }
  16. }
  17. }
  18. } catch {
  19. // continue regardless of error
  20. }
  21. return null;
  22. }
  23. async open(context) {
  24. const obj = context.value;
  25. throw new weka.Error(`Unsupported type '${obj.$class.name}'.`);
  26. }
  27. };
  28. weka.Error = class extends Error {
  29. constructor(message) {
  30. super(message);
  31. this.name = 'Error loading Weka model.';
  32. }
  33. };
  34. java.io = {};
  35. java.io.InputObjectStream = class {
  36. constructor(stream) {
  37. // Object Serialization Stream Protocol
  38. // https://www.cis.upenn.edu/~bcpierce/courses/629/jdkdocs/guide/serialization/spec/protocol.doc.html
  39. if (stream.length < 5) {
  40. throw new java.io.Error('Invalid stream size');
  41. }
  42. const signature = [0xac, 0xed];
  43. if (!stream.peek(2).every((value, index) => value === signature[index])) {
  44. throw new java.io.Error('Invalid stream signature');
  45. }
  46. this._reader = new java.io.InputObjectStream.BinaryReader(stream.peek());
  47. this._references = [];
  48. this._reader.skip(2);
  49. const version = this._reader.uint16();
  50. if (version !== 0x0005) {
  51. throw new java.io.Error(`Unsupported version '${version}'.`);
  52. }
  53. }
  54. read() {
  55. return this._object();
  56. }
  57. _object() {
  58. const code = this._reader.byte();
  59. switch (code) {
  60. case 0x73: { // TC_OBJECT
  61. const obj = {};
  62. obj.$class = this._classDesc();
  63. this._newHandle(obj);
  64. this._classData(obj);
  65. return obj;
  66. }
  67. case 0x74: { // TC_STRING
  68. return this._newString(false);
  69. }
  70. default: {
  71. throw new java.io.Error(`Unsupported code '${code}'.`);
  72. }
  73. }
  74. }
  75. _classDesc() {
  76. const code = this._reader.byte();
  77. switch (code) {
  78. case 0x72: // TC_CLASSDESC
  79. this._reader.skip(-1);
  80. return this._newClassDesc();
  81. case 0x71: // TC_REFERENCE
  82. return this._references[this._reader.uint32() - 0x7e0000];
  83. case 0x70: // TC_NULL
  84. this._reader.byte();
  85. return null;
  86. default:
  87. throw new java.io.Error(`Unsupported code '${code}'.`);
  88. }
  89. }
  90. _newClassDesc() {
  91. const code = this._reader.byte();
  92. switch (code) {
  93. case 0x72: { // TC_CLASSDESC
  94. const classDesc = {};
  95. classDesc.name = this._reader.string();
  96. classDesc.id = this._reader.uint64().toString();
  97. this._newHandle(classDesc);
  98. classDesc.flags = this._reader.byte();
  99. classDesc.fields = [];
  100. const count = this._reader.uint16();
  101. for (let i = 0; i < count; i++) {
  102. const field = {};
  103. field.type = String.fromCharCode(this._reader.byte());
  104. field.name = this._reader.string();
  105. if (field.type === '[' || field.type === 'L') {
  106. field.classname = this._object();
  107. }
  108. classDesc.fields.push(field);
  109. }
  110. if (this._reader.byte() !== 0x78) {
  111. throw new java.io.Error('Expected TC_ENDBLOCKDATA.');
  112. }
  113. classDesc.superClass = this._classDesc();
  114. return classDesc;
  115. }
  116. case 0x7D: // TC_PROXYCLASSDESC
  117. return null;
  118. default:
  119. throw new java.io.Error(`Unsupported code '${code}'.`);
  120. }
  121. }
  122. _classData(/* obj */) {
  123. /*
  124. const classname = obj.$class.name;
  125. let flags = obj.$class.flags;
  126. let superClass = obj.$class.superClass;
  127. while (superClass) {
  128. flags |= superClass.flags;
  129. superClass = superClass.superClass;
  130. }
  131. if (flags & 0x02) { // SC_SERIALIZABLE
  132. const customObject = objects[classname];
  133. const hasReadObjectMethod = customObject && customObject.readObject;
  134. if (flags & 0x01) { // SC_WRITE_METHOD
  135. if (!hasReadObjectMethod) {
  136. throw new Error(`Class '${classname}' does not implement readObject().`);
  137. }
  138. customObject.readObject(this, obj);
  139. if (this._reader.byte() !== 0x78) { // TC_ENDBLOCKDATA
  140. throw new java.io.Error('Expected TC_ENDBLOCKDATA.');
  141. }
  142. }
  143. else {
  144. if (hasReadObjectMethod) {
  145. customObject.readObject(this, obj);
  146. if (this._reader.byte() !== 0x78) { // TC_ENDBLOCKDATA
  147. throw new java.io.Error('Expected TC_ENDBLOCKDATA.');
  148. }
  149. }
  150. else {
  151. this._nowrclass(obj);
  152. }
  153. }
  154. }
  155. else if (flags & 0x04) { // SC_EXTERNALIZABLE
  156. if (flags & 0x08) { // SC_BLOCK_DATA
  157. this._objectAnnotation(obj);
  158. }
  159. else {
  160. this._externalContents();
  161. }
  162. }
  163. else {
  164. throw new Error('Illegal flags: ' + flags);
  165. }
  166. */
  167. }
  168. _newString(long) {
  169. const value = this._reader.string(long);
  170. this._newHandle(value);
  171. return value;
  172. }
  173. _newHandle(obj) {
  174. this._references.push(obj);
  175. }
  176. };
  177. java.io.InputObjectStream.BinaryReader = class {
  178. constructor(buffer) {
  179. this._buffer = buffer;
  180. this._position = 0;
  181. this._length = buffer.length;
  182. this._view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
  183. }
  184. skip(offset) {
  185. this._position += offset;
  186. if (this._position > this._length) {
  187. throw new java.io.Error(`Expected ${this._position - this._length} more bytes. The file might be corrupted. Unexpected end of file.`);
  188. }
  189. }
  190. byte() {
  191. const position = this._position;
  192. this.skip(1);
  193. return this._buffer[position];
  194. }
  195. uint16() {
  196. const position = this._position;
  197. this.skip(2);
  198. return this._view.getUint16(position, false);
  199. }
  200. uint32() {
  201. const position = this._position;
  202. this.skip(4);
  203. return this._view.getUint32(position, false);
  204. }
  205. uint64() {
  206. const position = this._position;
  207. this.skip(8);
  208. return this._view.getBigUint64(position, false);
  209. }
  210. string(long) {
  211. const size = long ? this.uint64().toNumber() : this.uint16();
  212. const position = this._position;
  213. this.skip(size);
  214. this._decoder = this._decoder || new TextDecoder('utf-8');
  215. return this._decoder.decode(this._buffer.subarray(position, this._position));
  216. }
  217. };
  218. java.io.Error = class extends Error {
  219. constructor(message) {
  220. super(message);
  221. this.name = 'Error loading Object Serialization Stream Protocol.';
  222. }
  223. };
  224. export const ModelFactory = weka.ModelFactory;