001/*-
002 * Copyright (c) 2019 Diamond Light Source Ltd.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 */
009
010package org.eclipse.january.dataset;
011
012import java.lang.reflect.Array;
013import java.util.Date;
014import java.util.HashMap;
015import java.util.LinkedHashMap;
016import java.util.List;
017import java.util.Map;
018import java.util.Map.Entry;
019import java.util.Set;
020
021import org.apache.commons.math3.complex.Complex;
022
023/**
024 * @since 2.3
025 */
026public class InterfaceUtils {
027        private static final Map<Class<?>, Class<? extends Dataset>> class2Interface;
028
029        private static final Map<Class<? extends Dataset>, Class<?>> interface2Class;
030
031        private static final Map<Class<?>, Integer> elementBytes;
032
033        private static final Map<Class<?>, Class<?>> bestFloatElement;
034
035        private static final Map<Class<? extends Dataset>, Class<? extends CompoundDataset>> interface2Compound;
036
037        private static final Map<Class<? extends CompoundDataset>, Class<? extends Dataset>> compound2Interface;
038
039        private static Set<Class<? extends Dataset>> interfaces;
040
041        static {
042                class2Interface = createClassInterfaceMap();
043
044                interface2Class = createInterfaceClassMap();
045                interfaces = interface2Class.keySet();
046
047                elementBytes = createElementBytesMap();
048
049                bestFloatElement = createBestFloatElementMap();
050
051                interface2Compound = createInterfaceCompoundMap();
052                compound2Interface = new HashMap<Class<? extends CompoundDataset>, Class<? extends Dataset>>();
053                for (Entry<Class<? extends Dataset>, Class<? extends CompoundDataset>> e : interface2Compound.entrySet()) {
054                        compound2Interface.put(e.getValue(), e.getKey());
055                }
056                compound2Interface.put(RGBByteDataset.class, ByteDataset.class);
057                compound2Interface.put(RGBDataset.class, ShortDataset.class);
058                compound2Interface.put(ComplexFloatDataset.class, FloatDataset.class);
059                compound2Interface.put(ComplexDoubleDataset.class, DoubleDataset.class);
060        }
061
062        private static Map<Class<?>, Class<? extends Dataset>> createClassInterfaceMap() {
063                Map<Class<?>, Class<? extends Dataset>> result = new HashMap<>();
064                result.put(Boolean.class, BooleanDataset.class);
065                result.put(Byte.class, ByteDataset.class);
066                result.put(Short.class, ShortDataset.class);
067                result.put(Integer.class, IntegerDataset.class);
068                result.put(Long.class, LongDataset.class);
069                result.put(Float.class, FloatDataset.class);
070                result.put(Double.class, DoubleDataset.class);
071                result.put(boolean.class, BooleanDataset.class);
072                result.put(byte.class, ByteDataset.class);
073                result.put(short.class, ShortDataset.class);
074                result.put(int.class, IntegerDataset.class);
075                result.put(long.class, LongDataset.class);
076                result.put(float.class, FloatDataset.class);
077                result.put(double.class, DoubleDataset.class);
078                result.put(Complex.class, ComplexDoubleDataset.class);
079                result.put(String.class, StringDataset.class);
080                result.put(Date.class, DateDataset.class);
081                return result;
082        }
083
084        private static Map<Class<? extends Dataset>, Class<?>> createInterfaceClassMap() {
085                Map<Class<? extends Dataset>, Class<?>> result = new LinkedHashMap<>();
086                // ordering is likelihood of occurrence as it is used in an iterative check
087                // XXX for current implementation
088                result.put(DoubleDataset.class, Double.class);
089                result.put(DateDataset.class, Date.class); // XXX must be before string (and integer for unit test)
090                result.put(IntegerDataset.class, Integer.class);
091                result.put(BooleanDataset.class, Boolean.class);
092                result.put(StringDataset.class, String.class);
093                result.put(ComplexDoubleDataset.class, Double.class); // XXX must be before compound double
094                result.put(RGBByteDataset.class, Byte.class); // XXX must be before compound byte
095                result.put(RGBDataset.class, Short.class); // XXX must be before compound short
096                result.put(ByteDataset.class, Byte.class);
097                result.put(ShortDataset.class, Short.class);
098                result.put(LongDataset.class, Long.class);
099                result.put(FloatDataset.class, Float.class);
100                result.put(ComplexFloatDataset.class, Float.class); // XXX must be before compound float
101                result.put(CompoundShortDataset.class, Short.class);
102                result.put(CompoundByteDataset.class, Byte.class);
103                result.put(CompoundIntegerDataset.class, Integer.class);
104                result.put(CompoundLongDataset.class, Long.class);
105                result.put(CompoundFloatDataset.class, Float.class);
106                result.put(CompoundDoubleDataset.class, Double.class);
107                result.put(ObjectDataset.class, Object.class);
108                return result;
109        }
110
111        private static Map<Class<?>, Integer> createElementBytesMap() {
112                Map<Class<?>, Integer> result = new LinkedHashMap<>();
113                result.put(Boolean.class, 1);
114                result.put(Byte.class, Byte.SIZE / 8);
115                result.put(Short.class, Short.SIZE / 8);
116                result.put(Integer.class, Integer.SIZE / 8);
117                result.put(Long.class, Long.SIZE / 8);
118                result.put(Float.class, Float.SIZE / 8);
119                result.put(Double.class, Double.SIZE / 8);
120                result.put(String.class, 1);
121                result.put(Object.class, 1);
122                result.put(Date.class, 1);
123                return result;
124        }
125
126        private static Map<Class<?>, Class<?>> createBestFloatElementMap() {
127                Map<Class<?>, Class<?>> result = new HashMap<>();
128                result.put(Boolean.class, Float.class);
129                result.put(Byte.class, Float.class);
130                result.put(Short.class, Float.class);
131                result.put(Integer.class, Double.class);
132                result.put(Long.class, Double.class);
133                result.put(Float.class, Float.class);
134                result.put(Double.class, Double.class);
135                return result;
136        }
137
138        private static Map<Class<? extends Dataset>, Class<? extends CompoundDataset>> createInterfaceCompoundMap() {
139                Map<Class<? extends Dataset>, Class<? extends CompoundDataset>> result = new HashMap<>();
140                result.put(ByteDataset.class, CompoundByteDataset.class);
141                result.put(ShortDataset.class, CompoundShortDataset.class);
142                result.put(IntegerDataset.class, CompoundIntegerDataset.class);
143                result.put(LongDataset.class, CompoundLongDataset.class);
144                result.put(FloatDataset.class, CompoundFloatDataset.class);
145                result.put(DoubleDataset.class, CompoundDoubleDataset.class);
146                return result;
147        }
148
149        /**
150         * @param object input
151         * @param dInterface dataset interface
152         * @return true if object is an instance of dataset interface
153         */
154        public static boolean isInstance(Object object, final Class<? extends Dataset> dInterface) {
155                return dInterface.isInstance(object);
156        }
157
158        /**
159         * @param clazz dataset class
160         * @param dInterface dataset interface
161         * @return true if given class implements interface
162         */
163        public static boolean hasInterface(final Class<? extends Dataset> clazz, final Class<? extends Dataset> dInterface) {
164                return dInterface.isAssignableFrom(clazz);
165        }
166
167        /**
168         * @param clazz dataset class
169         * @param dInterfaces dataset interface
170         * @return true if given class implements any of the interfaces
171         */
172        @SuppressWarnings("unchecked")
173        public static boolean hasInterface(final Class<? extends Dataset> clazz, final Class<? extends Dataset>... dInterfaces) {
174                for (Class<? extends Dataset> d : dInterfaces) {
175                        if (d != null && d.isAssignableFrom(clazz)) {
176                                return true;
177                        }
178                }
179                return false;
180        }
181
182        /**
183         * @param clazz element class
184         * @return true if supported as an element class (note, Object is not supported)
185         */
186        public static boolean isElementSupported(Class<? extends Object> clazz) {
187                return class2Interface.containsKey(clazz);
188        }
189
190        /**
191         * @param clazz dataset class
192         * @return (boxed) class of constituent element
193         */
194        public static Class<?> getElementClass(final Class<? extends Dataset> clazz) {
195                return interface2Class.get(clazz);
196        }
197
198        /**
199         * Get dataset interface from an object. The following are supported: Java Number objects, Apache common math Complex
200         * objects, Java arrays and lists, Dataset objects and ILazyDataset object
201         *
202         * @param obj input
203         * @return dataset interface 
204         */
205        public static Class <? extends Dataset> getInterface(Object obj) {
206                Class<? extends Dataset> dc = null;
207
208                if (obj == null) {
209                        return ObjectDataset.class;
210                }
211
212                if (obj instanceof List<?>) {
213                        List<?> jl = (List<?>) obj;
214                        int l = jl.size();
215                        for (int i = 0; i < l; i++) {
216                                dc = getBestInterface(dc, getInterface(jl.get(i)));
217                        }
218                } else if (obj.getClass().isArray()) {
219                        Class<?> ca = obj.getClass().getComponentType();
220                        if (isElementSupported(ca)) {
221                                return class2Interface.get(ca);
222                        }
223                        int l = Array.getLength(obj);
224                        for (int i = 0; i < l; i++) {
225                                Object lo = Array.get(obj, i);
226                                dc = getBestInterface(dc, getInterface(lo));
227                        }
228                } else if (obj instanceof Dataset) {
229                        dc = findSubInterface(((Dataset) obj).getClass());
230                } else if (obj instanceof ILazyDataset) {
231                        dc = getInterfaceFromClass(((ILazyDataset) obj).getElementsPerItem(), ((ILazyDataset) obj).getElementClass());
232                } else {
233                        Class<?> ca = obj.getClass();
234                        if (isElementSupported(ca)) {
235                                return class2Interface.get(ca);
236                        }
237                }
238                return dc;
239        }
240
241        /**
242         * Find sub-interface of Dataset
243         * @param clazz dataset class
244         * @return sub-interface or null if given class is Dataset.class
245         * @since 2.3
246         */
247        public static Class<? extends Dataset> findSubInterface(Class<? extends Dataset> clazz) {
248                if (Dataset.class.equals(clazz)) {
249                        throw new IllegalArgumentException("Class must be a sub-interface of Dataset");
250                }
251                for (Class<? extends Dataset> i : interfaces) {
252                        if (i.isAssignableFrom(clazz)) {
253                                return i;
254                        }
255                }
256                // XXX special cases for current implementation
257                if (BooleanDatasetBase.class.equals(clazz)) {
258                        return BooleanDataset.class;
259                }
260                if (StringDatasetBase.class.equals(clazz)) {
261                        return StringDataset.class;
262                }
263                if (ObjectDatasetBase.class.equals(clazz)) {
264                        return ObjectDataset.class;
265                }
266                throw new IllegalArgumentException("Unknown sub-interface of Dataset");
267        }
268
269        /**
270         * @param elementsPerItem item size
271         * @param elementClass element class
272         * @return dataset interface 
273         */
274        public static Class<? extends Dataset> getInterfaceFromClass(int elementsPerItem, Class<?> elementClass) {
275                Class<? extends Dataset> clazz = class2Interface.get(elementClass);
276                if (clazz == null) {
277                        throw new IllegalArgumentException("Class of object not supported");
278                }
279                if (elementsPerItem > 1 && interface2Compound.containsKey(clazz)) {
280                        clazz = interface2Compound.get(clazz);
281                }
282                return clazz;
283        }
284
285        /**
286         * @param clazz dataset interface
287         * @return elemental dataset interface available for given dataset interface
288         */
289        public static Class<? extends Dataset> getElementalInterface(final Class<? extends Dataset> clazz) {
290                Class<? extends Dataset> c = findSubInterface(clazz);
291                return isElemental(c) ? c : compound2Interface.get(c);
292        }
293
294        /**
295         * @param clazz dataset interface
296         * @return compound dataset interface available for given dataset interface
297         */
298        @SuppressWarnings("unchecked")
299        public static Class<? extends CompoundDataset> getCompoundInterface(final Class<? extends Dataset> clazz) {
300                Class<? extends CompoundDataset> c = null; 
301                Class<? extends Dataset> d = findSubInterface(clazz);
302                if (isElemental(d)) {
303                        c = interface2Compound.get(d);
304                } else {
305                        c = (Class<? extends CompoundDataset>) d;
306                }
307                if (c == null) {
308                        throw new IllegalArgumentException("Interface cannot be compound");
309                }
310                return c;
311        }
312
313        /**
314         * @param a dataset
315         * @return true if dataset is not compound or complex
316         */
317        public static boolean isElemental(ILazyDataset a) {
318                return isElemental(getInterface(a));
319        }
320
321        /**
322         * @param clazz dataset class
323         * @return true if dataset interface is not compound or complex
324         */
325        public static boolean isElemental(Class<? extends Dataset> clazz) {
326                return !CompoundDataset.class.isAssignableFrom(clazz);
327        }
328
329        /**
330         * @param clazz dataset class
331         * @return true if dataset interface is compound (not complex)
332         */
333        public static boolean isCompound(Class<? extends Dataset> clazz) {
334                Class<? extends Dataset> c = findSubInterface(clazz);
335                return compound2Interface.containsKey(c);
336        }
337
338        /**
339         * @param a dataset
340         * @return true if dataset has integer elements
341         */
342        public static boolean isInteger(ILazyDataset a) {
343                return a instanceof Dataset ? isInteger(((Dataset) a).getClass()) : isElementClassInteger(a.getElementClass());
344        }
345
346        /**
347         * @param a dataset
348         * @return true if dataset has floating point elements
349         */
350        public static boolean isFloating(ILazyDataset a) {
351                return a instanceof Dataset ? isFloating(((Dataset) a).getClass()) : isElementClassFloating(a.getElementClass());
352        }
353
354        /**
355         * @param clazz dataset class
356         * @return true if dataset interface has integer elements
357         */
358        public static boolean isInteger(Class<? extends Dataset> clazz) {
359                Class<?> c = interface2Class.get(clazz);
360                return isElementClassInteger(c);
361        }
362
363        /**
364         * @param clazz dataset class
365         * @return true if dataset interface has floating point elements
366         */
367        public static boolean isFloating(Class<? extends Dataset> clazz) {
368                Class<?> c = interface2Class.get(clazz);
369                return isElementClassFloating(c);
370        }
371
372        private static boolean isElementClassInteger(Class<?> c) {
373                return Byte.class == c || Short.class == c || Integer.class == c || Long.class == c;
374        }
375
376        private static boolean isElementClassFloating(Class<?> c) {
377                return Double.class == c || Float.class == c;
378        }
379
380        /**
381         * @param clazz dataset class
382         * @return true if dataset interface has complex items
383         */
384        public static boolean isComplex(Class<? extends Dataset> clazz) {
385                return ComplexDoubleDataset.class.isAssignableFrom(clazz) || ComplexFloatDataset.class.isAssignableFrom(clazz);
386        }
387
388        /**
389         * @param clazz dataset class
390         * @return true if dataset interface has numerical elements
391         */
392        public static boolean isNumerical(Class<? extends Dataset> clazz) {
393                Class<?> c = interface2Class.get(clazz);
394                return Boolean.class == c || isElementClassInteger(c) || isElementClassFloating(c);
395        }
396
397        /**
398         * @param clazz dataset class
399         * @return number of elements per item
400         */
401        public static int getElementsPerItem(Class<? extends Dataset> clazz) {
402                if (isComplex(clazz)) {
403                        return 2;
404                } else if (RGBByteDataset.class.isAssignableFrom(clazz) || RGBDataset.class.isAssignableFrom(clazz)) {
405                        return 3;
406                }
407                if (CompoundDataset.class.isAssignableFrom(clazz)) {
408                        throw new UnsupportedOperationException("Multi-element type unsupported");
409                }
410                return 1;
411        }
412
413        /**
414         * Find dataset interface that best fits given classes. The best class takes into account complex and array datasets
415         *
416         * @param a
417         *            first dataset class
418         * @param b
419         *            second dataset class
420         * @return best dataset interface
421         */
422        public static Class<? extends Dataset> getBestInterface(Class<? extends Dataset> a, Class<? extends Dataset> b) {
423                if (a == null) {
424                        return b;
425                }
426                if (b == null) {
427                        return a;
428                }
429
430                boolean isElemental = true;
431                final boolean az = isComplex(a);
432                if (!az && !isElemental(a)) {
433                        isElemental = false;
434                        a = compound2Interface.get(a);
435                }
436                final boolean bz = isComplex(b);
437                if (!bz && !isElemental(b)) {
438                        isElemental = false;
439                        b = compound2Interface.get(b);
440                }
441
442                if (isFloating(a)) {
443                        if (!isFloating(b)) {
444                                b = getBestFloatInterface(b); // note doesn't change if not numerical!!!
445                        }
446                        if (az) {
447                                b = DoubleDataset.class.isAssignableFrom(b) ? ComplexDoubleDataset.class : ComplexFloatDataset.class;
448                        }
449                } else if (isFloating(b)) {
450                        a = getBestFloatInterface(a);
451                        if (bz) {
452                                a = DoubleDataset.class.isAssignableFrom(a) ? ComplexDoubleDataset.class : ComplexFloatDataset.class;
453                        }
454                }
455
456                Class<? extends Dataset> c = isBetter(interface2Class.get(a), interface2Class.get(b)) ? a : b;
457                if ((az || bz) && !isComplex(c)) {
458                        c = DoubleDataset.class.isAssignableFrom(c) ? ComplexDoubleDataset.class : ComplexFloatDataset.class;
459                }
460
461                if (!isElemental && interface2Compound.containsKey(c)) {
462                        c = interface2Compound.get(c);
463                }
464                return c;
465        }
466
467        private static boolean isBetter(Class<?> a, Class<?> b) {
468                for (Class<?> k : elementBytes.keySet()) { // elements order in increasing width (for numerical primitives)
469                        if (k.equals(b)) {
470                                return true;
471                        }
472                        if (k.equals(a)) {
473                                return false;
474                        }
475                }
476                return true;
477        }
478
479        /**
480         * The largest dataset type suitable for a summation of around a few thousand items without changing from the "kind"
481         * of dataset
482         *
483         * @param a dataset
484         * @return largest dataset type available for given dataset type
485         */
486        public static Class<? extends Dataset> getLargestInterface(Dataset a) {
487                if (a instanceof BooleanDataset || a instanceof ByteDataset || a instanceof ShortDataset) {
488                        return IntegerDataset.class;
489                } else if (a instanceof IntegerDataset) {
490                        return LongDataset.class;
491                } else if (a instanceof FloatDataset) {
492                        return DoubleDataset.class;
493                } else if (a instanceof ComplexFloatDataset) {
494                        return ComplexDoubleDataset.class;
495                } else if (a instanceof CompoundByteDataset || a instanceof CompoundShortDataset) {
496                        return CompoundIntegerDataset.class;
497                } else if (a instanceof CompoundIntegerDataset) {
498                        return CompoundLongDataset.class;
499                } else if (a instanceof CompoundFloatDataset) {
500                        return CompoundDoubleDataset.class;
501                }
502                return a.getClass();
503        }
504
505        /**
506         * Find floating point dataset interface that best fits given types. The best type takes into account complex and array
507         * datasets
508         *
509         * @param clazz dataset class
510         * @return best dataset interface
511         */
512        public static Class<? extends Dataset> getBestFloatInterface(Class<? extends Dataset> clazz) {
513                Class<?> e = interface2Class.get(clazz);
514                if (bestFloatElement.containsKey(e)) {
515                        e = bestFloatElement.get(e);
516                        return class2Interface.get(e);
517                }
518                return clazz;
519        }
520
521        /**
522         * @param isize
523         *            number of elements in an item
524         * @param clazz dataset interface
525         * @return length of single item in bytes
526         */
527        public static int getItemBytes(final int isize, Class<? extends Dataset> clazz) {
528                int bytes = elementBytes.get(interface2Class.get(clazz));
529
530                return isize * bytes;
531        }
532
533        /**
534         * Convert double array to primitive array
535         * @param clazz dataset interface
536         * @param x values
537         * @return biggest native primitive array if integer. Return null if not interface is not numerical
538         */
539        public static Object fromDoublesToBiggestPrimitives(Class<? extends Dataset> clazz, double[] x) {
540                if (BooleanDataset.class.isAssignableFrom(clazz) || ByteDataset.class.isAssignableFrom(clazz)
541                                || ShortDataset.class.isAssignableFrom(clazz) || IntegerDataset.class.isAssignableFrom(clazz)) {
542                        int[] i32 = new int[x.length];
543                        for (int i = 0; i < x.length; i++) {
544                                i32[i] = (int) (long) x[i];
545                        }
546                        return i32;
547                } else if (LongDataset.class.isAssignableFrom(clazz)) {
548                        long[] i64 = new long[x.length];
549                        for (int i = 0; i < x.length; i++) {
550                                i64[i] = (long) x[i];
551                        }
552                        return i64;
553                } else if (FloatDataset.class.isAssignableFrom(clazz)) {
554                        float[] f32 = new float[x.length];
555                        for (int i = 0; i < x.length; i++) {
556                                f32[i] = (float) x[i];
557                        }
558                        return f32;
559                } else if (DoubleDataset.class.isAssignableFrom(clazz)) {
560                        return x;
561                }
562                return null;
563        }
564
565        /**
566         * Convert double to number
567         * @param clazz dataset interface
568         * @param x value
569         * @return number if integer. Return null if not interface is not numerical
570         * @since 2.3
571         */
572        public static Number fromDoubleToNumber(Class<? extends Dataset> clazz, double x) {
573                if (BooleanDataset.class.isAssignableFrom(clazz) || ByteDataset.class.isAssignableFrom(clazz)) {
574                        return Byte.valueOf((byte) (long) x);
575                } else if (ShortDataset.class.isAssignableFrom(clazz)) {
576                        return Short.valueOf((short) (long) x);
577                } else if (IntegerDataset.class.isAssignableFrom(clazz)) {
578                        return Integer.valueOf((int) (long) x);
579                } else if (LongDataset.class.isAssignableFrom(clazz)) {
580                        return Long.valueOf((long) x);
581                } else if (FloatDataset.class.isAssignableFrom(clazz)) {
582                        return Float.valueOf((float) x);
583                } else if (DoubleDataset.class.isAssignableFrom(clazz)) {
584                        return Double.valueOf(x);
585                }
586                return null;
587        }
588
589        /**
590         * Convert double to number
591         * @param clazz dataset interface
592         * @param x value
593         * @return biggest number if integer. Return null if not interface is not numerical
594         */
595        public static Number fromDoubleToBiggestNumber(Class<? extends Dataset> clazz, double x) {
596                if (BooleanDataset.class.isAssignableFrom(clazz) || ByteDataset.class.isAssignableFrom(clazz)
597                                || ShortDataset.class.isAssignableFrom(clazz) || IntegerDataset.class.isAssignableFrom(clazz)) {
598                        return Integer.valueOf((int) (long) x);
599                } else if (LongDataset.class.isAssignableFrom(clazz)) {
600                        return Long.valueOf((long) x);
601                } else if (FloatDataset.class.isAssignableFrom(clazz)) {
602                        return Float.valueOf((float) x);
603                } else if (DoubleDataset.class.isAssignableFrom(clazz)) {
604                        return Double.valueOf(x);
605                }
606                return null;
607        }
608
609        /**
610         * @param clazz dataset interface
611         * @param x value
612         * @return biggest native primitive if integer
613         * @since 2.3
614         */
615        public static Number toBiggestNumber(Class<? extends Dataset> clazz, Number x) {
616                if (BooleanDataset.class.isAssignableFrom(clazz) || ByteDataset.class.isAssignableFrom(clazz)
617                                || ShortDataset.class.isAssignableFrom(clazz) || IntegerDataset.class.isAssignableFrom(clazz)) {
618                        return x instanceof Integer ? x : Integer.valueOf(x.intValue());
619                } else if (LongDataset.class.isAssignableFrom(clazz)) {
620                        return x instanceof Long ? x : Long.valueOf(x.longValue());
621                } else if (FloatDataset.class.isAssignableFrom(clazz)) {
622                        return x instanceof Float ? x : Float.valueOf(x.floatValue());
623                } else if (DoubleDataset.class.isAssignableFrom(clazz)) {
624                        return x instanceof Double ? x : Double.valueOf(x.doubleValue());
625                }
626                return null;
627        }
628}