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}