/**
   * Creates a new {@link org.ehcache.config.CacheConfigurationBuilder} seeded with the
   * cache-template configuration by the given {@code name} in the XML configuration parsed using
   * {@link #parseConfiguration()}
   *
   * @param name the unique name identifying the cache-template element in the XML
   * @param keyType the type of keys for the {@link org.ehcache.config.CacheConfigurationBuilder} to
   *     use, would need to match the {@code key-type} declared in the template if declared in XML
   * @param valueType the type of values for the {@link
   *     org.ehcache.config.CacheConfigurationBuilder} to use, would need to match the {@code
   *     value-type} declared in the template if declared in XML
   * @param <K> type of keys
   * @param <V> type of values
   * @return the preconfigured {@link org.ehcache.config.CacheConfigurationBuilder} or {@code null}
   *     if no cache-template for the provided {@code name}
   * @throws IllegalStateException if {@link #parseConfiguration()} hasn't yet been successfully
   *     invoked
   * @throws IllegalArgumentException if {@code keyType} or {@code valueType} don't match the
   *     declared type(s) of the template
   * @throws ClassNotFoundException if a {@link java.lang.Class} declared in the XML couldn't be
   *     found
   * @throws InstantiationException if a user provided {@link java.lang.Class} couldn't get
   *     instantiated
   * @throws IllegalAccessException if a method (including constructor) couldn't be invoked on a
   *     user provided type
   */
  @SuppressWarnings("unchecked")
  public <K, V> CacheConfigurationBuilder<K, V> newCacheConfigurationBuilderFromTemplate(
      final String name, final Class<K> keyType, final Class<V> valueType)
      throws InstantiationException, IllegalAccessException, ClassNotFoundException {

    final ConfigurationParser.CacheTemplate cacheTemplate = templates.get(name);
    if (cacheTemplate == null) {
      return null;
    }
    final ClassLoader defaultClassLoader = ClassLoading.getDefaultClassLoader();
    Class keyClass = getClassForName(cacheTemplate.keyType(), defaultClassLoader);
    Class valueClass = getClassForName(cacheTemplate.valueType(), defaultClassLoader);
    if (keyType != null && cacheTemplate.keyType() != null && !keyClass.isAssignableFrom(keyType)) {
      throw new IllegalArgumentException(
          "CacheTemplate '" + name + "' declares key type of " + cacheTemplate.keyType());
    }
    if (valueType != null
        && cacheTemplate.valueType() != null
        && !valueClass.isAssignableFrom(valueType)) {
      throw new IllegalArgumentException(
          "CacheTemplate '" + name + "' declares value type of " + cacheTemplate.valueType());
    }

    CacheConfigurationBuilder<K, V> builder =
        CacheConfigurationBuilder.newCacheConfigurationBuilder();
    builder =
        builder
            .usingEvictionPrioritizer(
                getInstanceOfName(
                    cacheTemplate.evictionPrioritizer(),
                    defaultClassLoader,
                    EvictionPrioritizer.class,
                    Eviction.Prioritizer.class))
            .evictionVeto(
                getInstanceOfName(
                    cacheTemplate.evictionVeto(), defaultClassLoader, EvictionVeto.class));
    final ConfigurationParser.Expiry parsedExpiry = cacheTemplate.expiry();
    if (parsedExpiry != null) {
      builder = builder.withExpiry(getExpiry(defaultClassLoader, parsedExpiry));
    }

    if (cacheTemplate.keySerializer() != null) {
      final Class<Serializer<?>> keySerializer =
          (Class<Serializer<?>>) getClassForName(cacheTemplate.keySerializer(), defaultClassLoader);
      builder =
          builder.add(
              new DefaultSerializerConfiguration(
                  keySerializer, DefaultSerializerConfiguration.Type.KEY));
    }
    if (cacheTemplate.valueSerializer() != null) {
      final Class<Serializer<?>> valueSerializer =
          (Class<Serializer<?>>)
              getClassForName(cacheTemplate.valueSerializer(), defaultClassLoader);
      builder =
          builder.add(
              new DefaultSerializerConfiguration(
                  valueSerializer, DefaultSerializerConfiguration.Type.VALUE));
    }
    final String loaderWriter = cacheTemplate.loaderWriter();
    if (loaderWriter != null) {
      final Class<CacheLoaderWriter<?, ?>> cacheLoaderWriterClass =
          (Class<CacheLoaderWriter<?, ?>>) getClassForName(loaderWriter, defaultClassLoader);
      builder = builder.add(new DefaultCacheLoaderWriterConfiguration(cacheLoaderWriterClass));
      if (cacheTemplate.writeBehind() != null) {
        WriteBehind writeBehind = cacheTemplate.writeBehind();
        WriteBehindConfigurationBuilder writeBehindConfigurationBuilder =
            WriteBehindConfigurationBuilder.newWriteBehindConfiguration()
                .concurrencyLevel(writeBehind.concurrency())
                .queueSize(writeBehind.maxQueueSize())
                .rateLimit(writeBehind.rateLimitPerSecond())
                .retry(writeBehind.retryAttempts(), writeBehind.retryAttemptsDelay())
                .delay(writeBehind.minWriteDelay(), writeBehind.maxWriteDelay());
        if (writeBehind.isBatched()) {
          writeBehindConfigurationBuilder =
              writeBehindConfigurationBuilder.batchSize(writeBehind.batchSize());
        }
        if (writeBehind.isCoalesced()) {
          writeBehindConfigurationBuilder = writeBehindConfigurationBuilder.enableCoalescing();
        }
        builder = builder.add(writeBehindConfigurationBuilder);
      }
    }
    if (cacheTemplate.listeners() != null) {
      for (ConfigurationParser.Listener listener : cacheTemplate.listeners()) {
        final Class<CacheEventListener<?, ?>> cacheEventListenerClass =
            (Class<CacheEventListener<?, ?>>)
                getClassForName(listener.className(), defaultClassLoader);
        final List<EventType> eventListToFireOn = listener.fireOn();
        Set<org.ehcache.event.EventType> eventSetToFireOn =
            new HashSet<org.ehcache.event.EventType>();
        for (EventType events : eventListToFireOn) {
          switch (events) {
            case CREATED:
              eventSetToFireOn.add(org.ehcache.event.EventType.CREATED);
              break;
            case EVICTED:
              eventSetToFireOn.add(org.ehcache.event.EventType.EVICTED);
              break;
            case EXPIRED:
              eventSetToFireOn.add(org.ehcache.event.EventType.EXPIRED);
              break;
            case UPDATED:
              eventSetToFireOn.add(org.ehcache.event.EventType.UPDATED);
              break;
            case REMOVED:
              eventSetToFireOn.add(org.ehcache.event.EventType.REMOVED);
              break;
            default:
              throw new IllegalArgumentException("Invalid Event Type provided");
          }
        }
        CacheEventListenerConfigurationBuilder listenerBuilder =
            CacheEventListenerConfigurationBuilder.newEventListenerConfiguration(
                    cacheEventListenerClass, eventSetToFireOn)
                .firingMode(EventFiring.valueOf(listener.eventFiring().value()))
                .eventOrdering(EventOrdering.valueOf(listener.eventOrdering().value()));
        builder = builder.add(listenerBuilder);
      }
    }
    ResourcePoolsBuilder resourcePoolsBuilder = newResourcePoolsBuilder();
    for (ResourcePool resourcePool : cacheTemplate.resourcePools()) {
      resourcePoolsBuilder =
          resourcePoolsBuilder.with(
              resourcePool.getType(),
              resourcePool.getSize(),
              resourcePool.getUnit(),
              resourcePool.isPersistent());
    }
    builder = builder.withResourcePools(resourcePoolsBuilder);
    for (ServiceConfiguration<?> serviceConfiguration : cacheTemplate.serviceConfigs()) {
      builder = builder.add(serviceConfiguration);
    }
    if (cacheTemplate.storeByValueOnHeap() != null) {
      final OnHeapStoreServiceConfiguration onHeapStoreServiceConfig =
          new OnHeapStoreServiceConfiguration();
      onHeapStoreServiceConfig.storeByValue(cacheTemplate.storeByValueOnHeap());
      builder = builder.add(onHeapStoreServiceConfig);
    }
    return builder;
  }