Merge pull request #2724 from guobingkun/conditional_multi_bind

Conditional multi bind
This commit is contained in:
Fangjin Yang 2016-03-24 17:09:27 -07:00
commit 980943074b
2 changed files with 721 additions and 0 deletions

View File

@ -0,0 +1,244 @@
/*
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Metamarkets 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 io.druid.guice;
import com.google.common.base.Predicate;
import com.google.inject.Binder;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.Multibinder;
import java.lang.annotation.Annotation;
import java.util.Properties;
/**
* Provides the ability to conditionally bind an item to a set. The condition is based on the value set in the
* runtime.properties.
*
* Usage example:
*
* ConditionalMultibind.create(props, binder, Animal.class)
* .addConditionBinding("animal.type", Predicates.equalTo("cat"), Cat.class)
* .addConditionBinding("animal.type", Predicates.equalTo("dog"), Dog.class);
*
* At binding time, this will check the value set for property "animal.type" in props. If the value is "cat", it will
* add a binding to Cat.class. If the value is "dog", it will add a binding to Dog.class.
*
* At injection time, you will get the items that satisfy their corresponding predicates by calling
* injector.getInstance(Key.get(new TypeLiteral<Set<Animal>>(){}))
*/
public class ConditionalMultibind<T>
{
/**
* Create a ConditionalMultibind that resolves items to be added to the set at "binding" time.
*
* @param properties the runtime properties.
* @param binder the binder for the injector that is being configured.
* @param type the type that will be injected.
* @param <T> interface type.
*
* @return An instance of ConditionalMultibind that can be used to add conditional bindings.
*/
public static <T> ConditionalMultibind<T> create(Properties properties, Binder binder, Class<T> type)
{
return new ConditionalMultibind<T>(properties, Multibinder.<T>newSetBinder(binder, type));
}
/**
* Create a ConditionalMultibind that resolves items to be added to the set at "binding" time.
*
* @param properties the runtime properties.
* @param binder the binder for the injector that is being configured.
* @param type the type that will be injected.
* @param <T> interface type.
* @param annotationType the binding annotation.
*
* @return An instance of ConditionalMultibind that can be used to add conditional bindings.
*/
public static <T> ConditionalMultibind<T> create(
Properties properties,
Binder binder,
Class<T> type,
Class<? extends Annotation> annotationType
)
{
return new ConditionalMultibind<T>(properties, Multibinder.<T>newSetBinder(binder, type, annotationType));
}
/**
* Create a ConditionalMultibind that resolves items to be added to the set at "binding" time.
*
* @param properties the runtime properties.
* @param binder the binder for the injector that is being configured.
* @param type the type that will be injected.
* @param <T> interface type.
*
* @return An instance of ConditionalMultibind that can be used to add conditional bindings.
*/
public static <T> ConditionalMultibind<T> create(Properties properties, Binder binder, TypeLiteral<T> type)
{
return new ConditionalMultibind<T>(properties, Multibinder.<T>newSetBinder(binder, type));
}
/**
* Create a ConditionalMultibind that resolves items to be added to the set at "binding" time.
*
* @param properties the runtime properties.
* @param binder the binder for the injector that is being configured.
* @param type the type that will be injected.
* @param <T> interface type.
* @param annotationType the binding annotation.
*
* @return An instance of ConditionalMultibind that can be used to add conditional bindings.
*/
public static <T> ConditionalMultibind<T> create(
Properties properties,
Binder binder,
TypeLiteral<T> type,
Class<? extends Annotation> annotationType
)
{
return new ConditionalMultibind<T>(properties, Multibinder.<T>newSetBinder(binder, type, annotationType));
}
private final Properties properties;
private final Multibinder<T> multibinder;
public ConditionalMultibind(Properties properties, Multibinder<T> multibinder)
{
this.properties = properties;
this.multibinder = multibinder;
}
/**
* Unconditionally bind target to the set.
*
* @param target the target class to which it adds a binding.
*
* @return self to support a continuous syntax for adding more conditional bindings.
*/
public ConditionalMultibind<T> addBinding(Class<? extends T> target)
{
multibinder.addBinding().to(target);
return this;
}
/**
* Unconditionally bind target to the set.
*
* @param target the target instance to which it adds a binding.
*
* @return self to support a continuous syntax for adding more conditional bindings.
*/
public ConditionalMultibind<T> addBinding(T target)
{
multibinder.addBinding().toInstance(target);
return this;
}
/**
* Unconditionally bind target to the set.
*
* @param target the target type to which it adds a binding.
*
* @return self to support a continuous syntax for adding more conditional bindings.
*/
public ConditionalMultibind<T> addBinding(TypeLiteral<T> target)
{
multibinder.addBinding().to(target);
return this;
}
/**
* Conditionally bind target to the set. If "condition" returns true, add a binding to "target".
*
* @param property the property to inspect on
* @param condition the predicate used to verify whether to add a binding to "target"
* @param target the target class to which it adds a binding.
*
* @return self to support a continuous syntax for adding more conditional bindings.
*/
public ConditionalMultibind<T> addConditionBinding(
String property,
Predicate<String> condition,
Class<? extends T> target
)
{
final String value = properties.getProperty(property);
if (value == null) {
return this;
}
if (condition.apply(value)) {
multibinder.addBinding().to(target);
}
return this;
}
/**
* Conditionally bind target to the set. If "condition" returns true, add a binding to "target".
*
* @param property the property to inspect on
* @param condition the predicate used to verify whether to add a binding to "target"
* @param target the target instance to which it adds a binding.
*
* @return self to support a continuous syntax for adding more conditional bindings.
*/
public ConditionalMultibind<T> addConditionBinding(
String property,
Predicate<String> condition,
T target
)
{
final String value = properties.getProperty(property);
if (value == null) {
return this;
}
if (condition.apply(value)) {
multibinder.addBinding().toInstance(target);
}
return this;
}
/**
* Conditionally bind target to the set. If "condition" returns true, add a binding to "target".
*
* @param property the property to inspect on
* @param condition the predicate used to verify whether to add a binding to "target"
* @param target the target type to which it adds a binding.
*
* @return self to support a continuous syntax for adding more conditional bindings.
*/
public ConditionalMultibind<T> addConditionBinding(
String property,
Predicate<String> condition,
TypeLiteral<T> target
)
{
final String value = properties.getProperty(property);
if (value == null) {
return this;
}
if (condition.apply(value)) {
multibinder.addBinding().to(target);
}
return this;
}
}

