/**
  * Sets the mode of a {@link javax.crypto.CipherSpi} instance.
  *
  * <p>Like {@link #getCipher(String)}, we're accessing a private API here, so me must work around
  * the access restrictions
  *
  * @param cipherSpi the {@link javax.crypto.CipherSpi} instance
  * @param mode the mode to set
  * @throws Throwable if {@link javax.crypto.CipherSpi#engineSetPadding} throws
  */
 private static void cipherSpiSetMode(final CipherSpi cipherSpi, final String mode)
     throws Throwable {
   final Method engineSetMode = getMethod(cipherSpi.getClass(), "engineSetMode", String.class);
   engineSetMode.setAccessible(true);
   try {
     engineSetMode.invoke(cipherSpi, mode);
   } catch (final InvocationTargetException e) {
     throw e.getCause();
   }
 }
  /**
   * Replacement for JCA/JCE's {@link javax.crypto.Cipher#getInstance}. The original method only
   * accepts JCE providers from signed jars, which prevents us from bundling our cryptography
   * provider Bouncy Caster with the application.
   *
   * @param transformation the transformation to find an implementation for
   */
  public static Cipher getCipher(final String transformation) {
    try {
      /* Split the transformation into algorithm, mode and padding */

      final Matcher transformation_matcher =
          s_transformation_pattern.matcher(transformation.toUpperCase());
      if (!transformation_matcher.matches())
        throw new RuntimeException("Transformation " + transformation + " is invalid");

      final String algorithm = transformation_matcher.group(1);
      final String mode = transformation_matcher.group(3);
      final String padding = transformation_matcher.group(4);
      final boolean isBareAlgorithm = (mode == null) && (padding == null);

      /* Build the property values we need to search for. */

      final String algorithmModePadding =
          !isBareAlgorithm ? algorithm + "/" + mode + "/" + padding : null;
      final String algorithmMode = !isBareAlgorithm ? algorithm + "/" + mode : null;
      final String algorithmPadding = !isBareAlgorithm ? algorithm + "//" + padding : null;

      /* Search the provider for implementations. We ask for more specific (i.e matching
       * the requested mode and or padding) implementation first, then fall back to more
       * generals ones which we then must configure for the mode and padding.
       */

      final CipherSpi cipherSpi;

      if (!isBareAlgorithm && (resolveProperty(Provider, "Cipher", algorithmModePadding) != null)) {
        @SuppressWarnings("unchecked")
        final Class<? extends CipherSpi> cipherSpiClass =
            (Class<? extends CipherSpi>)
                Class.forName(resolveProperty(Provider, "Cipher", algorithmModePadding));
        cipherSpi = cipherSpiClass.newInstance();
      } else if (!isBareAlgorithm && (resolveProperty(Provider, "Cipher", algorithmMode) != null)) {
        @SuppressWarnings("unchecked")
        final Class<? extends CipherSpi> cipherSpiClass =
            (Class<? extends CipherSpi>)
                Class.forName(resolveProperty(Provider, "Cipher", algorithmMode));
        cipherSpi = cipherSpiClass.newInstance();
        if (!isBareAlgorithm) cipherSpiSetPadding(cipherSpi, padding);
      } else if (!isBareAlgorithm
          && (resolveProperty(Provider, "Cipher", algorithmPadding) != null)) {
        @SuppressWarnings("unchecked")
        final Class<? extends CipherSpi> cipherSpiClass =
            (Class<? extends CipherSpi>)
                Class.forName(resolveProperty(Provider, "Cipher", algorithmPadding));
        cipherSpi = cipherSpiClass.newInstance();
        if (!isBareAlgorithm) cipherSpiSetMode(cipherSpi, mode);
      } else if (resolveProperty(Provider, "Cipher", algorithm) != null) {
        @SuppressWarnings("unchecked")
        final Class<? extends CipherSpi> cipherSpiClass =
            (Class<? extends CipherSpi>)
                Class.forName(resolveProperty(Provider, "Cipher", algorithm));
        cipherSpi = cipherSpiClass.newInstance();
        if (!isBareAlgorithm) {
          cipherSpiSetMode(cipherSpi, mode);
          cipherSpiSetPadding(cipherSpi, padding);
        }
      } else {
        throw new RuntimeException(
            "Provider "
                + Provider.getName()
                + " ("
                + Provider.getClass()
                + ") does not implement "
                + transformation);
      }

      /* Create a {@link javax.crypto.Cipher} instance from the {@link javax.crypto.CipherSpi} the provider gave us */

      s_logger.info("Using SPI " + cipherSpi.getClass() + " for " + transformation);
      return getCipher(cipherSpi, transformation.toUpperCase());
    } catch (final RuntimeException e) {
      throw e;
    } catch (final Error e) {
      throw e;
    } catch (final Throwable e) {
      throw new RuntimeException(
          "Provider "
              + Provider.getName()
              + " ("
              + Provider.getClass()
              + ") failed to instanciate "
              + transformation,
          e);
    }
  }