/*
 * Druid - a distributed column store.
 * Copyright (C) 2012, 2013  Metamarkets Group Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * 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 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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package io.druid.guice;

import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.inject.Binder;
import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.util.Types;

import java.lang.annotation.Annotation;
import java.util.Properties;


/**
 * Provides a singleton value of type {@code <T>} from {@code Properties} bound to gson.
 * <br/>
 * <h3>Usage</h3>
 * To install this provider, bind it in your guice module, like below.
 *
 * <pre>
 * JsonConfigProvider.bind(binder, "druid.server", DruidServerConfig.class);
 * </pre>
 * <br/>
 * In the above case, {@code druid.server} should be a key found in the {@code Properties} bound elsewhere.
 * The value of that key should directly relate to the fields in {@code DruidServerConfig.class}.
 *
 * <h3>Implementation</h3>
 * <br/>
 * The state of {@code <T>} is defined by the value of the property {@code propertyBase}.
 * This value is a json structure, decoded via {@link JsonConfigurator#configurate(java.util.Properties, String, Class)}.
 * <br/>
 *
 * @param <T> type of config object to provide.
 */
public class JsonConfigProvider<T> implements Provider<Supplier<T>>
{
  @SuppressWarnings("unchecked")
  public static <T> void bind(Binder binder, String propertyBase, Class<T> classToProvide)
  {
    bind(
        binder,
        propertyBase,
        classToProvide,
        Key.get(classToProvide),
        (Key) Key.get(Types.newParameterizedType(Supplier.class, classToProvide))
    );
  }

  @SuppressWarnings("unchecked")
  public static <T> void bind(Binder binder, String propertyBase, Class<T> classToProvide, Annotation annotation)
  {
    bind(
        binder,
        propertyBase,
        classToProvide,
        Key.get(classToProvide, annotation),
        (Key) Key.get(Types.newParameterizedType(Supplier.class, classToProvide), annotation)
    );
  }

  @SuppressWarnings("unchecked")
  public static <T> void bind(
      Binder binder,
      String propertyBase,
      Class<T> classToProvide,
      Class<? extends Annotation> annotation
  )
  {
    bind(
        binder,
        propertyBase,
        classToProvide,
        Key.get(classToProvide, annotation),
        (Key) Key.get(Types.newParameterizedType(Supplier.class, classToProvide), annotation)
    );
  }

  @SuppressWarnings("unchecked")
  public static <T> void bind(
      Binder binder,
      String propertyBase,
      Class<T> clazz,
      Key<T> instanceKey,
      Key<Supplier<T>> supplierKey
  )
  {
    binder.bind(supplierKey).toProvider((Provider) of(propertyBase, clazz)).in(LazySingleton.class);
    binder.bind(instanceKey).toProvider(new SupplierProvider<T>(supplierKey));
  }

  public static <T> JsonConfigProvider<T> of(String propertyBase, Class<T> classToProvide)
  {
    return new JsonConfigProvider<T>(propertyBase, classToProvide);
  }

  private final String propertyBase;
  private final Class<T> classToProvide;

  private Properties props;
  private JsonConfigurator configurator;

  private Supplier<T> retVal = null;

  public JsonConfigProvider(
      String propertyBase,
      Class<T> classToProvide
  )
  {
    this.propertyBase = propertyBase;
    this.classToProvide = classToProvide;
  }

  @Inject
  public void inject(
      Properties props,
      JsonConfigurator configurator
  )
  {
    this.props = props;
    this.configurator = configurator;
  }

  @Override
  public Supplier<T> get()
  {
    if (retVal != null) {
      return retVal;
    }

    try {
      final T config = configurator.configurate(props, propertyBase, classToProvide);
      retVal = Suppliers.ofInstance(config);
    }
    catch (RuntimeException e) {
      // When a runtime exception gets thrown out, this provider will get called again if the object is asked for again.
      // This will have the same failed result, 'cause when it's called no parameters will have actually changed.
      // Guice will then report the same error multiple times, which is pretty annoying. Cache a null supplier and
      // return that instead.  This is technically enforcing a singleton, but such is life.
      retVal = Suppliers.ofInstance(null);
      throw e;
    }
    return retVal;
  }
}

@@ -1248,15 +1248,16 @@
 und 
-to gson
+in guice
 .%0A *
@@ -1917,16 +1917,16 @@
 lass)%7D.%0A
-
  * %3Cbr/%3E
@@ -1929,16 +1929,771 @@
 br/%3E%0A *%0A
+ * An example might be if DruidServerConfig.class were%0A *%0A * %3Cpre%3E%0A *   public class DruidServerConfig%0A *   %7B%0A *     @JsonProperty @NotNull public String hostname = null;%0A *     @JsonProperty @Min(1025) public int port = 8080;%0A *   %7D%0A * %3C/pre%3E%0A *%0A * And your Properties object had in it%0A *%0A * %3Cpre%3E%0A *   druid.server.hostname=0.0.0.0%0A *   druid.server.port=3333%0A * %3C/pre%3E%0A *%0A * Then this would bind a singleton instance of a DruidServerConfig object with hostname = %220.0.0.0%22 and port = 3333.%0A *%0A * If the port weren't set in the properties, then the default of 8080 would be taken.  Essentially, it is the same as%0A * subtracting the %22druid.server%22 prefix from the properties and building a Map which is then passed into%0A * ObjectMapper.convertValue()%0A *%0A
  * @para

Extend javadocs on JsonConfigProvider