View File

@ -0,0 +1,477 @@
/*
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Metamarkets 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 io.druid.guice;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Binder;
import com.google.inject.BindingAnnotation;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
/**
*/
public class ConditionalMultibindTest
{
private static final String ANIMAL_TYPE = "animal.type";
private Properties props;
@Before
public void setUp() throws Exception
{
props = new Properties();
}
@Test
public void testMultiConditionalBind_cat()
{
props.setProperty("animal.type", "cat");
Injector injector = Guice.createInjector(new Module()
{
@Override
public void configure(Binder binder)
{
ConditionalMultibind.create(props, binder, Animal.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("cat"), Cat.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("dog"), Dog.class);
}
});
Set<Animal> animalSet = injector.getInstance(Key.get(new TypeLiteral<Set<Animal>>()
{
}));
Assert.assertEquals(1, animalSet.size());
Assert.assertEquals(animalSet, ImmutableSet.<Animal>of(new Cat()));
}
@Test
public void testMultiConditionalBind_cat_dog()
{
props.setProperty("animal.type", "pets");
Injector injector = Guice.createInjector(new Module()
{
@Override
public void configure(Binder binder)
{
ConditionalMultibind.create(props, binder, Animal.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Cat.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Dog.class);
}
});
Set<Animal> animalSet = injector.getInstance(Key.get(new TypeLiteral<Set<Animal>>()
{
}));
Assert.assertEquals(2, animalSet.size());
Assert.assertEquals(animalSet, ImmutableSet.<Animal>of(new Cat(), new Dog()));
}
@Test
public void testMultiConditionalBind_cat_dog_non_continuous_syntax()
{
props.setProperty("animal.type", "pets");
Injector injector = Guice.createInjector(new Module()
{
@Override
public void configure(Binder binder)
{
ConditionalMultibind.create(props, binder, Animal.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Cat.class);
ConditionalMultibind.create(props, binder, Animal.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Dog.class);
}
});
Set<Animal> animalSet = injector.getInstance(Key.get(new TypeLiteral<Set<Animal>>()
{
}));
Assert.assertEquals(2, animalSet.size());
Assert.assertEquals(animalSet, ImmutableSet.<Animal>of(new Cat(), new Dog()));
}
@Test
public void testMultiConditionalBind_multiple_modules()
{
props.setProperty("animal.type", "pets");
Injector injector = Guice.createInjector(
new Module()
{
@Override
public void configure(Binder binder)
{
ConditionalMultibind.create(props, binder, Animal.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Cat.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Dog.class);
}
},
new Module()
{
@Override
public void configure(Binder binder)
{
ConditionalMultibind.create(props, binder, Animal.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("not_match"), Tiger.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Fish.class);
}
}
);
Set<Animal> animalSet = injector.getInstance(Key.get(new TypeLiteral<Set<Animal>>()
{
}));
Assert.assertEquals(3, animalSet.size());
Assert.assertEquals(animalSet, ImmutableSet.<Animal>of(new Cat(), new Dog(), new Fish()));
}
@Test
public void testMultiConditionalBind_multiple_modules_with_annotation()
{
props.setProperty("animal.type", "pets");
Injector injector = Guice.createInjector(
new Module()
{
@Override
public void configure(Binder binder)
{
ConditionalMultibind.create(props, binder, Animal.class, SanDiego.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Cat.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Dog.class);
}
},
new Module()
{
@Override
public void configure(Binder binder)
{
ConditionalMultibind.create(props, binder, Animal.class, SanDiego.class)
.addBinding(new Bird())
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Tiger.class);
ConditionalMultibind.create(props, binder, Animal.class, SanJose.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Fish.class);
}
}
);
Set<Animal> animalSet_1 = injector.getInstance(Key.get(new TypeLiteral<Set<Animal>>()
{
}, SanDiego.class));
Assert.assertEquals(4, animalSet_1.size());
Assert.assertEquals(animalSet_1, ImmutableSet.<Animal>of(new Bird(), new Cat(), new Dog(), new Tiger()));
Set<Animal> animalSet_2 = injector.getInstance(Key.get(new TypeLiteral<Set<Animal>>()
{
}, SanJose.class));
Assert.assertEquals(1, animalSet_2.size());
Assert.assertEquals(animalSet_2, ImmutableSet.<Animal>of(new Fish()));
}
@Test
public void testMultiConditionalBind_inject()
{
props.setProperty("animal.type", "pets");
Injector injector = Guice.createInjector(
new Module()
{
@Override
public void configure(Binder binder)
{
ConditionalMultibind.create(props, binder, Animal.class)
.addBinding(Bird.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Cat.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Dog.class);
}
},
new Module()
{
@Override
public void configure(Binder binder)
{
ConditionalMultibind.create(props, binder, Animal.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("not_match"), Tiger.class)
.addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Fish.class);
}
}
);
PetShotAvails shop = new PetShotAvails();
injector.injectMembers(shop);
Assert.assertEquals(4, shop.animals.size());
Assert.assertEquals(shop.animals, ImmutableSet.<Animal>of(new Bird(), new Cat(), new Dog(), new Fish()));
}
@Test
public void testMultiConditionalBind_typeLiteral()
{
props.setProperty("animal.type", "pets");
final Set<Animal> set1 = ImmutableSet.<Animal>of(new Dog(), new Tiger());
final Set<Animal> set2 = ImmutableSet.<Animal>of(new Cat(), new Fish());
final Set<Animal> set3 = ImmutableSet.<Animal>of(new Cat());
final Set<Animal> union = new HashSet<>();
union.addAll(set1);
union.addAll(set2);
final Zoo<Animal> zoo1 = new Zoo<>(set1);
final Zoo<Animal> zoo2 = new Zoo<>();
Injector injector = Guice.createInjector(
new Module()
{
@Override
public void configure(Binder binder)
{
ConditionalMultibind.create(props, binder,
new TypeLiteral<Set<Animal>>()
{
}
).addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), set1
).addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), set2);
ConditionalMultibind.create(props, binder,
new TypeLiteral<Zoo<Animal>>()
{
}
).addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), zoo1);
}
},
new Module()
{
@Override
public void configure(Binder binder)
{
ConditionalMultibind.create(props, binder,
new TypeLiteral<Set<Animal>>()
{
}
).addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), set3);
ConditionalMultibind.create(props, binder,
new TypeLiteral<Set<Animal>>()
{
},
SanDiego.class
).addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), union);
ConditionalMultibind.create(props, binder,
new TypeLiteral<Zoo<Animal>>()
{
}
).addBinding(new TypeLiteral<Zoo<Animal>>()
{
});
}
}
);
Set<Set<Animal>> actualAnimalSet = injector.getInstance(Key.get(new TypeLiteral<Set<Set<Animal>>>()
{
}));
Assert.assertEquals(3, actualAnimalSet.size());
Assert.assertEquals(ImmutableSet.of(set1, set2, set3), actualAnimalSet);
actualAnimalSet = injector.getInstance(Key.get(new TypeLiteral<Set<Set<Animal>>>()
{
}, SanDiego.class));
Assert.assertEquals(1, actualAnimalSet.size());
Assert.assertEquals(ImmutableSet.of(union), actualAnimalSet);
final Set<Zoo<Animal>> actualZooSet = injector.getInstance(Key.get(new TypeLiteral<Set<Zoo<Animal>>>()
{
}));
Assert.assertEquals(2, actualZooSet.size());
Assert.assertEquals(ImmutableSet.of(zoo1, zoo2), actualZooSet);
}
static abstract class Animal
{
private final String type;
Animal(String type)
{
this.type = type;
}
@Override
public String toString()
{
return "Animal{" +
"type='" + type + '\'' +
'}';
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Animal animal = (Animal) o;
return type != null ? type.equals(animal.type) : animal.type == null;
}
@Override
public int hashCode()
{
return type != null ? type.hashCode() : 0;
}
}
static class PetShotAvails
{
@Inject
Set<Animal> animals;
}
static class Dog extends Animal
{
Dog()
{
super("dog");
}
}
static class Cat extends Animal
{
Cat()
{
super("cat");
}
}
static class Fish extends Animal
{
Fish()
{
super("fish");
}
}
static class Tiger extends Animal
{
Tiger()
{
super("tiger");
}
}
static class Bird extends Animal
{
Bird()
{
super("bird");
}
}
static class Zoo<T>
{
Set<T> animals;
public Zoo()
{
animals = new HashSet<>();
}
public Zoo(Set<T> animals)
{
this.animals = animals;
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Zoo<?> zoo = (Zoo<?>) o;
return animals != null ? animals.equals(zoo.animals) : zoo.animals == null;
}
@Override
public int hashCode()
{
return animals != null ? animals.hashCode() : 0;
}
@Override
public String toString()
{
return "Zoo{" +
"animals=" + animals +
'}';
}
}
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@BindingAnnotation
@interface SanDiego
{
}
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@BindingAnnotation
@interface SanJose
{
}
}