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