content: /*
   * Copyright (c) 2002, 2020, Oracle and/or its affiliates.
   *
   * This program is free software; you can redistribute it and/or modify it under
   * the terms of the GNU General Public License, version 2.0, as published by the
   * Free Software Foundation.
   *
   * This program is also distributed with certain software (including but not
   * limited to OpenSSL) that is licensed under separate terms, as designated in a
   * particular file or component or in included license documentation. The
   * authors of MySQL hereby grant you an additional permission to link the
   * program and your derivative works with the separately licensed software that
   * they have included with MySQL.
   *
   * Without limiting anything contained in the foregoing, this file, which is
   * part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
   * version 1.0, a copy of which can be found at
   * http://oss.oracle.com/licenses/universal-foss-exception.
   *
   * This program is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   * FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
   * for more details.
   *
   * You should have received a copy of the GNU General Public License along with
   * this program; if not, write to the Free Software Foundation, Inc.,
   * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
   */
  
  package com.mysql.cj.util;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.PrintWriter;
  import java.io.Reader;
  import java.io.StringWriter;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.InvocationTargetException;
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.LinkedHashSet;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.concurrent.ConcurrentMap;
  
  import com.mysql.cj.Constants;
  import com.mysql.cj.Messages;
  import com.mysql.cj.exceptions.CJException;
  import com.mysql.cj.exceptions.ExceptionFactory;
  import com.mysql.cj.exceptions.ExceptionInterceptor;
  import com.mysql.cj.exceptions.WrongArgumentException;
  
  /**
   * Various utility methods for the driver.
   */
  public class Util {
      private static int jvmVersion = 8; // use default base version supported
  
      private static int jvmUpdateNumber = -1;
  
      static {
          int startPos = Constants.JVM_VERSION.indexOf('.');
          int endPos = startPos + 1;
          if (startPos != -1) {
              while (Character.isDigit(Constants.JVM_VERSION.charAt(endPos)) && ++endPos < Constants.JVM_VERSION.length()) {
                  // continue
              }
          }
          startPos++;
          if (endPos > startPos) {
              jvmVersion = Integer.parseInt(Constants.JVM_VERSION.substring(startPos, endPos));
          }
          startPos = Constants.JVM_VERSION.indexOf("_");
          endPos = startPos + 1;
          if (startPos != -1) {
              while (Character.isDigit(Constants.JVM_VERSION.charAt(endPos)) && ++endPos < Constants.JVM_VERSION.length()) {
                  // continue
              }
          }
          startPos++;
          if (endPos > startPos) {
              jvmUpdateNumber = Integer.parseInt(Constants.JVM_VERSION.substring(startPos, endPos));
          }
  
      }
  
      public static int getJVMVersion() {
          return jvmVersion;
      }
  
      public static boolean jvmMeetsMinimum(int version, int updateNumber) {
          return getJVMVersion() > version || getJVMVersion() == version && getJVMUpdateNumber() >= updateNumber;
      }
  
      public static int getJVMUpdateNumber() {
          return jvmUpdateNumber;
      }
  
      /**
       * Checks whether the given server version string is a MySQL Community edition
       * 
       * @param serverVersion
       *            full server version string
       * @return true if version does not contain "enterprise", "commercial" or "advanced"
       */
      public static boolean isCommunityEdition(String serverVersion) {
          return !isEnterpriseEdition(serverVersion);
      }
  
      /**
       * Checks whether the given server version string is a MySQL Enterprise edition
       * 
       * @param serverVersion
       *            full server version string
       * @return true if version contains "enterprise", "commercial" or "advanced"
       */
      public static boolean isEnterpriseEdition(String serverVersion) {
          return serverVersion.contains("enterprise") || serverVersion.contains("commercial") || serverVersion.contains("advanced");
      }
  
      /**
       * Converts a nested exception into a nicer message
       * 
       * @param ex
       *            the exception to expand into a message.
       * 
       * @return a message containing the exception, the message (if any), and a
       *         stacktrace.
       */
      public static String stackTraceToString(Throwable ex) {
          StringBuilder traceBuf = new StringBuilder();
          traceBuf.append(Messages.getString("Util.1"));
  
          if (ex != null) {
              traceBuf.append(ex.getClass().getName());
  
              String message = ex.getMessage();
  
              if (message != null) {
                  traceBuf.append(Messages.getString("Util.2"));
                  traceBuf.append(message);
              }
  
              StringWriter out = new StringWriter();
  
              PrintWriter printOut = new PrintWriter(out);
  
              ex.printStackTrace(printOut);
  
              traceBuf.append(Messages.getString("Util.3"));
              traceBuf.append(out.toString());
          }
  
          traceBuf.append(Messages.getString("Util.4"));
  
          return traceBuf.toString();
      }
  
      public static Object getInstance(String className, Class<?>[] argTypes, Object[] args, ExceptionInterceptor exceptionInterceptor, String errorMessage) {
  
          try {
              return handleNewInstance(Class.forName(className).getConstructor(argTypes), args, exceptionInterceptor);
          } catch (SecurityException | NoSuchMethodException | ClassNotFoundException e) {
              throw ExceptionFactory.createException(WrongArgumentException.class, errorMessage, e, exceptionInterceptor);
          }
      }
  
      public static Object getInstance(String className, Class<?>[] argTypes, Object[] args, ExceptionInterceptor exceptionInterceptor) {
          return getInstance(className, argTypes, args, exceptionInterceptor, "Can't instantiate required class");
      }
  
      /**
       * Handles constructing new instance with the given constructor and wrapping
       * (or not, as required) the exceptions that could possibly be generated
       * 
       * @param ctor
       *            constructor
       * @param args
       *            arguments for constructor
       * @param exceptionInterceptor
       *            exception interceptor
       * @return object
       */
      public static Object handleNewInstance(Constructor<?> ctor, Object[] args, ExceptionInterceptor exceptionInterceptor) {
          try {
  
              return ctor.newInstance(args);
          } catch (IllegalArgumentException | InstantiationException | IllegalAccessException e) {
              throw ExceptionFactory.createException(WrongArgumentException.class, "Can't instantiate required class", e, exceptionInterceptor);
          } catch (InvocationTargetException e) {
              Throwable target = e.getTargetException();
  
              if (target instanceof ExceptionInInitializerError) {
                  target = ((ExceptionInInitializerError) target).getException();
              } else if (target instanceof CJException) {
                  throw (CJException) target;
              }
  
              throw ExceptionFactory.createException(WrongArgumentException.class, target.getMessage(), target, exceptionInterceptor);
          }
      }
  
      /**
       * Does a network interface exist locally with the given hostname?
       * 
       * @param hostname
       *            the hostname (or IP address in string form) to check
       * @return true if it exists, false if no, or unable to determine due to VM
       *         version support of java.net.NetworkInterface
       */
      public static boolean interfaceExists(String hostname) {
          try {
              Class<?> networkInterfaceClass = Class.forName("java.net.NetworkInterface");
              return networkInterfaceClass.getMethod("getByName", (Class[]) null).invoke(networkInterfaceClass, new Object[] { hostname }) != null;
          } catch (Throwable t) {
              return false;
          }
      }
  
      public static Map<Object, Object> calculateDifferences(Map<?, ?> map1, Map<?, ?> map2) {
          Map<Object, Object> diffMap = new HashMap<>();
  
          for (Map.Entry<?, ?> entry : map1.entrySet()) {
              Object key = entry.getKey();
  
              Number value1 = null;
              Number value2 = null;
  
              if (entry.getValue() instanceof Number) {
  
                  value1 = (Number) entry.getValue();
                  value2 = (Number) map2.get(key);
              } else {
                  try {
                      value1 = new Double(entry.getValue().toString());
                      value2 = new Double(map2.get(key).toString());
                  } catch (NumberFormatException nfe) {
                      continue;
                  }
              }
  
              if (value1.equals(value2)) {
                  continue;
              }
  
              if (value1 instanceof Byte) {
                  diffMap.put(key, Byte.valueOf((byte) (((Byte) value2).byteValue() - ((Byte) value1).byteValue())));
              } else if (value1 instanceof Short) {
                  diffMap.put(key, Short.valueOf((short) (((Short) value2).shortValue() - ((Short) value1).shortValue())));
              } else if (value1 instanceof Integer) {
                  diffMap.put(key, Integer.valueOf((((Integer) value2).intValue() - ((Integer) value1).intValue())));
              } else if (value1 instanceof Long) {
                  diffMap.put(key, Long.valueOf((((Long) value2).longValue() - ((Long) value1).longValue())));
              } else if (value1 instanceof Float) {
                  diffMap.put(key, Float.valueOf(((Float) value2).floatValue() - ((Float) value1).floatValue()));
              } else if (value1 instanceof Double) {
                  diffMap.put(key, Double.valueOf((((Double) value2).shortValue() - ((Double) value1).shortValue())));
              } else if (value1 instanceof BigDecimal) {
                  diffMap.put(key, ((BigDecimal) value2).subtract((BigDecimal) value1));
              } else if (value1 instanceof BigInteger) {
                  diffMap.put(key, ((BigInteger) value2).subtract((BigInteger) value1));
              }
          }
  
          return diffMap;
      }
  
      public static <T> List<T> loadClasses(String extensionClassNames, String errorMessageKey, ExceptionInterceptor exceptionInterceptor) {
  
          List<T> instances = new LinkedList<>();
  
          List<String> interceptorsToCreate = StringUtils.split(extensionClassNames, ",", true);
  
          String className = null;
  
          try {
              for (int i = 0, s = interceptorsToCreate.size(); i < s; i++) {
                  className = interceptorsToCreate.get(i);
                  @SuppressWarnings("unchecked")
                  T instance = (T) Class.forName(className).newInstance();
  
                  instances.add(instance);
              }
  
          } catch (Throwable t) {
              throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString(errorMessageKey, new Object[] { className }), t,
                      exceptionInterceptor);
          }
  
          return instances;
      }
  
      /** Cache for the JDBC interfaces already verified */
      private static final ConcurrentMap<Class<?>, Boolean> isJdbcInterfaceCache = new ConcurrentHashMap<>();
  
      /**
       * Recursively checks for interfaces on the given class to determine if it implements a java.sql, javax.sql or com.mysql.cj.jdbc interface.
       * 
       * @param clazz
       *            The class to investigate.
       * @return boolean
       */
      public static boolean isJdbcInterface(Class<?> clazz) {
          if (Util.isJdbcInterfaceCache.containsKey(clazz)) {
              return (Util.isJdbcInterfaceCache.get(clazz));
          }
  
          if (clazz.isInterface()) {
              try {
                  if (isJdbcPackage(clazz.getPackage().getName())) {
                      Util.isJdbcInterfaceCache.putIfAbsent(clazz, true);
                      return true;
                  }
              } catch (Exception ex) {
                  /*
                   * We may experience a NPE from getPackage() returning null, or class-loading facilities.
                   * This happens when this class is instrumented to implement runtime-generated interfaces.
                   */
              }
          }
  
          for (Class<?> iface : clazz.getInterfaces()) {
              if (isJdbcInterface(iface)) {
                  Util.isJdbcInterfaceCache.putIfAbsent(clazz, true);
                  return true;
              }
          }
  
          if (clazz.getSuperclass() != null && isJdbcInterface(clazz.getSuperclass())) {
              Util.isJdbcInterfaceCache.putIfAbsent(clazz, true);
              return true;
          }
  
          Util.isJdbcInterfaceCache.putIfAbsent(clazz, false);
          return false;
      }
  
      /**
       * Check if the package name is a known JDBC package.
       * 
       * @param packageName
       *            The package name to check.
       * @return boolean
       */
      public static boolean isJdbcPackage(String packageName) {
          return packageName != null
                  && (packageName.startsWith("java.sql") || packageName.startsWith("javax.sql") || packageName.startsWith("com.mysql.cj.jdbc"));
      }
  
      /** Cache for the implemented interfaces searched. */
      private static final ConcurrentMap<Class<?>, Class<?>[]> implementedInterfacesCache = new ConcurrentHashMap<>();
  
      /**
       * Retrieves a list with all interfaces implemented by the given class. If possible gets this information from a cache instead of navigating through the
       * object hierarchy. Results are stored in a cache for future reference.
       * 
       * @param clazz
       *            The class from which the interface list will be retrieved.
       * @return
       *         An array with all the interfaces for the given class.
       */
      public static Class<?>[] getImplementedInterfaces(Class<?> clazz) {
          Class<?>[] implementedInterfaces = Util.implementedInterfacesCache.get(clazz);
          if (implementedInterfaces != null) {
              return implementedInterfaces;
          }
  
          Set<Class<?>> interfaces = new LinkedHashSet<>();
          Class<?> superClass = clazz;
          do {
              Collections.addAll(interfaces, superClass.getInterfaces());
          } while ((superClass = superClass.getSuperclass()) != null);
  
          implementedInterfaces = interfaces.toArray(new Class<?>[interfaces.size()]);
          Class<?>[] oldValue = Util.implementedInterfacesCache.putIfAbsent(clazz, implementedInterfaces);
          if (oldValue != null) {
              implementedInterfaces = oldValue;
          }
  
          return implementedInterfaces;
      }
  
      /**
       * Computes the number of seconds elapsed since the given time in milliseconds.
       * 
       * @param timeInMillis
       *            The past instant in milliseconds.
       * @return
       *         The number of seconds, truncated, elapsed since timeInMillis.
       */
      public static long secondsSinceMillis(long timeInMillis) {
          return (System.currentTimeMillis() - timeInMillis) / 1000;
      }
  
      /**
       * Converts long to int, truncating to maximum/minimum value if needed.
       * 
       * @param longValue
       *            long value
       * @return int value
       */
      public static int truncateAndConvertToInt(long longValue) {
          return longValue > Integer.MAX_VALUE ? Integer.MAX_VALUE : longValue < Integer.MIN_VALUE ? Integer.MIN_VALUE : (int) longValue;
      }
  
      /**
       * Converts long[] to int[], truncating to maximum/minimum value if needed.
       * 
       * @param longArray
       *            log values
       * @return int values
       */
      public static int[] truncateAndConvertToInt(long[] longArray) {
          int[] intArray = new int[longArray.length];
  
          for (int i = 0; i < longArray.length; i++) {
              intArray[i] = longArray[i] > Integer.MAX_VALUE ? Integer.MAX_VALUE : longArray[i] < Integer.MIN_VALUE ? Integer.MIN_VALUE : (int) longArray[i];
          }
          return intArray;
      }
  
      /**
       * Returns the package name of the given class.
       * Using clazz.getPackage().getName() is not an alternative because under some class loaders the method getPackage() just returns null.
       * 
       * @param clazz
       *            the Class from which to get the package name
       * @return the package name
       */
      public static String getPackageName(Class<?> clazz) {
          String fqcn = clazz.getName();
          int classNameStartsAt = fqcn.lastIndexOf('.');
          if (classNameStartsAt > 0) {
              return fqcn.substring(0, classNameStartsAt);
          }
          return "";
      }
  
      /**
       * Checks if the JVM is running on Windows Operating System.
       * 
       * @return
       *         <code>true</code> if currently running on Windows, <code>false</code> otherwise.
       */
      public static boolean isRunningOnWindows() {
          return StringUtils.indexOfIgnoreCase(Constants.OS_NAME, "WINDOWS") != -1;
      }
  
      /**
       * Reads length bytes from reader into buf. Blocks until enough input is
       * available
       * 
       * @param reader
       *            {@link Reader}
       * @param buf
       *            char array to read into
       * @param length
       *            number of chars to read
       * 
       * @return the actual number of chars read
       * 
       * @throws IOException
       *             if an error occurs
       */
      public static int readFully(Reader reader, char[] buf, int length) throws IOException {
          int numCharsRead = 0;
  
          while (numCharsRead < length) {
              int count = reader.read(buf, numCharsRead, length - numCharsRead);
  
              if (count < 0) {
                  break;
              }
  
              numCharsRead += count;
          }
  
          return numCharsRead;
      }
  
      public static final int readBlock(InputStream i, byte[] b, ExceptionInterceptor exceptionInterceptor) {
          try {
              return i.read(b);
          } catch (Throwable ex) {
              throw ExceptionFactory.createException(Messages.getString("Util.5") + ex.getClass().getName(), exceptionInterceptor);
          }
      }
  
      public static final int readBlock(InputStream i, byte[] b, int length, ExceptionInterceptor exceptionInterceptor) {
          try {
              int lengthToRead = length;
  
              if (lengthToRead > b.length) {
                  lengthToRead = b.length;
              }
  
              return i.read(b, 0, lengthToRead);
          } catch (Throwable ex) {
              throw ExceptionFactory.createException(Messages.getString("Util.5") + ex.getClass().getName(), exceptionInterceptor);
          }
      }
  }
  