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                Class<? extends Dataset> eInterface = getElementalInterface(clazz);
196                return interface2Class.get(eInterface);
197        }
198
199        /**
200         * Get dataset interface from an object. The following are supported: Java Number objects, Apache common math Complex
201         * objects, Java arrays and lists, Dataset objects and ILazyDataset object
202         *
203         * @param obj input
204         * @return dataset interface 
205         */
206        public static Class <? extends Dataset> getInterface(Object obj) {
207                Class<? extends Dataset> dc = null;
208
209                if (obj == null) {
210                        return ObjectDataset.class;
211                }
212
213                if (obj instanceof List<?>) {
214                        List<?> jl = (List<?>) obj;
215                        int l = jl.size();
216                        for (int i = 0; i < l; i++) {
217                                dc = getBestInterface(dc, getInterface(jl.get(i)));
218                        }
219                } else if (obj.getClass().isArray()) {
220                        Class<?> ca = obj.getClass().getComponentType();
221                        if (isElementSupported(ca)) {
222                                return class2Interface.get(ca);
223                        }
224                        int l = Array.getLength(obj);
225                        for (int i = 0; i < l; i++) {
226                                Object lo = Array.get(obj, i);
227                                dc = getBestInterface(dc, getInterface(lo));
228                        }
229                } else if (obj instanceof Dataset) {
230                        dc = findSubInterface(((Dataset) obj).getClass());
231                } else if (obj instanceof ILazyDataset) {
232                        dc = getInterfaceFromClass(((ILazyDataset) obj).getElementsPerItem(), ((ILazyDataset) obj).getElementClass());
233                } else {
234                        Class<?> ca = obj.getClass();
235                        if (isElementSupported(ca)) {
236                                return class2Interface.get(ca);
237                        }
238                }
239                return dc;
240        }
241
242        /**
243         * Find sub-interface of Dataset
244         * @param clazz dataset class
245         * @return sub-interface or null if given class is Dataset.class
246         * @since 2.3
247         */
248        public static Class<? extends Dataset> findSubInterface(Class<? extends Dataset> clazz) {
249                if (Dataset.class.equals(clazz)) {
250                        throw new IllegalArgumentException("Class must be a sub-interface of Dataset");
251                }
252                for (Class<? extends Dataset> i : interfaces) {
253                        if (i.isAssignableFrom(clazz)) {
254                                return i;
255                        }
256                }
257                // XXX special cases for current implementation
258                if (BooleanDatasetBase.class.equals(clazz)) {
259                        return BooleanDataset.class;
260                }
261                if (StringDatasetBase.class.equals(clazz)) {
262                        return StringDataset.class;
263                }
264                if (ObjectDatasetBase.class.equals(clazz)) {
265                        return ObjectDataset.class;
266                }
267                throw new IllegalArgumentException("Unknown sub-interface of Dataset");
268        }
269
270        /**
271         * @param elementsPerItem item size
272         * @param elementClass element class
273         * @return dataset interface 
274         */
275        public static Class<? extends Dataset> getInterfaceFromClass(int elementsPerItem, Class<?> elementClass) {
276                Class<? extends Dataset> clazz = class2Interface.get(elementClass);
277                if (clazz == null) {
278                        throw new IllegalArgumentException("Class of object not supported");
279                }
280                if (elementsPerItem > 1 && interface2Compound.containsKey(clazz)) {
281                        clazz = interface2Compound.get(clazz);
282                }
283                return clazz;
284        }
285
286        /**
287         * @param clazz dataset interface
288         * @return elemental dataset interface available for given dataset interface
289         */
290        public static Class<? extends Dataset> getElementalInterface(final Class<? extends Dataset> clazz) {
291                Class<? extends Dataset> c = findSubInterface(clazz);
292                return isElemental(c) ? c : compound2Interface.get(c);
293        }
294
295        /**
296         * @param clazz dataset interface
297         * @return compound dataset interface available for given dataset interface
298         */
299        @SuppressWarnings("unchecked")
300        public static Class<? extends CompoundDataset> getCompoundInterface(final Class<? extends Dataset> clazz) {
301                Class<? extends CompoundDataset> c = null; 
302                Class<? extends Dataset> d = findSubInterface(clazz);
303                if (isElemental(d)) {
304                        c = interface2Compound.get(d);
305                } else {
306                        c = (Class<? extends CompoundDataset>) d;
307                }
308                if (c == null) {
309                        throw new IllegalArgumentException("Interface cannot be compound");
310                }
311                return c;
312        }
313
314        /**
315         * @param a dataset
316         * @return true if dataset is not compound or complex
317         */
318        public static boolean isElemental(ILazyDataset a) {
319                return isElemental(getInterface(a));
320        }
321
322        /**
323         * @param clazz dataset class
324         * @return true if dataset interface is not compound or complex
325         */
326        public static boolean isElemental(Class<? extends Dataset> clazz) {
327                return !CompoundDataset.class.isAssignableFrom(clazz);
328        }
329
330        /**
331         * @param clazz dataset class
332         * @return true if dataset interface is compound (not complex)
333         */
334        public static boolean isCompound(Class<? extends Dataset> clazz) {
335                Class<? extends Dataset> c = findSubInterface(clazz);
336                return compound2Interface.containsKey(c);
337        }
338
339        /**
340         * @param a dataset
341         * @return true if dataset has integer elements
342         */
343        public static boolean isInteger(ILazyDataset a) {
344                return a instanceof Dataset ? isInteger(((Dataset) a).getClass()) : isElementClassInteger(a.getElementClass());
345        }
346
347        /**
348         * @param a dataset
349         * @return true if dataset has floating point elements
350         */
351        public static boolean isFloating(ILazyDataset a) {
352                return a instanceof Dataset ? isFloating(((Dataset) a).getClass()) : isElementClassFloating(a.getElementClass());
353        }
354
355        /**
356         * @param clazz dataset class
357         * @return true if dataset interface has integer elements
358         */
359        public static boolean isInteger(Class<? extends Dataset> clazz) {
360                Class<?> c = getElementClass(clazz);
361                return isElementClassInteger(c);
362        }
363
364        /**
365         * @param clazz dataset class
366         * @return true if dataset interface has floating point elements
367         */
368        public static boolean isFloating(Class<? extends Dataset> clazz) {
369                Class<?> c = getElementClass(clazz);
370                return isElementClassFloating(c);
371        }
372
373        private static boolean isElementClassInteger(Class<?> c) {
374                return Byte.class == c || Short.class == c || Integer.class == c || Long.class == c;
375        }
376
377        private static boolean isElementClassFloating(Class<?> c) {
378                return Double.class == c || Float.class == c;
379        }
380
381        /**
382         * @param clazz dataset class
383         * @return true if dataset interface has complex items
384         */
385        public static boolean isComplex(Class<? extends Dataset> clazz) {
386                return ComplexDoubleDataset.class.isAssignableFrom(clazz) || ComplexFloatDataset.class.isAssignableFrom(clazz);
387        }
388
389        /**
390         * @param clazz dataset class
391         * @return true if dataset interface has numerical elements
392         */
393        public static boolean isNumerical(Class<? extends Dataset> clazz) {
394                Class<?> c = getElementClass(clazz);
395                return Boolean.class == c || isElementClassInteger(c) || isElementClassFloating(c);
396        }
397
398        /**
399         * @param clazz dataset class
400         * @return number of elements per item
401         */
402        public static int getElementsPerItem(Class<? extends Dataset> clazz) {
403                if (isComplex(clazz)) {
404                        return 2;
405                } else if (RGBByteDataset.class.isAssignableFrom(clazz) || RGBDataset.class.isAssignableFrom(clazz)) {
406                        return 3;
407                }
408                if (CompoundDataset.class.isAssignableFrom(clazz)) {
409                        throw new UnsupportedOperationException("Multi-element type unsupported");
410                }
411                return 1;
412        }
413
414        /**
415         * Find dataset interface that best fits given classes. The best class takes into account complex and array datasets
416         *
417         * @param a
418         *            first dataset class
419         * @param b
420         *            second dataset class
421         * @return best dataset interface
422         */
423        public static Class<? extends Dataset> getBestInterface(Class<? extends Dataset> a, Class<? extends Dataset> b) {
424                if (a == null) {
425                        return b;
426                }
427                if (b == null) {
428                        return a;
429                }
430
431                boolean isElemental = true;
432                final boolean az = isComplex(a);
433                if (!az && !isElemental(a)) {
434                        isElemental = false;
435                        a = compound2Interface.get(a);
436                }
437                final boolean bz = isComplex(b);
438                if (!bz && !isElemental(b)) {
439                        isElemental = false;
440                        b = compound2Interface.get(b);
441                }
442
443                if (isFloating(a)) {
444                        if (!isFloating(b)) {
445                                b = getBestFloatInterface(b); // note doesn't change if not numerical!!!
446                        }
447                        if (az) {
448                                b = DoubleDataset.class.isAssignableFrom(b) ? ComplexDoubleDataset.class : ComplexFloatDataset.class;
449                        }
450                } else if (isFloating(b)) {
451                        a = getBestFloatInterface(a);
452                        if (bz) {
453                                a = DoubleDataset.class.isAssignableFrom(a) ? ComplexDoubleDataset.class : ComplexFloatDataset.class;
454                        }
455                }
456
457                Class<? extends Dataset> c = isBetter(interface2Class.get(a), interface2Class.get(b)) ? a : b;
458                if ((az || bz) && !isComplex(c)) {
459                        c = DoubleDataset.class.isAssignableFrom(c) ? ComplexDoubleDataset.class : ComplexFloatDataset.class;
460                }
461
462                if (!isElemental && interface2Compound.containsKey(c)) {
463                        c = interface2Compound.get(c);
464                }
465                return c;
466        }
467
468        private static boolean isBetter(Class<?> a, Class<?> b) {
469                for (Class<?> k : elementBytes.keySet()) { // elements order in increasing width (for numerical primitives)
470                        if (k.equals(b)) {
471                                return true;
472                        }
473                        if (k.equals(a)) {
474                                return false;
475                        }
476                }
477                return true;
478        }
479
480        /**
481         * The largest dataset type suitable for a summation of around a few thousand items without changing from the "kind"
482         * of dataset
483         *
484         * @param a dataset
485         * @return largest dataset type available for given dataset type
486         */
487        public static Class<? extends Dataset> getLargestInterface(Dataset a) {
488                if (a instanceof BooleanDataset || a instanceof ByteDataset || a instanceof ShortDataset) {
489                        return IntegerDataset.class;
490                } else if (a instanceof IntegerDataset) {
491                        return LongDataset.class;
492                } else if (a instanceof FloatDataset) {
493                        return DoubleDataset.class;
494                } else if (a instanceof ComplexFloatDataset) {
495                        return ComplexDoubleDataset.class;
496                } else if (a instanceof CompoundByteDataset || a instanceof CompoundShortDataset) {
497                        return CompoundIntegerDataset.class;
498                } else if (a instanceof CompoundIntegerDataset) {
499                        return CompoundLongDataset.class;
500                } else if (a instanceof CompoundFloatDataset) {
501                        return CompoundDoubleDataset.class;
502                }
503                return a.getClass();
504        }
505
506        /**
507         * Find floating point dataset interface that best fits given class. The best interface takes into account complex and array
508         * datasets
509         *
510         * @param clazz dataset class
511         * @return best dataset interface
512         */
513        public static Class<? extends Dataset> getBestFloatInterface(Class<? extends Dataset> clazz) {
514                Class<?> e = interface2Class.get(clazz);
515                if (bestFloatElement.containsKey(e)) {
516                        e = bestFloatElement.get(e);
517                        return class2Interface.get(e);
518                }
519                return clazz;
520        }
521
522        /**
523         * @param isize
524         *            number of elements in an item
525         * @param clazz dataset interface
526         * @return length of single item in bytes
527         */
528        public static int getItemBytes(final int isize, Class<? extends Dataset> clazz) {
529                Class<?> eClass = interface2Class.get(clazz);
530                if (eClass == null) {
531                        eClass = interface2Class.get(findSubInterface(clazz));
532                }
533                int bytes = elementBytes.get(eClass);
534
535                return isize * bytes;
536        }
537
538        /**
539         * Convert double array to primitive array
540         * @param clazz dataset interface
541         * @param x values
542         * @return biggest native primitive array if integer. Return null if not interface is not numerical
543         */
544        public static Object fromDoublesToBiggestPrimitives(Class<? extends Dataset> clazz, double[] x) {
545                if (BooleanDataset.class.isAssignableFrom(clazz) || ByteDataset.class.isAssignableFrom(clazz)
546                                || ShortDataset.class.isAssignableFrom(clazz) || IntegerDataset.class.isAssignableFrom(clazz)) {
547                        int[] i32 = new int[x.length];
548                        for (int i = 0; i < x.length; i++) {
549                                i32[i] = (int) (long) x[i];
550                        }
551                        return i32;
552                } else if (LongDataset.class.isAssignableFrom(clazz)) {
553                        long[] i64 = new long[x.length];
554                        for (int i = 0; i < x.length; i++) {
555                                i64[i] = (long) x[i];
556                        }
557                        return i64;
558                } else if (FloatDataset.class.isAssignableFrom(clazz)) {
559                        float[] f32 = new float[x.length];
560                        for (int i = 0; i < x.length; i++) {
561                                f32[i] = (float) x[i];
562                        }
563                        return f32;
564                } else if (DoubleDataset.class.isAssignableFrom(clazz)) {
565                        return x;
566                }
567                return null;
568        }
569
570        /**
571         * Convert double to number
572         * @param clazz dataset interface
573         * @param x value
574         * @return number if integer. Return null if not interface is not numerical
575         * @since 2.3
576         */
577        public static Number fromDoubleToNumber(Class<? extends Dataset> clazz, double x) {
578                if (BooleanDataset.class.isAssignableFrom(clazz) || ByteDataset.class.isAssignableFrom(clazz)) {
579                        return Byte.valueOf((byte) (long) x);
580                } else if (ShortDataset.class.isAssignableFrom(clazz)) {
581                        return Short.valueOf((short) (long) x);
582                } else if (IntegerDataset.class.isAssignableFrom(clazz)) {
583                        return Integer.valueOf((int) (long) x);
584                } else if (LongDataset.class.isAssignableFrom(clazz)) {
585                        return Long.valueOf((long) x);
586                } else if (FloatDataset.class.isAssignableFrom(clazz)) {
587                        return Float.valueOf((float) x);
588                } else if (DoubleDataset.class.isAssignableFrom(clazz)) {
589                        return Double.valueOf(x);
590                }
591                return null;
592        }
593
594        /**
595         * Convert double to number
596         * @param clazz dataset interface
597         * @param x value
598         * @return biggest number if integer. Return null if not interface is not numerical
599         */
600        public static Number fromDoubleToBiggestNumber(Class<? extends Dataset> clazz, double x) {
601                if (BooleanDataset.class.isAssignableFrom(clazz) || ByteDataset.class.isAssignableFrom(clazz)
602                                || ShortDataset.class.isAssignableFrom(clazz) || IntegerDataset.class.isAssignableFrom(clazz)) {
603                        return Integer.valueOf((int) (long) x);
604                } else if (LongDataset.class.isAssignableFrom(clazz)) {
605                        return Long.valueOf((long) x);
606                } else if (FloatDataset.class.isAssignableFrom(clazz)) {
607                        return Float.valueOf((float) x);
608                } else if (DoubleDataset.class.isAssignableFrom(clazz)) {
609                        return Double.valueOf(x);
610                }
611                return null;
612        }
613
614        /**
615         * @param clazz dataset interface
616         * @param x value
617         * @return biggest native primitive if integer
618         * @since 2.3
619         */
620        public static Number toBiggestNumber(Class<? extends Dataset> clazz, Number x) {
621                if (BooleanDataset.class.isAssignableFrom(clazz) || ByteDataset.class.isAssignableFrom(clazz)
622                                || ShortDataset.class.isAssignableFrom(clazz) || IntegerDataset.class.isAssignableFrom(clazz)) {
623                        return x instanceof Integer ? x : Integer.valueOf(x.intValue());
624                } else if (LongDataset.class.isAssignableFrom(clazz)) {
625                        return x instanceof Long ? x : Long.valueOf(x.longValue());
626                } else if (FloatDataset.class.isAssignableFrom(clazz)) {
627                        return x instanceof Float ? x : Float.valueOf(x.floatValue());
628                } else if (DoubleDataset.class.isAssignableFrom(clazz)) {
629                        return x instanceof Double ? x : Double.valueOf(x.doubleValue());
630                }
631                return null;
632        }
633}