Xcos writers: fix ScilabString encoding
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / io / ScilabTypeCoder.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2015 - Scilab Enterprises - Clement DAVID
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
10  *
11  */
12
13 package org.scilab.modules.xcos.io;
14
15 import java.nio.ByteBuffer;
16 import java.nio.DoubleBuffer;
17 import java.nio.IntBuffer;
18 import java.nio.LongBuffer;
19 import java.nio.charset.Charset;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import org.scilab.modules.types.ScilabBoolean;
24 import org.scilab.modules.types.ScilabDouble;
25 import org.scilab.modules.types.ScilabInteger;
26 import org.scilab.modules.types.ScilabIntegerTypeEnum;
27 import org.scilab.modules.types.ScilabList;
28 import org.scilab.modules.types.ScilabMList;
29 import org.scilab.modules.types.ScilabString;
30 import org.scilab.modules.types.ScilabTList;
31 import org.scilab.modules.types.ScilabType;
32 import org.scilab.modules.types.ScilabTypeEnum;
33 import org.scilab.modules.xcos.VectorOfDouble;
34 import org.scilab.modules.xcos.VectorOfInt;
35 import org.scilab.modules.xcos.VectorOfScicosID;
36
37 import java.util.logging.Level;
38 import java.util.logging.Logger;
39
40 /**
41  * Encode and decode using a var2vec / vec2var compatible encoding.
42  *
43  * <p>
44  * This encoder is used to store arbitrary data to the model. Usually some properties are hard to map to both Java and C++ STL type system, using a shared encoding let us provide an implementation
45  * whatever the language is without sharing too low-level information.
46  */
47 public class ScilabTypeCoder {
48
49     private static final Logger LOG = Logger.getLogger("org.scilab.modules.xcos.io");
50
51     class JavaScilabType {
52         final ScilabTypeEnum type;
53         final ScilabIntegerTypeEnum intType;
54
55         public JavaScilabType(ScilabTypeEnum type, ScilabIntegerTypeEnum intType) {
56             this.type = type;
57             this.intType = intType;
58         }
59     }
60
61     /** current position in the vec buffer */
62     int position = 0;
63
64     public ScilabTypeCoder() {
65     }
66
67     /*
68      * var2vec implementation
69      */
70
71     /**
72      * Encode any scilab type to a buffer
73      *
74      * @param var
75      *            the Scilab value to encode
76      * @return the encoded buffer
77      */
78     public VectorOfDouble var2vec(ScilabType var) {
79         final ScilabType value;
80         if (var == null) {
81             value = new ScilabDouble();
82         } else {
83             value = var;
84         }
85
86         if (LOG.isLoggable(Level.FINER)) {
87             LOG.entering(ScilabTypeCoder.class.getCanonicalName(), "var2vec");
88         }
89
90         VectorOfDouble vec = new VectorOfDouble();
91         encode(value, vec);
92
93         // System.err.println("var2vec:" + var.toString() + ":" + toString(vec));
94         if (LOG.isLoggable(Level.FINE)) {
95             LOG.fine("var2vec:" + var.toString() + ":" + toString(vec));
96         }
97
98         if (LOG.isLoggable(Level.FINER)) {
99             LOG.exiting(ScilabTypeCoder.class.getCanonicalName(), "var2vec");
100         }
101
102         return vec;
103     }
104
105     @SuppressWarnings("unchecked")
106     private VectorOfDouble encode(ScilabType var, VectorOfDouble vec) {
107         switch (var.getType()) {
108             case sci_matrix:
109                 encode((ScilabDouble) var, vec);
110                 break;
111             case sci_ints:
112                 encode((ScilabInteger) var, vec);
113                 break;
114             case sci_boolean:
115                 encode((ScilabBoolean) var, vec);
116                 break;
117             case sci_strings:
118                 encode((ScilabString) var, vec);
119                 break;
120             case sci_list:
121                 encode((ArrayList<ScilabType>) var, vec, var.getType());
122                 break;
123             case sci_mlist:
124                 encode((ArrayList<ScilabType>) var, vec, var.getType());
125                 break;
126             case sci_tlist:
127                 encode((ArrayList<ScilabType>) var, vec, var.getType());
128                 break;
129             default:
130                 break;
131         }
132
133         return vec;
134     }
135
136     /**
137      * Encode the double data
138      *
139      * @param var
140      *            the data to encode
141      * @param vec
142      *            the resulting buffer
143      */
144     private void encode(ScilabDouble var, VectorOfDouble vec) {
145         // Header
146         encodeHeader(var, vec, ScilabTypeEnum.sci_matrix);
147
148         // specific flag for managing the complex case
149         if (var.isReal()) {
150             vec.add(0f);
151         } else {
152             vec.add(1f);
153         }
154
155         // push the data
156         for (int i = 0; i < var.getHeight(); i++)
157             for (int j = 0; j < var.getWidth(); j++) {
158                 vec.add(var.getRealElement(i, j));
159             }
160
161         // push the complex data
162         if (!var.isReal())
163             for (int i = 0; i < var.getHeight(); i++)
164                 for (int j = 0; j < var.getWidth(); j++) {
165                     vec.add(var.getImaginaryElement(i, j));
166                 }
167     }
168
169     private void encode(ScilabInteger var, VectorOfDouble vec) {
170         // pre-processing: retrieve the raw data per type
171         int sizeof;
172         long[][] longData = null;
173         short[][] shortData = null;
174         int[][] intData = null;
175         byte[][] byteData = null;
176         switch (var.getPrec()) {
177             case sci_int64:
178             case sci_uint64:
179                 sizeof = Long.BYTES;
180                 longData = var.getDataAsLong();
181                 break;
182             case sci_int32:
183             case sci_uint32:
184                 sizeof = Integer.BYTES;
185                 intData = var.getDataAsInt();
186                 break;
187             case sci_int16:
188             case sci_uint16:
189                 sizeof = Short.BYTES;
190                 shortData = var.getDataAsShort();
191                 break;
192             case sci_int8:
193             case sci_uint8:
194                 sizeof = Byte.BYTES;
195                 byteData = var.getDataAsByte();
196                 break;
197             default:
198                 throw new IllegalArgumentException();
199         }
200
201         // Header
202         encodeHeader(var, vec, ScilabTypeEnum.sci_ints);
203
204         // push the data on a pre-allocated space
205         final int requiredBytes = sizeof * var.getHeight() * var.getWidth();
206         final int doubleLen = (requiredBytes + Double.BYTES - 1) / Double.BYTES;
207         final int position = vec.size();
208         vec.resize(position + doubleLen);
209         ByteBuffer view = vec.asByteBuffer(position, doubleLen);
210
211         for (int i = 0; i < var.getHeight(); i++) {
212             for (int j = 0; j < var.getWidth(); j++) {
213                 switch (var.getPrec()) {
214                     case sci_int64:
215                     case sci_uint64:
216                         view.putLong(longData[i][j]);
217                         break;
218                     case sci_int32:
219                     case sci_uint32:
220                         view.putInt(intData[i][j]);
221                         break;
222                     case sci_int16:
223                     case sci_uint16:
224                         view.putShort(shortData[i][j]);
225                         break;
226                     case sci_int8:
227                     case sci_uint8:
228                         view.put(byteData[i][j]);
229                         break;
230                 }
231             }
232         }
233     }
234
235     private void encode(ScilabBoolean var, VectorOfDouble vec) {
236         // header
237         encodeHeader(var, vec, ScilabTypeEnum.sci_boolean);
238
239         // put all the boolean as int accordingly to Scilab 6 implementation
240         final int requiredBytes = Integer.BYTES * var.getHeight() * var.getWidth();
241         final int doubleLen = (requiredBytes + Double.BYTES - 1) / Double.BYTES;
242         int position = vec.size();
243         vec.resize(position + doubleLen);
244
245         ByteBuffer buffer = vec.asByteBuffer(position, doubleLen);
246
247         for (int i = 0; i < var.getHeight(); i++) {
248             for (int j = 0; j < var.getWidth(); j++) {
249                 buffer.putInt(var.getData()[i][j] ? 1 : 0);
250             }
251         }
252     }
253
254     private void encode(ScilabString var, VectorOfDouble vec) {
255         // header
256         encodeHeader(var, vec, ScilabTypeEnum.sci_strings);
257
258         // add the offset table which contains the offset of each UTF-8 encoded strings
259         int offsetTableStart = vec.size();
260         vec.resize(offsetTableStart + var.getHeight() * var.getWidth());
261
262         // encode the strings as UTF-8 and store the associated offset
263         for (int i = 0; i < var.getHeight(); i++) {
264             for (int j = 0; j < var.getWidth(); j++) {
265                 String str = var.getData()[i][j];
266                 byte[] bytes = str.getBytes(Charset.forName("UTF-8"));
267                 // append the terminal '\0'
268                 final int requiredBytes = ((bytes.length + 1) * Byte.BYTES);
269                 final int doubleLen = (requiredBytes + Double.BYTES - 1) / Double.BYTES;
270
271                 // set the offset
272                 vec.set(offsetTableStart++, doubleLen);
273
274                 // push the data through a temporary byte buffer
275                 int position = vec.size();
276                 vec.resize(position + doubleLen);
277                 vec.asByteBuffer(position, doubleLen).put(bytes);
278             }
279         }
280     }
281
282     private void encode(ArrayList<ScilabType> var, VectorOfDouble vec, ScilabTypeEnum as) {
283         // header
284         encodeHeader(var, vec, as);
285
286         // encode list content
287         for (ScilabType localVar : var) {
288             encode(localVar, vec);
289         }
290     }
291
292     /**
293      * Helper method to add an header of the detected type
294      *
295      * @param var
296      *            the scilab matrix type to encode
297      * @param vec
298      *            the raw encoded data container
299      * @param as
300      *            the type to encode
301      * @param detected
302      *            the detected type
303      */
304     @SuppressWarnings({ "unchecked", "fallthrough" })
305     private void encodeHeader(Object var, VectorOfDouble vec, final ScilabTypeEnum as) {
306         ScilabType matrix = null;
307         ArrayList<ScilabType> list = null;
308
309         // defensive programming
310         switch (as) {
311             case sci_boolean:
312             case sci_ints:
313             case sci_matrix:
314             case sci_strings:
315                 matrix = (ScilabType) var;
316                 break;
317             case sci_list:
318             case sci_mlist:
319             case sci_tlist:
320                 list = (ArrayList<ScilabType>) var;
321                 break;
322             default:
323                 throw new IllegalArgumentException();
324         }
325
326         vec.add(as.swigValue());
327         if (matrix instanceof ScilabInteger) {
328             vec.add(((ScilabInteger) matrix).getPrec().swigValue());
329         }
330         if (matrix != null) {
331             vec.add(2);
332             vec.add(matrix.getHeight());
333             vec.add(matrix.getWidth());
334         } else if (list != null) {
335             vec.add(list.size());
336         } else {
337             throw new IllegalArgumentException();
338         }
339     }
340
341     /*
342      * vec2var implementation
343      */
344
345     /**
346      * Decode a scilab type from a buffer
347      *
348      * @param vec
349      *            the buffer containing encoded scilab types
350      * @return the decoded scilab type
351      */
352     public ScilabType vec2var(VectorOfDouble vec) {
353         position = 0;
354
355         if (LOG.isLoggable(Level.FINER)) {
356             LOG.entering(ScilabTypeCoder.class.getName(), "vec2var");
357         }
358
359         ScilabType var = decodeHeader(vec);
360         decode(vec, var);
361
362         // System.err.println("vec2var:" + toString(vec) + ":" + var.toString());
363         if (LOG.isLoggable(Level.FINE)) {
364             LOG.fine("vec2var:" + toString(vec) + ":" + var.toString());
365         }
366
367         if (LOG.isLoggable(Level.FINER)) {
368             LOG.exiting(ScilabTypeCoder.class.getName(), "vec2var");
369         }
370
371         return var;
372     }
373
374     @SuppressWarnings("unchecked")
375     private ScilabType decode(VectorOfDouble vec, ScilabType var) {
376         switch (var.getType()) {
377             case sci_matrix:
378                 decode(vec, (ScilabDouble) var);
379                 break;
380             case sci_ints:
381                 decode(vec, (ScilabInteger) var);
382                 break;
383             case sci_boolean:
384                 decode(vec, (ScilabBoolean) var);
385                 break;
386             case sci_strings:
387                 decode(vec, (ScilabString) var);
388                 break;
389             case sci_list:
390                 decode(vec, (ArrayList<ScilabType>) var);
391                 break;
392             case sci_mlist:
393                 decode(vec, (ArrayList<ScilabType>) var);
394                 break;
395             case sci_tlist:
396                 decode(vec, (ArrayList<ScilabType>) var);
397                 break;
398             default:
399                 break;
400         }
401         return var;
402     }
403
404     private ScilabType decode(VectorOfDouble vec, ScilabDouble var) {
405         double[][] realPart = var.getRealPart();
406         for (int i = 0; i < var.getHeight(); i++)
407             for (int j = 0; j < var.getWidth(); j++) {
408                 realPart[i][j] = vec.get(position++);
409             }
410
411         if (!var.isReal()) {
412             double[][] imaginaryPart = var.getImaginaryPart();
413
414             for (int i = 0; i < var.getHeight(); i++)
415                 for (int j = 0; j < var.getWidth(); j++) {
416                     imaginaryPart[i][j] = vec.get(position++);
417                 }
418         }
419         return var;
420     }
421
422     private ScilabType decode(VectorOfDouble vec, ScilabInteger var) {
423         final int sizeof;
424         long[][] longData = null;
425         short[][] shortData = null;
426         int[][] intData = null;
427         byte[][] byteData = null;
428
429         switch (var.getPrec()) {
430             case sci_int64:
431             case sci_uint64:
432                 sizeof = Long.BYTES;
433                 longData = var.getDataAsLong();
434                 break;
435             case sci_int32:
436             case sci_uint32:
437                 sizeof = Integer.BYTES;
438                 intData = var.getDataAsInt();
439                 break;
440             case sci_int16:
441             case sci_uint16:
442                 sizeof = Short.BYTES;
443                 shortData = var.getDataAsShort();
444                 break;
445             case sci_int8:
446             case sci_uint8:
447                 sizeof = Byte.BYTES;
448                 byteData = var.getDataAsByte();
449                 break;
450             default:
451                 throw new IllegalArgumentException();
452         }
453
454         final int doubleLen = (sizeof * var.getHeight() * var.getWidth()) / Double.BYTES + 1;
455         ByteBuffer view = vec.asByteBuffer(position, doubleLen);
456
457         for (int i = 0; i < var.getHeight(); i++) {
458             for (int j = 0; j < var.getWidth(); j++) {
459                 switch (var.getPrec()) {
460                     case sci_int64:
461                     case sci_uint64:
462                         longData[i][j] = view.getLong();
463                         break;
464                     case sci_int32:
465                     case sci_uint32:
466                         intData[i][j] = view.getInt();
467                         break;
468                     case sci_int16:
469                     case sci_uint16:
470                         shortData[i][j] = view.getShort();
471                         break;
472                     case sci_int8:
473                     case sci_uint8:
474                         byteData[i][j] = view.get();
475                         break;
476                 }
477             }
478         }
479         position += doubleLen;
480
481         return var;
482     }
483
484     private ScilabType decode(VectorOfDouble vec, ScilabBoolean var) {
485         final boolean[][] data = var.getData();
486
487         final int doubleLen = (Integer.BYTES * var.getHeight() * var.getWidth()) / Double.BYTES + 1;
488         ByteBuffer view = vec.asByteBuffer(position, doubleLen);
489
490         for (int i = 0; i < var.getHeight(); i++) {
491             for (int j = 0; j < var.getWidth(); j++) {
492                 data[i][j] = view.getInt() != 0;
493             }
494         }
495         position += doubleLen;
496         return var;
497     }
498
499     private ScilabType decode(VectorOfDouble vec, ScilabString var) {
500         final String[][] data = var.getData();
501
502         // reconstruct the offset
503         int[][] offset = new int[var.getHeight()][var.getWidth()];
504         for (int i = 0; i < var.getHeight(); i++) {
505             for (int j = 0; j < var.getWidth(); j++) {
506                 offset[i][j] = (int) vec.get(position++);
507             }
508         }
509
510         // reconstruct each String object
511         Charset utf8 = Charset.forName("UTF-8");
512         for (int i = 0; i < var.getHeight(); i++) {
513             for (int j = 0; j < var.getWidth(); j++) {
514                 ByteBuffer view = vec.asByteBuffer(position, offset[i][j]);
515                 byte[] bytes = new byte[offset[i][j] * Double.BYTES];
516
517                 view.get(bytes);
518
519                 // to avoid mis-decoding we have to look for the strlen of bytes
520                 int length = 0;
521                 for (; length < bytes.length; length++)
522                     if (bytes[length] == 0) {
523                         break;
524                     }
525
526                 data[i][j] = new String(bytes, 0, length, utf8);
527
528                 position += offset[i][j];
529             }
530         }
531
532         return var;
533     }
534
535     private ScilabType decode(VectorOfDouble vec, ArrayList<ScilabType> var) {
536         for (int i = 0; i < var.size(); i++) {
537             var.set(i, vec2var(vec));
538         }
539         return (ScilabType) var;
540     }
541
542     @SuppressWarnings("fallthrough")
543     private ScilabType decodeHeader(VectorOfDouble vec) {
544         int nativeScilabType = (int) vec.get(position++);
545
546         // specific integer sub-type
547         int precision = 0;
548
549         // for data[][]-based type
550         int height = 0;
551         int width = 0;
552
553         // for ArrayList-based type
554         int listLen = 0;
555
556         final ScilabTypeEnum type = ScilabTypeEnum.swigToEnum(nativeScilabType);
557         switch (type) {
558             case sci_ints:
559                 // special case for integer precision
560                 precision = (int) vec.get(position++);
561             case sci_matrix:
562             case sci_boolean:
563             case sci_strings:
564                 position++; // n-Dims not managed
565                 height = (int) vec.get(position++);
566                 width = (int) vec.get(position++);
567                 break;
568             case sci_list:
569             case sci_mlist:
570             case sci_tlist:
571                 listLen = (int) vec.get(position++);
572                 break;
573             default:
574                 throw new IllegalArgumentException();
575         }
576
577         // special case for complex double matrix
578         double[][] imagData = null;
579         if (type == ScilabTypeEnum.sci_matrix) {
580             boolean isComplex = vec.get(position++) != 0;
581
582             if (isComplex) {
583                 imagData = new double[height][width];
584             }
585         }
586
587         // allocate the right type with the decoded properties
588         switch (type) {
589             case sci_matrix:
590                 if (height * width == 0) {
591                     return new ScilabDouble();
592                 } else {
593                     return new ScilabDouble(new double[height][width], imagData);
594                 }
595             case sci_boolean:
596                 if (height * width == 0) {
597                     return new ScilabBoolean();
598                 } else {
599                     return new ScilabBoolean(new boolean[height][width]);
600                 }
601             case sci_ints:
602                 if (height * width == 0) {
603                     return new ScilabInteger();
604                 } else
605                     switch (ScilabIntegerTypeEnum.swigToEnum(precision)) {
606                         case sci_int8:
607                             return new ScilabInteger(new byte[height][width], false);
608                         case sci_int16:
609                             return new ScilabInteger(new short[height][width], false);
610                         case sci_int32:
611                             return new ScilabInteger(new int[height][width], false);
612                         case sci_int64:
613                             return new ScilabInteger(new long[height][width], false);
614                         case sci_uint8:
615                             return new ScilabInteger(new byte[height][width], true);
616                         case sci_uint16:
617                             return new ScilabInteger(new short[height][width], true);
618                         case sci_uint32:
619                             return new ScilabInteger(new int[height][width], true);
620                         case sci_uint64:
621                             return new ScilabInteger(new long[height][width], true);
622
623                     }
624             case sci_strings:
625                 if (height * width == 0) {
626                     return new ScilabString();
627                 } else {
628                     return new ScilabString(new String[height][width]);
629                 }
630             case sci_list:
631                 return new ScilabList(Collections.nCopies(listLen, null));
632             case sci_mlist:
633                 return new ScilabMList(new String[listLen], Collections.nCopies(listLen, null));
634             case sci_tlist:
635                 return new ScilabTList(new String[listLen], Collections.nCopies(listLen, null));
636             default:
637                 throw new IllegalArgumentException();
638
639         }
640     }
641
642     /**
643      * Utility to display a vec on debug
644      *
645      * @param vec the vector to convert
646      * @return a representative string
647      */
648     private static String toString(VectorOfDouble vec) {
649         int len = vec.size();
650         double[] copy = new double[len];
651         DoubleBuffer.wrap(copy).put(vec.asByteBuffer(0, len).asDoubleBuffer());
652         return Arrays.toString(copy);
653     }
654
655     /**
656      * Utility to display a vec on debug
657      *
658      * @param vec the vector to convert
659      * @return a representative string
660      */
661     private static String toString(VectorOfScicosID vec) {
662         int len = vec.size();
663         long[] copy = new long[len];
664         LongBuffer.wrap(copy).put(vec.asByteBuffer(0, len).asLongBuffer());
665         return Arrays.toString(copy);
666     }
667
668     /**
669      * Utility to display a vec on debug
670      *
671      * @param vec the vector to convert
672      * @return a representative string
673      */
674     private static String toString(VectorOfInt vec) {
675         int len = vec.size();
676         int[] copy = new int[len];
677         IntBuffer.wrap(copy).put(vec.asByteBuffer(0, len).asIntBuffer());
678         return Arrays.toString(copy);
679     }
680 }