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;
019
020import org.apache.commons.math3.complex.Complex;
021
022/**
023 * @since 2.3
024 */
025public class InterfaceUtils {
026        private static final Map<Class<?>, Class<? extends Dataset>> class2Interface;
027
028        private static final Map<Class<? extends Dataset>, Class<?>> interface2Class;
029
030        private static final Map<Class<?>, Integer> elementBytes;
031
032        private static final Map<Class<?>, Class<?>> bestFloatElement;
033
034        private static final Map<Class<? extends Dataset>, Class<? extends CompoundDataset>> interface2Compound;
035
036        private static final Map<Class<? extends CompoundDataset>, Class<? extends Dataset>> compound2Interface;
037        static {
038                class2Interface = createClassInterfaceMap();
039
040                interface2Class = createInterfaceClassMap();
041
042                elementBytes = createElementBytesMap();
043
044                bestFloatElement = createBestFloatElementMap();
045
046                interface2Compound = createInterfaceCompoundMap();
047                compound2Interface = new HashMap<Class<? extends CompoundDataset>, Class<? extends Dataset>>();
048                for (Entry<Class<? extends Dataset>, Class<? extends CompoundDataset>> e : interface2Compound.entrySet()) {
049                        compound2Interface.put(e.getValue(), e.getKey());
050                }
051        }
052
053        private static Map<Class<?>, Class<? extends Dataset>> createClassInterfaceMap() {
054                Map<Class<?>, Class<? extends Dataset>> result = new HashMap<>();
055                result.put(Boolean.class, BooleanDataset.class);
056                result.put(Byte.class, ByteDataset.class);
057                result.put(Short.class, ShortDataset.class);
058                result.put(Integer.class, IntegerDataset.class);
059                result.put(Long.class, LongDataset.class);
060                result.put(Float.class, FloatDataset.class);
061                result.put(Double.class, DoubleDataset.class);
062                result.put(boolean.class, BooleanDataset.class);
063                result.put(byte.class, ByteDataset.class);
064                result.put(short.class, ShortDataset.class);
065                result.put(int.class, IntegerDataset.class);
066                result.put(long.class, LongDataset.class);
067                result.put(float.class, FloatDataset.class);
068                result.put(double.class, DoubleDataset.class);
069                result.put(Complex.class, ComplexDoubleDataset.class);
070                result.put(String.class, StringDataset.class);
071                result.put(Date.class, DateDataset.class);
072                return result;
073        }
074
075        private static Map<Class<? extends Dataset>, Class<?>> createInterfaceClassMap() {
076                Map<Class<? extends Dataset>, Class<?>> result = new HashMap<>();
077                result.put(BooleanDataset.class, Boolean.class);
078                result.put(ByteDataset.class, Byte.class);
079                result.put(ShortDataset.class, Short.class);
080                result.put(IntegerDataset.class, Integer.class);
081                result.put(LongDataset.class, Long.class);
082                result.put(FloatDataset.class, Float.class);
083                result.put(DoubleDataset.class, Double.class);
084                result.put(CompoundByteDataset.class, Byte.class);
085                result.put(CompoundShortDataset.class, Short.class);
086                result.put(CompoundIntegerDataset.class, Integer.class);
087                result.put(CompoundLongDataset.class, Long.class);
088                result.put(CompoundFloatDataset.class, Float.class);
089                result.put(CompoundDoubleDataset.class, Double.class);
090                result.put(ComplexFloatDataset.class, Float.class);
091                result.put(ComplexDoubleDataset.class, Double.class);
092                result.put(RGBDataset.class, Short.class);
093                result.put(StringDataset.class, String.class);
094                result.put(DateDataset.class, Date.class);
095                result.put(ObjectDataset.class, Object.class);
096                return result;
097        }
098
099        private static Map<Class<?>, Integer> createElementBytesMap() {
100                Map<Class<?>, Integer> result = new LinkedHashMap<>();
101                result.put(Boolean.class, 1);
102                result.put(Byte.class, Byte.SIZE / 8);
103                result.put(Short.class, Short.SIZE / 8);
104                result.put(Integer.class, Integer.SIZE / 8);
105                result.put(Long.class, Long.SIZE / 8);
106                result.put(Float.class, Float.SIZE / 8);
107                result.put(Double.class, Double.SIZE / 8);
108                result.put(String.class, 1);
109                result.put(Object.class, 1);
110                result.put(Date.class, 1);
111                return result;
112        }
113
114        private static Map<Class<?>, Class<?>> createBestFloatElementMap() {
115                Map<Class<?>, Class<?>> result = new HashMap<>();
116                result.put(Boolean.class, Float.class);
117                result.put(Byte.class, Float.class);
118                result.put(Short.class, Float.class);
119                result.put(Integer.class, Double.class);
120                result.put(Long.class, Double.class);
121                result.put(Float.class, Float.class);
122                result.put(Double.class, Double.class);
123                return result;
124        }
125
126        private static Map<Class<? extends Dataset>, Class<? extends CompoundDataset>> createInterfaceCompoundMap() {
127                Map<Class<? extends Dataset>, Class<? extends CompoundDataset>> result = new HashMap<>();
128                result.put(ByteDataset.class, CompoundByteDataset.class);
129                result.put(ShortDataset.class, CompoundShortDataset.class);
130                result.put(IntegerDataset.class, CompoundIntegerDataset.class);
131                result.put(LongDataset.class, CompoundLongDataset.class);
132                result.put(FloatDataset.class, CompoundFloatDataset.class);
133                result.put(DoubleDataset.class, CompoundDoubleDataset.class);
134                return result;
135        }
136
137        /**
138         * @param clazz
139         * @return true if supported as an element class (note, Object is not supported)
140         */
141        public static boolean isElementSupported(Class<? extends Object> clazz) {
142                return class2Interface.containsKey(clazz);
143        }
144
145        /**
146         * @param clazz dataset class
147         * @return (boxed) class of constituent element
148         */
149        public static Class<?> getElementClass(final Class<? extends Dataset> clazz) {
150                return interface2Class.get(clazz);
151        }
152
153        /**
154         * Get dataset interface from an object. The following are supported: Java Number objects, Apache common math Complex
155         * objects, Java arrays and lists
156         *
157         * @param obj
158         * @return dataset interface 
159         */
160        public static Class <? extends Dataset> getInterface(Object obj) {
161                Class<? extends Dataset> dc = null;
162
163                if (obj == null) {
164                        return ObjectDataset.class;
165                }
166
167                if (obj instanceof List<?>) {
168                        List<?> jl = (List<?>) obj;
169                        int l = jl.size();
170                        for (int i = 0; i < l; i++) {
171                                Class<? extends Dataset> lc = getInterface(jl.get(i));
172                                if (isBetter(lc, dc)) {
173                                        dc = lc;
174                                }
175                        }
176                } else if (obj.getClass().isArray()) {
177                        Class<?> ca = obj.getClass().getComponentType();
178                        if (isElementSupported(ca)) {
179                                return class2Interface.get(ca);
180                        }
181                        int l = Array.getLength(obj);
182                        for (int i = 0; i < l; i++) {
183                                Object lo = Array.get(obj, i);
184                                Class<? extends Dataset> lc = getInterface(lo);
185                                if (isBetter(lc, dc)) {
186                                        dc = lc;
187                                }
188                        }
189                } else if (obj instanceof Dataset) {
190                        return ((Dataset) obj).getClass();
191                } else if (obj instanceof ILazyDataset) {
192                        dc = getInterfaceFromClass(((ILazyDataset) obj).getElementsPerItem(), ((ILazyDataset) obj).getElementClass());
193                } else {
194                        Class<?> ca = obj.getClass();
195                        if (isElementSupported(ca)) {
196                                return class2Interface.get(ca);
197                        }
198                }
199                return dc;
200        }
201
202        /**
203         * @param elementsPerItem
204         * @param elementClass
205         * @return dataset interface 
206         */
207        public static Class<? extends Dataset> getInterfaceFromClass(int elementsPerItem, Class<?> elementClass) {
208                Class<? extends Dataset> clazz = class2Interface.get(elementClass);
209                if (clazz == null) {
210                        throw new IllegalArgumentException("Class of object not supported");
211                }
212                if (elementsPerItem > 1 && interface2Compound.containsKey(clazz)) {
213                        clazz = interface2Compound.get(clazz);
214                }
215                return clazz;
216        }
217
218        /**
219         * @param clazz dataset interface
220         * @return elemental dataset interface available for given dataset interface
221         */
222        public static Class<? extends Dataset> getElementalInterface(final Class<? extends Dataset> clazz) {
223                return isElemental(clazz) ? clazz : compound2Interface.get(clazz);
224        }
225
226        /**
227         * @param a dataset
228         * @return true if dataset is not compound or complex
229         */
230        public static boolean isElemental(ILazyDataset a) {
231                return isElemental(getInterface(a));
232        }
233
234        /**
235         * @param clazz
236         * @return true if dataset interface is not compound or complex
237         */
238        public static boolean isElemental(Class<? extends Dataset> clazz) {
239                return !CompoundDataset.class.isAssignableFrom(clazz) || !ComplexFloatDataset.class.equals(clazz) || !ComplexDoubleDataset.class.equals(clazz);
240        }
241
242        /**
243         * @param clazz
244         * @return true if dataset interface is compound (not complex)
245         */
246        public static boolean isCompound(Class<? extends Dataset> clazz) {
247                return compound2Interface.containsKey(clazz) || RGBDataset.class.equals(clazz);
248        }
249
250        /**
251         * @param a dataset
252         * @return true if dataset has integer elements
253         */
254        public static boolean isInteger(ILazyDataset a) {
255                return a instanceof Dataset ? isInteger(((Dataset) a).getClass()) : isElementClassInteger(a.getElementClass());
256        }
257
258        /**
259         * @param a dataset
260         * @return true if dataset has floating point elements
261         */
262        public static boolean isFloating(ILazyDataset a) {
263                return a instanceof Dataset ? isFloating(((Dataset) a).getClass()) : isElementClassFloating(a.getElementClass());
264        }
265
266        /**
267         * @param clazz
268         * @return true if dataset interface has integer elements
269         */
270        public static boolean isInteger(Class<? extends Dataset> clazz) {
271                Class<?> c = interface2Class.get(clazz);
272                return isElementClassInteger(c);
273        }
274
275        /**
276         * @param clazz
277         * @return true if dataset interface has floating point elements
278         */
279        public static boolean isFloating(Class<? extends Dataset> clazz) {
280                Class<?> c = interface2Class.get(clazz);
281                return isElementClassFloating(c);
282        }
283
284        private static boolean isElementClassInteger(Class<?> c) {
285                return Byte.class == c || Short.class == c || Integer.class == c || Long.class == c;
286        }
287
288        private static boolean isElementClassFloating(Class<?> c) {
289                return Double.class == c || Float.class == c;
290        }
291
292        /**
293         * @param clazz
294         * @return true if dataset interface has complex items
295         */
296        public static boolean isComplex(Class<? extends Dataset> clazz) {
297                return ComplexDoubleDataset.class.isAssignableFrom(clazz) || ComplexFloatDataset.class.isAssignableFrom(clazz);
298        }
299
300        /**
301         * @param clazz
302         * @return true if dataset interface has numerical elements
303         */
304        public static boolean isNumerical(Class<? extends Dataset> clazz) {
305                Class<?> c = interface2Class.get(clazz);
306                return Boolean.class == c || isElementClassInteger(c) || isElementClassFloating(c);
307        }
308
309        /**
310         * @param clazz
311         * @return number of elements per item
312         */
313        public static int getElementsPerItem(Class<? extends Dataset> clazz) {
314                if (isComplex(clazz)) {
315                        return 2;
316                } else if (RGBDataset.class.isAssignableFrom(clazz)) {
317                        return 3;
318                }
319                if (CompoundDataset.class.isAssignableFrom(clazz)) {
320                        throw new UnsupportedOperationException("Multi-element type unsupported");
321                }
322                return 1;
323        }
324
325        /**
326         * Find dataset interface that best fits given classes. The best class takes into account complex and array datasets
327         *
328         * @param a
329         *            first dataset class
330         * @param b
331         *            second dataset class
332         * @return best dataset interface
333         */
334        public static Class<? extends Dataset> getBestInterface(Class<? extends Dataset> a, Class<? extends Dataset> b) {
335                boolean isElemental = true;
336                
337                if (a == null) {
338                        return b;
339                }
340                if (b == null) {
341                        return a;
342                }
343                if (!isElemental(a)) {
344                        isElemental = false;
345                        a = compound2Interface.get(a);
346                }
347                if (!isElemental(b)) {
348                        isElemental = false;
349                        b = compound2Interface.get(b);
350                }
351
352                if (isFloating(a)) {
353                        if (!isFloating(b)) {
354                                b = getBestFloatInterface(b); // note doesn't change if not numerical!!!
355                        } else if (isComplex(b)) {
356                                a = DoubleDataset.class.isAssignableFrom(a) ? ComplexDoubleDataset.class : ComplexFloatDataset.class;
357                        }
358                        if (isComplex(a) && !isComplex(b)) {
359                                b = DoubleDataset.class.isAssignableFrom(b) ? ComplexDoubleDataset.class : ComplexFloatDataset.class;
360                        }
361                } else if (isFloating(b)) {
362                        a = getBestFloatInterface(a);
363                        if (isComplex(b)) {
364                                a = DoubleDataset.class.isAssignableFrom(a) ? ComplexDoubleDataset.class : ComplexFloatDataset.class;
365                        }
366                }
367
368                Class<? extends Dataset> c = isBetter(interface2Class.get(a), interface2Class.get(b)) ? a : b;
369
370                if (!isElemental && interface2Compound.containsKey(c)) {
371                        c = interface2Compound.get(c);
372                }
373                return c;
374        }
375
376        private static boolean isBetter(Class<?> a, Class<?> b) {
377                for (Class<?> k : elementBytes.keySet()) { // elements order in increasing width (for numerical primitives)
378                        if (k.equals(b)) {
379                                return true;
380                        }
381                        if (k.equals(a)) {
382                                return false;
383                        }
384                }
385                return true;
386        }
387
388        /**
389         * The largest dataset type suitable for a summation of around a few thousand items without changing from the "kind"
390         * of dataset
391         *
392         * @param otype
393         * @return largest dataset type available for given dataset type
394         */
395        public static Class<? extends Dataset> getLargestInterface(Dataset a) {
396                if (a instanceof BooleanDataset || a instanceof ByteDataset || a instanceof ShortDataset) {
397                        return IntegerDataset.class;
398                } else if (a instanceof IntegerDataset) {
399                        return LongDataset.class;
400                } else if (a instanceof FloatDataset) {
401                        return DoubleDataset.class;
402                } else if (a instanceof ComplexFloatDataset) {
403                        return ComplexDoubleDataset.class;
404                } else if (a instanceof CompoundByteDataset || a instanceof CompoundShortDataset) {
405                        return CompoundIntegerDataset.class;
406                } else if (a instanceof CompoundIntegerDataset) {
407                        return CompoundLongDataset.class;
408                } else if (a instanceof CompoundFloatDataset) {
409                        return CompoundDoubleDataset.class;
410                }
411                return a.getClass();
412        }
413
414        /**
415         * Find floating point dataset interface that best fits given types. The best type takes into account complex and array
416         * datasets
417         *
418         * @param clazz
419         *            old dataset class
420         * @return best dataset interface
421         */
422        public static Class<? extends Dataset> getBestFloatInterface(Class<? extends Dataset> clazz) {
423                Class<?> e = interface2Class.get(clazz);
424                if (bestFloatElement.containsKey(e)) {
425                        e = bestFloatElement.get(e);
426                        return class2Interface.get(e);
427                }
428                return clazz;
429        }
430
431        /**
432         * @param isize
433         *            number of elements in an item
434         * @param dtype
435         * @return length of single item in bytes
436         */
437        public static int getItemBytes(final int isize, Class<? extends Dataset> clazz) {
438                int bytes = elementBytes.get(interface2Class.get(clazz));
439
440                return isize * bytes;
441        }
442
443        /**
444         * Convert double array to primitive array
445         * @param clazz dataset interface
446         * @param x
447         * @return biggest native primitive array if integer. Return null if not interface is not numerical
448         */
449        public static Object fromDoublesToBiggestPrimitives(Class<? extends Dataset> clazz, double[] x) {
450                if (BooleanDataset.class.isAssignableFrom(clazz) || ByteDataset.class.isAssignableFrom(clazz)
451                                || ShortDataset.class.isAssignableFrom(clazz) || IntegerDataset.class.isAssignableFrom(clazz)) {
452                        int[] i32 = new int[x.length];
453                        for (int i = 0; i < x.length; i++) {
454                                i32[i] = (int) (long) x[i];
455                        }
456                        return i32;
457                } else if (LongDataset.class.isAssignableFrom(clazz)) {
458                        long[] i64 = new long[x.length];
459                        for (int i = 0; i < x.length; i++) {
460                                i64[i] = (long) x[i];
461                        }
462                        return i64;
463                } else if (FloatDataset.class.isAssignableFrom(clazz)) {
464                        float[] f32 = new float[x.length];
465                        for (int i = 0; i < x.length; i++) {
466                                f32[i] = (float) x[i];
467                        }
468                        return f32;
469                } else if (DoubleDataset.class.isAssignableFrom(clazz)) {
470                        return x;
471                }
472                return null;
473        }
474
475        /**
476         * Convert double to number
477         * @param clazz dataset interface
478         * @param x
479         * @return biggest number if integer. Return null if not interface is not numerical
480         */
481        public static Number fromDoubleToBiggestNumber(Class<? extends Dataset> clazz, double x) {
482                if (BooleanDataset.class.isAssignableFrom(clazz) || ByteDataset.class.isAssignableFrom(clazz)
483                                || ShortDataset.class.isAssignableFrom(clazz) || IntegerDataset.class.isAssignableFrom(clazz)) {
484                        return Integer.valueOf((int) (long) x);
485                } else if (LongDataset.class.isAssignableFrom(clazz)) {
486                        return Long.valueOf((long) x);
487                } else if (FloatDataset.class.isAssignableFrom(clazz)) {
488                        return Float.valueOf((float) x);
489                } else if (DoubleDataset.class.isAssignableFrom(clazz)) {
490                        return Double.valueOf(x);
491                }
492                return null;
493        }
494
495        /**
496         * @param clazz dataset interface
497         * @param x
498         * @return biggest native primitive if integer
499         * @since 2.3
500         */
501        public static Number toBiggestNumber(Class<? extends Dataset> clazz, Number x) {
502                if (BooleanDataset.class.isAssignableFrom(clazz) || ByteDataset.class.isAssignableFrom(clazz)
503                                || ShortDataset.class.isAssignableFrom(clazz) || IntegerDataset.class.isAssignableFrom(clazz)) {
504                        return x instanceof Integer ? x : Integer.valueOf(x.intValue());
505                } else if (LongDataset.class.isAssignableFrom(clazz)) {
506                        return x instanceof Long ? x : Long.valueOf(x.longValue());
507                } else if (FloatDataset.class.isAssignableFrom(clazz)) {
508                        return x instanceof Float ? x : Float.valueOf(x.floatValue());
509                } else if (DoubleDataset.class.isAssignableFrom(clazz)) {
510                        return x instanceof Double ? x : Double.valueOf(x.doubleValue());
511                }
512                return null;
513        }
514}