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}