content: /*
   * 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 com.datastax.oss.driver.internal.core.config.typesafe;
  
  import com.datastax.oss.driver.api.core.CqlSession;
  import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
  import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
  import com.datastax.oss.driver.api.core.config.DriverOption;
  import com.datastax.oss.driver.api.core.config.ProgrammaticDriverConfigLoaderBuilder;
  import com.typesafe.config.Config;
  import com.typesafe.config.ConfigFactory;
  import com.typesafe.config.ConfigValueFactory;
  import edu.umd.cs.findbugs.annotations.NonNull;
  import edu.umd.cs.findbugs.annotations.Nullable;
  import java.time.Duration;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  import java.util.function.Supplier;
  import net.jcip.annotations.NotThreadSafe;
  
  @NotThreadSafe
  public class DefaultProgrammaticDriverConfigLoaderBuilder
      implements ProgrammaticDriverConfigLoaderBuilder {
  
    public static final Supplier<Config> DEFAULT_FALLBACK_SUPPLIER =
        () ->
            ConfigFactory.defaultApplication()
                // Do not remove root path here, it must be done after merging configs
                .withFallback(ConfigFactory.defaultReference(CqlSession.class.getClassLoader()));
  
    private final Map<String, Object> values = new HashMap<>();
  
    private final Supplier<Config> fallbackSupplier;
    private final String rootPath;
  
    private String currentProfileName = DriverExecutionProfile.DEFAULT_NAME;
  
    /**
     * Creates an instance of {@link DefaultProgrammaticDriverConfigLoaderBuilder} with default
     * settings.
     *
     * <p>Fallback configuration for options that haven't been specified programmatically will be
     * obtained from standard classpath resources. Application-specific classpath resources will be
     * located using the {@linkplain Thread#getContextClassLoader() the current thread's context class
     * loader}. This might not be suitable for OSGi deployments, which should use {@link
     * #DefaultProgrammaticDriverConfigLoaderBuilder(ClassLoader)} instead.
     */
    public DefaultProgrammaticDriverConfigLoaderBuilder() {
      this(DEFAULT_FALLBACK_SUPPLIER, DefaultDriverConfigLoader.DEFAULT_ROOT_PATH);
    }
  
    /**
     * Creates an instance of {@link DefaultProgrammaticDriverConfigLoaderBuilder} with default
     * settings but a custom class loader.
     *
     * <p>Fallback configuration for options that haven't been specified programmatically will be
     * obtained from standard classpath resources. Application-specific classpath resources will be
     * located using the provided {@link ClassLoader} instead of {@linkplain
     * Thread#getContextClassLoader() the current thread's context class loader}.
     */
    public DefaultProgrammaticDriverConfigLoaderBuilder(@NonNull ClassLoader appClassLoader) {
      this(
          () ->
              ConfigFactory.defaultApplication(appClassLoader)
                  .withFallback(ConfigFactory.defaultReference(CqlSession.class.getClassLoader())),
          DefaultDriverConfigLoader.DEFAULT_ROOT_PATH);
    }
  
    /**
     * Creates an instance of {@link DefaultProgrammaticDriverConfigLoaderBuilder} using a custom
     * fallback config supplier.
     *
     * @param fallbackSupplier the supplier that will provide fallback configuration for options that
     *     haven't been specified programmatically.
     * @param rootPath the root path used in non-programmatic sources (fallback reference.conf and
     *     system properties). In most cases it should be {@link
     *     DefaultDriverConfigLoader#DEFAULT_ROOT_PATH}. Cannot be null but can be empty.
     */
    public DefaultProgrammaticDriverConfigLoaderBuilder(
        @NonNull Supplier<Config> fallbackSupplier, @NonNull String rootPath) {
      this.fallbackSupplier = fallbackSupplier;
      this.rootPath = rootPath;
    }
  
    private ProgrammaticDriverConfigLoaderBuilder with(
        @NonNull DriverOption option, @Nullable Object value) {
      return with(option.getPath(), value);
    }
  
    private ProgrammaticDriverConfigLoaderBuilder with(@NonNull String path, @Nullable Object value) {
      if (!DriverExecutionProfile.DEFAULT_NAME.equals(currentProfileName)) {
        path = "profiles." + currentProfileName + "." + path;
      }
      if (!rootPath.isEmpty()) {
        path = rootPath + "." + path;
      }
      values.put(path, value);
      return this;
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder startProfile(@NonNull String profileName) {
      currentProfileName = Objects.requireNonNull(profileName);
      return this;
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder endProfile() {
      currentProfileName = DriverExecutionProfile.DEFAULT_NAME;
      return this;
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder withBoolean(
        @NonNull DriverOption option, boolean value) {
      return with(option, value);
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder withBooleanList(
        @NonNull DriverOption option, @NonNull List<Boolean> value) {
      return with(option, value);
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder withInt(@NonNull DriverOption option, int value) {
      return with(option, value);
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder withIntList(
        @NonNull DriverOption option, @NonNull List<Integer> value) {
      return with(option, value);
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder withLong(@NonNull DriverOption option, long value) {
      return with(option, value);
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder withLongList(
        @NonNull DriverOption option, @NonNull List<Long> value) {
      return with(option, value);
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder withDouble(
        @NonNull DriverOption option, double value) {
      return with(option, value);
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder withDoubleList(
        @NonNull DriverOption option, @NonNull List<Double> value) {
      return with(option, value);
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder withString(
        @NonNull DriverOption option, @NonNull String value) {
      return with(option, value);
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder withStringList(
        @NonNull DriverOption option, @NonNull List<String> value) {
      return with(option, value);
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder withStringMap(
        @NonNull DriverOption option, @NonNull Map<String, String> value) {
      for (String key : value.keySet()) {
        this.with(option.getPath() + "." + key, value.get(key));
      }
      return this;
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder withBytes(@NonNull DriverOption option, long value) {
      return with(option, value);
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder withBytesList(
        @NonNull DriverOption option, @NonNull List<Long> value) {
      return with(option, value);
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder withDuration(
        @NonNull DriverOption option, @NonNull Duration value) {
      return with(option, value);
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder withDurationList(
        @NonNull DriverOption option, @NonNull List<Duration> value) {
      return with(option, value);
    }
  
    @NonNull
    @Override
    public ProgrammaticDriverConfigLoaderBuilder without(@NonNull DriverOption option) {
      return with(option, null);
    }
  
    @NonNull
    @Override
    public DriverConfigLoader build() {
      return new DefaultDriverConfigLoader(
          () -> {
            ConfigFactory.invalidateCaches();
            Config programmaticConfig = buildConfig();
            Config config =
                ConfigFactory.defaultOverrides()
                    .withFallback(programmaticConfig)
                    .withFallback(fallbackSupplier.get())
                    .resolve();
            // Only remove rootPath after the merge between system properties
            // and fallback configuration, since both are supposed to
            // contain the same rootPath prefix.
            return rootPath.isEmpty() ? config : config.getConfig(rootPath);
          });
    }
  
    private Config buildConfig() {
      Config config = ConfigFactory.empty();
      for (Map.Entry<String, Object> entry : values.entrySet()) {
        config = config.withValue(entry.getKey(), ConfigValueFactory.fromAnyRef(entry.getValue()));
      }
      return config;
    }
  }
  