    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}