/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ignite.internal.util.typedef; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteSystemProperties; import org.apache.ignite.internal.util.GridLeanMap; import org.apache.ignite.internal.util.typedef.internal.SB; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteFuture; import org.jetbrains.annotations.Nullable; import static org.apache.ignite.IgniteSystemProperties.IGNITE_HOME; /** * Defines global scope. * <p> * Contains often used utility functions allowing to cut down on code bloat. This * is somewhat analogous to {@code Predef} in Scala. Note that this should only be used * when this typedef <b>does not sacrifice</b> the code readability. */ public final class X { /** An empty immutable {@code Object} array. */ public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; /** Time span dividers. */ private static final long[] SPAN_DIVS = new long[] {1000L, 60L, 60L, 60L}; /** The names of methods commonly used to access a wrapped exception. */ private static final String[] CAUSE_MTD_NAMES = new String[] { "getCause", "getNextException", "getTargetException", "getException", "getSourceException", "getRootCause", "getCausedByException", "getNested", "getLinkedException", "getNestedException", "getLinkedCause", "getThrowable" }; /** The Method object for Java 1.4 getCause. */ private static final Method THROWABLE_CAUSE_METHOD; /** * */ static { Method causeMtd; try { causeMtd = Throwable.class.getMethod("getCause", null); } catch (Exception ignored) { causeMtd = null; } THROWABLE_CAUSE_METHOD = causeMtd; } /** * Ensures singleton. */ private X() { // No-op. } /** * Alias for {@code System.out.println()}. */ public static void println() { System.out.println(); } /** * Alias for {@code System.err.println()}. */ public static void printerrln() { System.err.println(); } /** * Alias for {@code System.out.println}. * * @param s1 First string to print. * @param rest Optional list of objects to print as well. */ public static void println(@Nullable String s1, @Nullable Object... rest) { System.out.println(s1); if (rest != null && rest.length > 0) for (Object obj : rest) System.out.println(obj); } /** * Alias for {@code System.err.println}. * * @param s1 First string to print. * @param rest Optional list of objects to print as well. */ public static void printerrln(@Nullable String s1, @Nullable Object... rest) { error(s1, rest); } /** * Alias for {@code System.err.println}. * * @param s1 First string to print. * @param rest Optional list of objects to print as well. */ public static void error(@Nullable String s1, @Nullable Object... rest) { System.err.println(s1); if (rest != null && rest.length > 0) for (Object obj : rest) System.err.println(obj); } /** * Alias for {@code System.out.print}. * * @param s1 First string to print. * @param rest Optional list of objects to print as well. */ public static void print(@Nullable String s1, @Nullable Object... rest) { System.out.print(s1); if (rest != null && rest.length > 0) for (Object obj : rest) System.out.print(obj); } /** * Alias for {@code System.err.print}. * * @param s1 First string to print. * @param rest Optional list of objects to print as well. */ public static void printerr(@Nullable String s1, @Nullable Object... rest) { System.err.print(s1); if (rest != null && rest.length > 0) for (Object obj : rest) System.err.print(obj); } /** * Gets either system property or environment variable with given name. * * @param name Name of the system property or environment variable. * @return Value of the system property or environment variable. Returns * {@code null} if neither can be found for given name. */ @Nullable public static String getSystemOrEnv(String name) { assert name != null; String v = System.getProperty(name); if (v == null) v = System.getenv(name); return v; } /** * Creates string presentation of given time {@code span} in hh:mm:ss:msec {@code HMSM} format. * * @param span Time span. * @return String presentation. */ public static String timeSpan2HMSM(long span) { long[] t = new long[4]; long sp = span; for (int i = 0; i < SPAN_DIVS.length && sp > 0; sp /= SPAN_DIVS[i++]) t[i] = sp % SPAN_DIVS[i]; return (t[3] < 10 ? "0" + t[3] : Long.toString(t[3])) + ':' + (t[2] < 10 ? "0" + t[2] : Long.toString(t[2])) + ':' + (t[1] < 10 ? "0" + t[1] : Long.toString(t[1])) + ':' + (t[0] < 10 ? "00" + t[0] : ( t[0] < 100 ? "0" + t[0] : Long.toString(t[0]))); } /** * Creates string presentation of given time {@code span} in hh:mm:ss {@code HMS} format. * * @param span Time span. * @return String presentation. */ public static String timeSpan2HMS(long span) { long[] t = new long[4]; long sp = span; for (int i = 0; i < SPAN_DIVS.length && sp > 0; sp /= SPAN_DIVS[i++]) t[i] = sp % SPAN_DIVS[i]; return (t[3] < 10 ? "0" + t[3] : Long.toString(t[3])) + ':' + (t[2] < 10 ? "0" + t[2] : Long.toString(t[2])) + ':' + (t[1] < 10 ? "0" + t[1] : Long.toString(t[1])); } /** * Clones a passed in object. If parameter {@code deep} is set to {@code true} * then this method will use deep cloning algorithm based on deep reflection * ignoring {@link Cloneable} interface unless parameter {@code honorCloneable} * is set to false. * <p> * If {@code deep} is {@code false} then this method will check the object for * {@link Cloneable} interface and use {@link Object#clone()} to make a copy, * otherwise the object itself will be returned. * * @param obj Object to create a clone from. * @param deep {@code true} to use algorithm of deep cloning. If {@code false} * then this method will always be checking whether a passed in object * implements interface {@link Cloneable} and if it does then method * {@link Object#clone()} will be used to clone object, if does not * then the object itself will be returned. * @param honorCloneable Flag indicating whether {@link Cloneable} interface * should be honored or not when cloning. This parameter makes sense only if * parameter {@code deep} is set to {@code true}. * @param <T> Type of cloning object. * @return Copy of a passed in object. */ @SuppressWarnings({"unchecked"}) @Nullable public static <T> T cloneObject(@Nullable T obj, boolean deep, boolean honorCloneable) { if (obj == null) return null; try { return !deep ? shallowClone(obj) : (T)deepClone(new GridLeanMap<Integer, Integer>(), new ArrayList<>(), obj, honorCloneable); } catch (Exception e) { throw new IgniteException("Unable to clone instance of class: " + obj.getClass(), e); } } /** * @param obj Object to make a clone for. * @param <T> Type of cloning object. * @return Copy of a passed in object. */ @SuppressWarnings({"unchecked"}) @Nullable private static <T> T shallowClone(@Nullable T obj) { if (obj == null) return null; if (!(obj instanceof Cloneable)) return obj; if (obj.getClass().isArray()) return obj instanceof byte[] ? (T)(((byte[])obj).clone()) : obj instanceof short[] ? (T)(((short[])obj).clone()) : obj instanceof char[] ? (T)(((char[])obj).clone()) : obj instanceof int[] ? (T)(((int[])obj).clone()) : obj instanceof long[] ? (T)(((long[])obj).clone()) : obj instanceof float[] ? (T)(((float[])obj).clone()) : obj instanceof double[] ? (T)(((double[])obj).clone()) : obj instanceof boolean[] ? (T)(((boolean[])obj).clone()) : (T)(((Object[])obj).clone()); try { // 'getDeclaredMethods' searches for ALL methods, 'getMethods' - only public methods. Method mtd = obj.getClass().getDeclaredMethod("clone"); boolean set = false; if (!mtd.isAccessible()) mtd.setAccessible(set = true); T clone = (T)mtd.invoke(obj); if (set) mtd.setAccessible(false); return clone; } catch (Exception e) { throw new IgniteException("Unable to clone instance of class: " + obj.getClass(), e); } } /** * Recursively clones the object. * * @param identityIdxs Map of object identities to indexes in {@code clones} parameter. * @param clones List of already cloned objects. * @param obj The object to deep-clone. * @param honorCloneable {@code true} if method should account {@link Cloneable} interface. * @return Clone of the input object. * @throws Exception If deep-cloning fails. */ @Nullable private static Object deepClone(Map<Integer, Integer> identityIdxs, List<Object> clones, @Nullable Object obj, boolean honorCloneable) throws Exception { if (obj == null) return null; if (honorCloneable && obj instanceof Cloneable) return shallowClone(obj); Integer idx = identityIdxs.get(System.identityHashCode(obj)); Object clone = null; if (idx != null) clone = clones.get(idx); if (clone != null) return clone; if (obj instanceof Class) // No clone needed for java.lang.Class instance. return obj; Class cls = obj.getClass(); if (cls.isArray()) { Class<?> arrType = cls.getComponentType(); int len = Array.getLength(obj); clone = Array.newInstance(arrType, len); for (int i = 0; i < len; i++) Array.set(clone, i, deepClone(identityIdxs, clones, Array.get(obj, i), honorCloneable)); clones.add(clone); identityIdxs.put(System.identityHashCode(obj), clones.size() - 1); return clone; } clone = U.forceNewInstance(cls); if (clone == null) throw new IgniteException("Failed to clone object (empty constructor could not be assigned): " + obj); clones.add(clone); identityIdxs.put(System.identityHashCode(obj), clones.size() - 1); for (Class<?> c = cls; c != Object.class; c = c.getSuperclass()) for (Field f : c.getDeclaredFields()) cloneField(identityIdxs, clones, obj, clone, f, honorCloneable); return clone; } /** * @param identityIdxs Map of object identities to indexes in {@code clones} parameter. * @param clones List of already cloned objects. * @param obj Object to clone. * @param clone Clone. * @param f Field to clone. * @param honorCloneable {@code true} if method should account {@link Cloneable} interface. * @throws Exception If failed. */ private static void cloneField(Map<Integer, Integer> identityIdxs, List<Object> clones, Object obj, Object clone, Field f, boolean honorCloneable) throws Exception { int modifiers = f.getModifiers(); // Skip over static fields. if (Modifier.isStatic(modifiers)) return; boolean set = false; if (!f.isAccessible()) { f.setAccessible(true); set = true; } try { if (f.getType().isPrimitive()) f.set(clone, f.get(obj)); else f.set(clone, deepClone(identityIdxs, clones, f.get(obj), honorCloneable)); } finally { if (set) f.setAccessible(false); } } /** * Checks if passed in {@code 'Throwable'} has given class in {@code 'cause'} hierarchy * <b>including</b> that throwable itself. * <p> * Note that this method follows includes {@link Throwable#getSuppressed()} * into check. * * @param t Throwable to check (if {@code null}, {@code false} is returned). * @param cls Cause classes to check (if {@code null} or empty, {@code false} is returned). * @return {@code True} if one of the causing exception is an instance of passed in classes, * {@code false} otherwise. */ @SafeVarargs public static boolean hasCause(@Nullable Throwable t, @Nullable Class<? extends Throwable>... cls) { if (t == null || F.isEmpty(cls)) return false; assert cls != null; for (Throwable th = t; th != null; th = th.getCause()) { for (Class<? extends Throwable> c : cls) { if (c.isAssignableFrom(th.getClass())) return true; } for (Throwable n : th.getSuppressed()) { if (hasCause(n, cls)) return true; } if (th.getCause() == th) break; } return false; } /** * Checks if passed throwable has given class in one of the suppressed exceptions. * * @param t Throwable to check (if {@code null}, {@code false} is returned). * @param cls Class to check. * @return {@code True} if one of the suppressed exceptions is an instance of passed class, * {@code false} otherwise. */ public static boolean hasSuppressed(@Nullable Throwable t, @Nullable Class<? extends Throwable> cls) { if (t == null || cls == null) return false; if (t.getSuppressed() != null) { for (Throwable th : t.getSuppressed()) { if (cls.isAssignableFrom(th.getClass())) return true; if (hasSuppressed(th, cls)) return true; } } return false; } /** * Gets first cause if passed in {@code 'Throwable'} has given class in {@code 'cause'} hierarchy. * <p> * Note that this method follows includes {@link Throwable#getSuppressed()} * into check. * * @param t Throwable to check (if {@code null}, {@code null} is returned). * @param cls Cause class to get cause (if {@code null}, {@code null} is returned). * @return First causing exception of passed in class, {@code null} otherwise. */ @SuppressWarnings({"unchecked"}) @Nullable public static <T extends Throwable> T cause(@Nullable Throwable t, @Nullable Class<T> cls) { if (t == null || cls == null) return null; for (Throwable th = t; th != null; th = th.getCause()) { if (cls.isAssignableFrom(th.getClass())) return (T)th; for (Throwable n : th.getSuppressed()) { T found = cause(n, cls); if (found != null) return found; } if (th.getCause() == th) break; } return null; } /** * @param throwable The exception to examine. * @return The wrapped exception, or {@code null} if not found. */ private static Throwable getCauseUsingWellKnownTypes(Throwable throwable) { if (throwable instanceof SQLException) return ((SQLException)throwable).getNextException(); if (throwable instanceof InvocationTargetException) return ((InvocationTargetException)throwable).getTargetException(); return null; } /** * Finds a {@code Throwable} by method name. * * @param throwable The exception to examine. * @param mtdName The name of the method to find and invoke. * @return The wrapped exception, or {@code null} if not found. */ private static Throwable getCauseUsingMethodName(Throwable throwable, String mtdName) { Method mtd = null; try { mtd = throwable.getClass().getMethod(mtdName, null); } catch (NoSuchMethodException | SecurityException ignored) { // exception ignored } if (mtd != null && Throwable.class.isAssignableFrom(mtd.getReturnType())) { try { return (Throwable)mtd.invoke(throwable, EMPTY_OBJECT_ARRAY); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ignored) { // exception ignored } } return null; } /** * Finds a {@code Throwable} by field name. * * @param throwable The exception to examine. * @param fieldName The name of the attribute to examine. * @return The wrapped exception, or {@code null} if not found. */ private static Throwable getCauseUsingFieldName(Throwable throwable, String fieldName) { Field field = null; try { field = throwable.getClass().getField(fieldName); } catch (NoSuchFieldException | SecurityException ignored) { // exception ignored } if (field != null && Throwable.class.isAssignableFrom(field.getType())) { try { return (Throwable)field.get(throwable); } catch (IllegalAccessException | IllegalArgumentException ignored) { // exception ignored } } return null; } /** * Checks if the Throwable class has a {@code getCause} method. * * This is true for JDK 1.4 and above. * * @return True if Throwable is nestable. */ public static boolean isThrowableNested() { return THROWABLE_CAUSE_METHOD != null; } /** * Checks whether this {@code Throwable} class can store a cause. * * This method does not check whether it actually does store a cause. * * @param throwable The {@code Throwable} to examine, may be null. * @return Boolean {@code true} if nested otherwise {@code false}. */ public static boolean isNestedThrowable(Throwable throwable) { if (throwable == null) return false; if (throwable instanceof SQLException || throwable instanceof InvocationTargetException) return true; if (isThrowableNested()) return true; Class<?> cls = throwable.getClass(); for (String CAUSE_MTD_NAME : CAUSE_MTD_NAMES) { try { Method mtd = cls.getMethod(CAUSE_MTD_NAME, null); if (mtd != null && Throwable.class.isAssignableFrom(mtd.getReturnType())) return true; } catch (NoSuchMethodException | SecurityException ignored) { // exception ignored } } try { Field field = cls.getField("detail"); if (field != null) return true; } catch (NoSuchFieldException | SecurityException ignored) { // exception ignored } return false; } /** * Introspects the {@code Throwable} to obtain the cause. * * The method searches for methods with specific names that return a {@code Throwable} object. * This will pick up most wrapping exceptions, including those from JDK 1.4. * * The default list searched for are:</p> <ul> <li>{@code getCause()}</li> * <li>{@code getNextException()}</li> <li>{@code getTargetException()}</li> * <li>{@code getException()}</li> <li>{@code getSourceException()}</li> * <li>{@code getRootCause()}</li> <li>{@code getCausedByException()}</li> * <li>{@code getNested()}</li> </ul> * * <p>In the absence of any such method, the object is inspected for a {@code detail} * field assignable to a {@code Throwable}.</p> * * If none of the above is found, returns {@code null}. * * @param throwable The throwable to introspect for a cause, may be null. * @return The cause of the {@code Throwable}, * {@code null} if none found or null throwable input. */ public static Throwable getCause(Throwable throwable) { return getCause(throwable, CAUSE_MTD_NAMES); } /** * Introspects the {@code Throwable} to obtain the cause. * * <ol> <li>Try known exception types.</li> * <li>Try the supplied array of method names.</li> * <li>Try the field 'detail'.</li> </ol> * * <p>A {@code null} set of method names means use the default set. * A {@code null} in the set of method names will be ignored.</p> * * @param throwable The throwable to introspect for a cause, may be null. * @param mtdNames The method names, null treated as default set. * @return The cause of the {@code Throwable}, {@code null} if none found or null throwable input. */ public static Throwable getCause(Throwable throwable, String[] mtdNames) { if (throwable == null) return null; Throwable cause = getCauseUsingWellKnownTypes(throwable); if (cause == null) { if (mtdNames == null) mtdNames = CAUSE_MTD_NAMES; for (String mtdName : mtdNames) { if (mtdName != null) { cause = getCauseUsingMethodName(throwable, mtdName); if (cause != null) break; } } if (cause == null) cause = getCauseUsingFieldName(throwable, "detail"); } return cause; } /** * Returns the list of {@code Throwable} objects in the exception chain. * * A throwable without cause will return a list containing one element - the input throwable. * A throwable with one cause will return a list containing two elements - the input throwable * and the cause throwable. A {@code null} throwable will return a list of size zero. * * This method handles recursive cause structures that might otherwise cause infinite loops. * The cause chain is processed until the end is reached, or until the next item in the chain * is already in the result set.</p> * * @param throwable The throwable to inspect, may be null. * @return The list of throwables, never null. */ public static List<Throwable> getThrowableList(Throwable throwable) { List<Throwable> list = new ArrayList<>(); while (throwable != null && !list.contains(throwable)) { list.add(throwable); throwable = getCause(throwable); } return list; } /** * Returns the list of {@code Throwable} objects in the exception chain. * * A throwable without cause will return an array containing one element - the input throwable. * A throwable with one cause will return an array containing two elements - the input throwable * and the cause throwable. A {@code null} throwable will return an array of size zero. * * @param throwable The throwable to inspect, may be null. * @return The array of throwables, never null. */ public static Throwable[] getThrowables(Throwable throwable) { List<Throwable> list = getThrowableList(throwable); return list.toArray(new Throwable[list.size()]); } /** * A way to get the entire nested stack-trace of an throwable. * * The result of this method is highly dependent on the JDK version * and whether the exceptions override printStackTrace or not. * * @param throwable The {@code Throwable} to be examined. * @return The nested stack trace, with the root cause first. */ public static String getFullStackTrace(Throwable throwable) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw, true); Throwable[] ts = getThrowables(throwable); for (Throwable t : ts) { t.printStackTrace(pw); if (isNestedThrowable(t)) break; } return sw.getBuffer().toString(); } /** * Synchronously waits for all futures in the collection. * * @param futs Futures to wait for. */ public static void waitAll(@Nullable Iterable<IgniteFuture<?>> futs) { if (F.isEmpty(futs)) return; for (IgniteFuture fut : futs) fut.get(); } /** * Pretty-formatting for minutes. * * @param mins Minutes to format. * @return Formatted presentation of minutes. */ public static String formatMins(long mins) { assert mins >= 0; if (mins == 0) return "< 1 min"; SB sb = new SB(); long dd = mins / 1440; // 1440 mins = 60 mins * 24 hours if (dd > 0) sb.a(dd).a(dd == 1 ? " day " : " days "); mins %= 1440; long hh = mins / 60; if (hh > 0) sb.a(hh).a(hh == 1 ? " hour " : " hours "); mins %= 60; if (mins > 0) sb.a(mins).a(mins == 1 ? " min " : " mins "); return sb.toString().trim(); } /** * Exits with code {@code -1} if maximum memory is below 90% of minimally allowed threshold. * * @param min Minimum memory threshold. */ public static void checkMinMemory(long min) { long maxMem = Runtime.getRuntime().maxMemory(); if (maxMem < .85 * min) { printerrln("Heap limit is too low (" + (maxMem / (1024 * 1024)) + "MB), please increase heap size at least up to " + (min / (1024 * 1024)) + "MB."); System.exit(-1); } } /** * Copies input byte stream to output byte stream. * * @param in Input byte stream. * @param out Output byte stream. * @param bufSize Intermediate buffer size. * @return Number of the copied bytes. * @throws IOException Thrown if an I/O error occurs. */ public static int copy(InputStream in, OutputStream out, int bufSize) throws IOException { byte[] buf = new byte[bufSize]; int cnt = 0; for (int n; (n = in.read(buf)) > 0;) { out.write(buf, 0, n); cnt += n; } return cnt; } /** * Tries to resolve Ignite installation home folder. * * @return Installation home folder. * @throws IgniteCheckedException If Ignite home folder was not set. */ public static String resolveIgniteHome() throws IgniteCheckedException { String var = IgniteSystemProperties.getString(IGNITE_HOME); if (var != null) return var; else throw new IgniteCheckedException("Failed to resolve Ignite home folder " + "(please set 'IGNITE_HOME' environment or system variable)"); } /** * Parses double from possibly {@code null} or invalid string. * * @param s String to parse double from. If string is null or invalid, a default value is used. * @param dflt Default value for double, if parsing failed. * @return Resulting double. */ public static double parseDouble(@Nullable String s, double dflt) { try { return s != null ? Double.parseDouble(s) : dflt; } catch (NumberFormatException ignored) { return dflt; } } }