JCLOUDS-107: Use Guice multibinding extensions

This commit is contained in:
Ignasi Barrera 2013-06-04 18:36:36 +02:00
parent ea262c3995
commit 2701655f91
10 changed files with 10 additions and 1073 deletions

View File

@ -73,6 +73,12 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
<!-- for ohai -->
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-multibindings</artifactId>
<version>3.0</version>
</dependency>
<!-- for transient chef provider -->
<dependency>
<groupId>org.apache.jclouds</groupId>

View File

@ -25,13 +25,13 @@ import java.util.regex.Pattern;
import org.jclouds.domain.JsonBall;
import org.jclouds.ohai.Automatic;
import org.jclouds.ohai.config.multibindings.MapBinder;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
import com.google.inject.Binder;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.MapBinder;
/**
*

View File

@ -22,11 +22,11 @@ import java.lang.management.RuntimeMXBean;
import javax.inject.Singleton;
import org.jclouds.domain.JsonBall;
import org.jclouds.ohai.config.multibindings.MapBinder;
import org.jclouds.ohai.suppliers.UptimeSecondsSupplier;
import com.google.common.base.Supplier;
import com.google.inject.Provides;
import com.google.inject.multibindings.MapBinder;
/**
* Wires the components needed to parse ohai data from a JVM

View File

@ -32,7 +32,6 @@ import org.jclouds.domain.JsonBall;
import org.jclouds.json.Json;
import org.jclouds.ohai.Automatic;
import org.jclouds.ohai.AutomaticSupplier;
import org.jclouds.ohai.config.multibindings.MapBinder;
import org.jclouds.ohai.functions.ByteArrayToMacAddress;
import org.jclouds.ohai.functions.MapSetToMultimap;
@ -42,6 +41,7 @@ import com.google.common.collect.Multimap;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.MapBinder;
/**
* Wires the components needed to parse ohai data

View File

@ -1,37 +0,0 @@
/*
* 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 org.jclouds.ohai.config.multibindings;
import com.google.inject.BindingAnnotation;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* An internal binding annotation applied to each element in a multibinding. All
* elements are assigned a globally-unique id to allow different modules to
* contribute multibindings independently.
*
* @author jessewilson@google.com (Jesse Wilson)
*/
@Retention(RUNTIME)
@BindingAnnotation
@interface Element {
String setName();
int uniqueId();
}

View File

@ -1,539 +0,0 @@
/*
* 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 org.jclouds.ohai.config.multibindings;
import static com.google.inject.util.Types.newParameterizedTypeWithOwner;
import static org.jclouds.ohai.config.multibindings.Multibinder.checkConfiguration;
import static org.jclouds.ohai.config.multibindings.Multibinder.checkNotNull;
import static org.jclouds.ohai.config.multibindings.Multibinder.setOf;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.jclouds.ohai.config.multibindings.Multibinder.RealMultibinder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Binder;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.ProviderWithDependencies;
import com.google.inject.util.Types;
/**
* An API to bind multiple map entries separately, only to later inject them as
* a complete map. MapBinder is intended for use in your application's module:
*
* <pre>
* <code>
* public class SnacksModule extends AbstractModule {
* protected void configure() {
* MapBinder&lt;String, Snack&gt; mapbinder
* = MapBinder.newMapBinder(binder(), String.class, Snack.class);
* mapbinder.addBinding("twix").toInstance(new Twix());
* mapbinder.addBinding("snickers").toProvider(SnickersProvider.class);
* mapbinder.addBinding("skittles").to(Skittles.class);
* }
* }</code>
* </pre>
*
* <p>
* With this binding, a {@link Map}{@code <String, Snack>} can now be injected:
*
* <pre>
* <code>
* class SnackMachine {
* {@literal @}Inject
* public SnackMachine(Map&lt;String, Snack&gt; snacks) { ... }
* }</code>
* </pre>
*
* <p>
* In addition to binding {@code Map<K, V>}, a mapbinder will also bind
* {@code Map<K, Provider<V>>} for lazy value provision:
*
* <pre>
* <code>
* class SnackMachine {
* {@literal @}Inject
* public SnackMachine(Map&lt;String, Provider&lt;Snack&gt;&gt; snackSuppliers) { ... }
* }</code>
* </pre>
*
* <p>
* Contributing mapbindings from different modules is supported. For example, it
* is okay to have both {@code CandyModule} and {@code ChipsModule} both create
* their own {@code MapBinder<String, Snack>}, and to each contribute bindings
* to the snacks map. When that map is injected, it will contain entries from
* both modules.
*
* <p>
* The map's iteration order is consistent with the binding order. This is
* convenient when multiple elements are contributed by the same module because
* that module can order its bindings appropriately. Avoid relying on the
* iteration order of elements contributed by different modules, since there is
* no equivalent mechanism to order modules.
*
* <p>
* Values are resolved at map injection time. If a value is bound to a provider,
* that provider's get method will be called each time the map is injected
* (unless the binding is also scoped, or a map of providers is injected).
*
* <p>
* Annotations are used to create different maps of the same key/value type.
* Each distinct annotation gets its own independent map.
*
* <p>
* <strong>Keys must be distinct.</strong> If the same key is bound more than
* once, map injection will fail. However, use {@link #permitDuplicates()} in
* order to allow duplicate keys; extra bindings to {@code Map<K, Set<V>>} and
* {@code Map<K, Set<Provider<V>>} will be added.
*
* <p>
* <strong>Keys must be non-null.</strong> {@code addBinding(null)} will throw
* an unchecked exception.
*
* <p>
* <strong>Values must be non-null to use map injection.</strong> If any value
* is null, map injection will fail (although injecting a map of providers will
* not).
*
* @author dpb@google.com (David P. Baker)
*/
public abstract class MapBinder<K, V> {
private MapBinder() {
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/
* {@code valueType} in a {@link Map} that is itself bound with no binding
* annotation.
*/
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
return newMapBinder(binder, valueType, Key.get(mapOf(keyType, valueType)),
Key.get(mapOfProviderOf(keyType, valueType)), Key.get(mapOf(keyType, setOf(valueType))),
Key.get(mapOfSetOfProviderOf(keyType, valueType)),
Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType)));
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/
* {@code valueType} in a {@link Map} that is itself bound with no binding
* annotation.
*/
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder, Class<K> keyType, Class<V> valueType) {
return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType));
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/
* {@code valueType} in a {@link Map} that is itself bound with
* {@code annotation}.
*/
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType,
Annotation annotation) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
return newMapBinder(binder, valueType, Key.get(mapOf(keyType, valueType), annotation),
Key.get(mapOfProviderOf(keyType, valueType), annotation),
Key.get(mapOf(keyType, setOf(valueType)), annotation),
Key.get(mapOfSetOfProviderOf(keyType, valueType), annotation),
Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType), annotation));
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/
* {@code valueType} in a {@link Map} that is itself bound with
* {@code annotation}.
*/
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder, Class<K> keyType, Class<V> valueType,
Annotation annotation) {
return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotation);
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/
* {@code valueType} in a {@link Map} that is itself bound with
* {@code annotationType}.
*/
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType,
Class<? extends Annotation> annotationType) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
return newMapBinder(binder, valueType, Key.get(mapOf(keyType, valueType), annotationType),
Key.get(mapOfProviderOf(keyType, valueType), annotationType),
Key.get(mapOf(keyType, setOf(valueType)), annotationType),
Key.get(mapOfSetOfProviderOf(keyType, valueType), annotationType),
Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType), annotationType));
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/
* {@code valueType} in a {@link Map} that is itself bound with
* {@code annotationType}.
*/
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder, Class<K> keyType, Class<V> valueType,
Class<? extends Annotation> annotationType) {
return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotationType);
}
@SuppressWarnings("unchecked")
// a map of <K, V> is safely a Map<K, V>
private static <K, V> TypeLiteral<Map<K, V>> mapOf(TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
return (TypeLiteral<Map<K, V>>) TypeLiteral.get(Types.mapOf(keyType.getType(), valueType.getType()));
}
@SuppressWarnings("unchecked")
// a provider map <K, V> is safely a Map<K, Provider<V>>
private static <K, V> TypeLiteral<Map<K, Provider<V>>> mapOfProviderOf(TypeLiteral<K> keyType,
TypeLiteral<V> valueType) {
return (TypeLiteral<Map<K, Provider<V>>>) TypeLiteral.get(Types.mapOf(keyType.getType(),
Types.providerOf(valueType.getType())));
}
@SuppressWarnings("unchecked")
// a provider map <K, Set<V>> is safely a Map<K, Set<Provider<V>>>
private static <K, V> TypeLiteral<Map<K, Set<Provider<V>>>> mapOfSetOfProviderOf(TypeLiteral<K> keyType,
TypeLiteral<V> valueType) {
return (TypeLiteral<Map<K, Set<Provider<V>>>>) TypeLiteral.get(Types.mapOf(keyType.getType(),
Types.setOf(Types.providerOf(valueType.getType()))));
}
@SuppressWarnings("unchecked")
// a provider entry <K, V> is safely a Map.Entry<K, Provider<V>>
private static <K, V> TypeLiteral<Map.Entry<K, Provider<V>>> entryOfProviderOf(TypeLiteral<K> keyType,
TypeLiteral<V> valueType) {
return (TypeLiteral<Entry<K, Provider<V>>>) TypeLiteral.get(newParameterizedTypeWithOwner(Map.class, Entry.class,
keyType.getType(), Types.providerOf(valueType.getType())));
}
private static <K, V> MapBinder<K, V> newMapBinder(Binder binder, TypeLiteral<V> valueType, Key<Map<K, V>> mapKey,
Key<Map<K, Provider<V>>> providerMapKey, Key<Map<K, Set<V>>> multimapKey,
Key<Map<K, Set<Provider<V>>>> providerMultimapKey, Multibinder<Entry<K, Provider<V>>> entrySetBinder) {
RealMapBinder<K, V> mapBinder = new RealMapBinder<K, V>(binder, valueType, mapKey, providerMapKey, multimapKey,
providerMultimapKey, entrySetBinder);
binder.install(mapBinder);
return mapBinder;
}
/**
* Configures the {@code MapBinder} to handle duplicate entries.
* <p>
* When multiple equal keys are bound, the value that gets included in the
* map is arbitrary.
* <p>
* In addition to the {@code Map<K, V>} and {@code Map<K, Provider<V>>} maps
* that are normally bound, a {@code Map<K, Set<V>>} and {@code Map<K,
* Set<Provider<V>>>} are <em>also</em> bound, which contain all values bound
* to each key.
* <p>
* When multiple modules contribute elements to the map, this configuration
* option impacts all of them.
*
* @return this map binder
*/
public abstract MapBinder<K, V> permitDuplicates();
/**
* Returns a binding builder used to add a new entry in the map. Each key
* must be distinct (and non-null). Bound providers will be evaluated each
* time the map is injected.
*
* <p>
* It is an error to call this method without also calling one of the
* {@code to} methods on the returned binding builder.
*
* <p>
* Scoping elements independently is supported. Use the {@code in} method to
* specify a binding scope.
*/
public abstract LinkedBindingBuilder<V> addBinding(K key);
/**
* The actual mapbinder plays several roles:
*
* <p>
* As a MapBinder, it acts as a factory for LinkedBindingBuilders for each of
* the map's values. It delegates to a {@link Multibinder} of entries (keys
* to value providers).
*
* <p>
* As a Module, it installs the binding to the map itself, as well as to a
* corresponding map whose values are providers. It uses the entry set
* multibinder to construct the map and the provider map.
*
* <p>
* As a module, this implements equals() and hashcode() in order to trick
* Guice into executing its configure() method only once. That makes it so
* that multiple mapbinders can be created for the same target map, but only
* one is bound. Since the list of bindings is retrieved from the injector
* itself (and not the mapbinder), each mapbinder has access to all
* contributions from all equivalent mapbinders.
*
* <p>
* Rather than binding a single Map.Entry&lt;K, V&gt;, the map binder binds
* keys and values independently. This allows the values to be properly
* scoped.
*
* <p>
* We use a subclass to hide 'implements Module' from the public API.
*/
private static final class RealMapBinder<K, V> extends MapBinder<K, V> implements Module {
private final TypeLiteral<V> valueType;
private final Key<Map<K, V>> mapKey;
private final Key<Map<K, Provider<V>>> providerMapKey;
private final Key<Map<K, Set<V>>> multimapKey;
private final Key<Map<K, Set<Provider<V>>>> providerMultimapKey;
private final RealMultibinder<Map.Entry<K, Provider<V>>> entrySetBinder;
/*
* the target injector's binder. non-null until initialization, null
* afterwards
*/
private Binder binder;
private RealMapBinder(Binder binder, TypeLiteral<V> valueType, Key<Map<K, V>> mapKey,
Key<Map<K, Provider<V>>> providerMapKey, Key<Map<K, Set<V>>> multimapKey,
Key<Map<K, Set<Provider<V>>>> providerMultimapKey, Multibinder<Map.Entry<K, Provider<V>>> entrySetBinder) {
this.valueType = valueType;
this.mapKey = mapKey;
this.providerMapKey = providerMapKey;
this.multimapKey = multimapKey;
this.providerMultimapKey = providerMultimapKey;
this.entrySetBinder = (RealMultibinder<Entry<K, Provider<V>>>) entrySetBinder;
this.binder = binder;
}
@Override
public MapBinder<K, V> permitDuplicates() {
entrySetBinder.permitDuplicates();
binder.install(new MultimapBinder<K, V>(multimapKey, providerMultimapKey, entrySetBinder.getSetKey()));
return this;
}
/**
* This creates two bindings. One for the {@code Map.Entry<K,
* Provider<V>>} and another for {@code V}.
*/
@Override
public LinkedBindingBuilder<V> addBinding(K key) {
checkNotNull(key, "key");
checkConfiguration(!isInitialized(), "MapBinder was already initialized");
Key<V> valueKey = Key.get(valueType, new RealElement(entrySetBinder.getSetName()));
entrySetBinder.addBinding().toInstance(new MapEntry<K, Provider<V>>(key, binder.getProvider(valueKey)));
return binder.bind(valueKey);
}
public void configure(Binder binder) {
checkConfiguration(!isInitialized(), "MapBinder was already initialized");
final ImmutableSet<Dependency<?>> dependencies = ImmutableSet.<Dependency<?>> of(Dependency.get(entrySetBinder
.getSetKey()));
// Binds a Map<K, Provider<V>> from a collection of Map<Entry<K,
// Provider<V>>.
final Provider<Set<Entry<K, Provider<V>>>> entrySetProvider = binder.getProvider(entrySetBinder.getSetKey());
binder.bind(providerMapKey).toProvider(new ProviderWithDependencies<Map<K, Provider<V>>>() {
private Map<K, Provider<V>> providerMap;
@SuppressWarnings("unused")
@Inject
void initialize(Injector injector) {
RealMapBinder.this.binder = null;
boolean permitDuplicates = entrySetBinder.permitsDuplicates(injector);
Map<K, Provider<V>> providerMapMutable = new LinkedHashMap<K, Provider<V>>();
for (Entry<K, Provider<V>> entry : entrySetProvider.get()) {
Provider<V> previous = providerMapMutable.put(entry.getKey(), entry.getValue());
checkConfiguration(previous == null || permitDuplicates,
"Map injection failed due to duplicated key \"%s\"", entry.getKey());
}
providerMap = ImmutableMap.copyOf(providerMapMutable);
}
public Map<K, Provider<V>> get() {
return providerMap;
}
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
});
final Provider<Map<K, Provider<V>>> mapProvider = binder.getProvider(providerMapKey);
binder.bind(mapKey).toProvider(new ProviderWithDependencies<Map<K, V>>() {
public Map<K, V> get() {
Map<K, V> map = new LinkedHashMap<K, V>();
for (Entry<K, Provider<V>> entry : mapProvider.get().entrySet()) {
V value = entry.getValue().get();
K key = entry.getKey();
checkConfiguration(value != null, "Map injection failed due to null value for key \"%s\"", key);
map.put(key, value);
}
return Collections.unmodifiableMap(map);
}
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
});
}
private boolean isInitialized() {
return binder == null;
}
@Override
public boolean equals(Object o) {
return o instanceof RealMapBinder<?, ?> && ((RealMapBinder<?, ?>) o).mapKey.equals(mapKey);
}
@Override
public int hashCode() {
return mapKey.hashCode();
}
/**
* Binds {@code Map<K, Set<V>>} and {{@code Map<K, Set<Provider<V>>>}.
*/
private static final class MultimapBinder<K, V> implements Module {
private final Key<Map<K, Set<V>>> multimapKey;
private final Key<Map<K, Set<Provider<V>>>> providerMultimapKey;
private final Key<Set<Entry<K, Provider<V>>>> entrySetKey;
public MultimapBinder(Key<Map<K, Set<V>>> multimapKey, Key<Map<K, Set<Provider<V>>>> providerMultimapKey,
Key<Set<Entry<K, Provider<V>>>> entrySetKey) {
this.multimapKey = multimapKey;
this.providerMultimapKey = providerMultimapKey;
this.entrySetKey = entrySetKey;
}
public void configure(Binder binder) {
final ImmutableSet<Dependency<?>> dependencies = ImmutableSet.<Dependency<?>> of(Dependency
.get(entrySetKey));
final Provider<Set<Entry<K, Provider<V>>>> entrySetProvider = binder.getProvider(entrySetKey);
// Binds a Map<K, Set<Provider<V>>> from a collection of
// Map<Entry<K, Provider<V>> if
// permitDuplicates was called.
binder.bind(providerMultimapKey).toProvider(new ProviderWithDependencies<Map<K, Set<Provider<V>>>>() {
private Map<K, Set<Provider<V>>> providerMultimap;
@SuppressWarnings("unused")
@Inject
void initialize(Injector injector) {
Map<K, ImmutableSet.Builder<Provider<V>>> providerMultimapMutable = new LinkedHashMap<K, ImmutableSet.Builder<Provider<V>>>();
for (Entry<K, Provider<V>> entry : entrySetProvider.get()) {
if (!providerMultimapMutable.containsKey(entry.getKey())) {
providerMultimapMutable.put(entry.getKey(), ImmutableSet.<Provider<V>> builder());
}
providerMultimapMutable.get(entry.getKey()).add(entry.getValue());
}
ImmutableMap.Builder<K, Set<Provider<V>>> providerMultimapBuilder = ImmutableMap.builder();
for (Entry<K, ImmutableSet.Builder<Provider<V>>> entry : providerMultimapMutable.entrySet()) {
providerMultimapBuilder.put(entry.getKey(), entry.getValue().build());
}
providerMultimap = providerMultimapBuilder.build();
}
public Map<K, Set<Provider<V>>> get() {
return providerMultimap;
}
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
});
final Provider<Map<K, Set<Provider<V>>>> multimapProvider = binder.getProvider(providerMultimapKey);
binder.bind(multimapKey).toProvider(new ProviderWithDependencies<Map<K, Set<V>>>() {
public Map<K, Set<V>> get() {
ImmutableMap.Builder<K, Set<V>> multimapBuilder = ImmutableMap.builder();
for (Entry<K, Set<Provider<V>>> entry : multimapProvider.get().entrySet()) {
K key = entry.getKey();
ImmutableSet.Builder<V> valuesBuilder = ImmutableSet.builder();
for (Provider<V> valueProvider : entry.getValue()) {
V value = valueProvider.get();
checkConfiguration(value != null, "Multimap injection failed due to null value for key \"%s\"",
key);
valuesBuilder.add(value);
}
multimapBuilder.put(key, valuesBuilder.build());
}
return multimapBuilder.build();
}
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
});
}
}
private static final class MapEntry<K, V> implements Map.Entry<K, V> {
private final K key;
private final V value;
private MapEntry(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
return obj instanceof Map.Entry<?, ?> && key.equals(((Map.Entry<?, ?>) obj).getKey())
&& value.equals(((Map.Entry<?, ?>) obj).getValue());
}
@Override
public int hashCode() {
return 127 * ("key".hashCode() ^ key.hashCode()) + 127 * ("value".hashCode() ^ value.hashCode());
}
@Override
public String toString() {
return "MapEntry(" + key + ", " + value + ")";
}
}
}
}

View File

@ -1,409 +0,0 @@
/*
* 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 org.jclouds.ohai.config.multibindings;
import static com.google.inject.name.Names.named;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.ConfigurationException;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.HasDependencies;
import com.google.inject.spi.Message;
import com.google.inject.spi.Toolable;
import com.google.inject.util.Types;
/**
*
* An API to bind multiple values separately, only to later inject them as a
* complete collection. Multibinder is intended for use in your application's
* module:
*
* <pre>
* <code>
* public class SnacksModule extends AbstractModule {
* protected void configure() {
* Multibinder&lt;Snack&gt; multibinder
* = Multibinder.newSetBinder(binder(), Snack.class);
* multibinder.addBinding().toInstance(new Twix());
* multibinder.addBinding().toProvider(SnickersProvider.class);
* multibinder.addBinding().to(Skittles.class);
* }
* }</code>
* </pre>
*
* <p>
* With this binding, a {@link Set}{@code <Snack>} can now be injected:
*
* <pre>
* <code>
* class SnackMachine {
* {@literal @}Inject
* public SnackMachine(Set&lt;Snack&gt; snacks) { ... }
* }</code>
* </pre>
*
* <p>
* Contributing multibindings from different modules is supported. For example,
* it is okay to have both {@code CandyModule} and {@code ChipsModule} to both
* create their own {@code Multibinder<Snack>}, and to each contribute bindings
* to the set of snacks. When that set is injected, it will contain elements
* from both modules.
*
* <p>
* The set's iteration order is consistent with the binding order. This is
* convenient when multiple elements are contributed by the same module because
* that module can order its bindings appropriately. Avoid relying on the
* iteration order of elements contributed by different modules, since there is
* no equivalent mechanism to order modules.
*
* <p>
* Elements are resolved at set injection time. If an element is bound to a
* provider, that provider's get method will be called each time the set is
* injected (unless the binding is also scoped).
*
* <p>
* Annotations are be used to create different sets of the same element type.
* Each distinct annotation gets its own independent collection of elements.
*
* <p>
* <strong>Elements must be distinct.</strong> If multiple bound elements have
* the same value, set injection will fail.
*
* <p>
* <strong>Elements must be non-null.</strong> If any set element is null, set
* injection will fail.
*
* @author jessewilson@google.com (Jesse Wilson)
*/
public abstract class Multibinder<T> {
private Multibinder() {
}
/**
* Returns a new multibinder that collects instances of {@code type} in a
* {@link Set} that is itself bound with no binding annotation.
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, TypeLiteral<T> type) {
binder = binder.skipSources(RealMultibinder.class, Multibinder.class);
RealMultibinder<T> result = new RealMultibinder<T>(binder, type, "", Key.get(Multibinder.<T> setOf(type)));
binder.install(result);
return result;
}
/**
* Returns a new multibinder that collects instances of {@code type} in a
* {@link Set} that is itself bound with no binding annotation.
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, Class<T> type) {
return newSetBinder(binder, TypeLiteral.get(type));
}
/**
* Returns a new multibinder that collects instances of {@code type} in a
* {@link Set} that is itself bound with {@code annotation}.
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, TypeLiteral<T> type, Annotation annotation) {
binder = binder.skipSources(RealMultibinder.class, Multibinder.class);
RealMultibinder<T> result = new RealMultibinder<T>(binder, type, annotation.toString(), Key.get(
Multibinder.<T> setOf(type), annotation));
binder.install(result);
return result;
}
/**
* Returns a new multibinder that collects instances of {@code type} in a
* {@link Set} that is itself bound with {@code annotation}.
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, Class<T> type, Annotation annotation) {
return newSetBinder(binder, TypeLiteral.get(type), annotation);
}
/**
* Returns a new multibinder that collects instances of {@code type} in a
* {@link Set} that is itself bound with {@code annotationType}.
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, TypeLiteral<T> type,
Class<? extends Annotation> annotationType) {
binder = binder.skipSources(RealMultibinder.class, Multibinder.class);
RealMultibinder<T> result = new RealMultibinder<T>(binder, type, "@" + annotationType.getName(), Key.get(
Multibinder.<T> setOf(type), annotationType));
binder.install(result);
return result;
}
/**
* Returns a new multibinder that collects instances of {@code type} in a
* {@link Set} that is itself bound with {@code annotationType}.
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, Class<T> type,
Class<? extends Annotation> annotationType) {
return newSetBinder(binder, TypeLiteral.get(type), annotationType);
}
@SuppressWarnings("unchecked")
// wrapping a T in a Set safely returns a Set<T>
static <T> TypeLiteral<Set<T>> setOf(TypeLiteral<T> elementType) {
Type type = Types.setOf(elementType.getType());
return (TypeLiteral<Set<T>>) TypeLiteral.get(type);
}
/**
* Configures the bound set to silently discard duplicate elements. When
* multiple equal values are bound, the one that gets included is arbitrary.
* When multiple modules contribute elements to the set, this configuration
* option impacts all of them.
*
* @return this multibinder
*/
public abstract Multibinder<T> permitDuplicates();
/**
* Returns a binding builder used to add a new element in the set. Each bound
* element must have a distinct value. Bound providers will be evaluated each
* time the set is injected.
*
* <p>
* It is an error to call this method without also calling one of the
* {@code to} methods on the returned binding builder.
*
* <p>
* Scoping elements independently is supported. Use the {@code in} method to
* specify a binding scope.
*/
public abstract LinkedBindingBuilder<T> addBinding();
/**
* The actual multibinder plays several roles:
*
* <p>
* As a Multibinder, it acts as a factory for LinkedBindingBuilders for each
* of the set's elements. Each binding is given an annotation that identifies
* it as a part of this set.
*
* <p>
* As a Module, it installs the binding to the set itself. As a module, this
* implements equals() and hashcode() in order to trick Guice into executing
* its configure() method only once. That makes it so that multiple
* multibinders can be created for the same target collection, but only one
* is bound. Since the list of bindings is retrieved from the injector itself
* (and not the multibinder), each multibinder has access to all
* contributions from all multibinders.
*
* <p>
* As a Provider, this constructs the set instances.
*
* <p>
* We use a subclass to hide 'implements Module, Provider' from the public
* API.
*/
static final class RealMultibinder<T> extends Multibinder<T> implements Module, Provider<Set<T>>, HasDependencies {
private final TypeLiteral<T> elementType;
private final String setName;
private final Key<Set<T>> setKey;
private final Key<Boolean> permitDuplicatesKey;
/*
* the target injector's binder. non-null until initialization, null
* afterwards
*/
private Binder binder;
/*
* a provider for each element in the set. null until initialization,
* non-null afterwards
*/
private List<Provider<T>> providers;
private Set<Dependency<?>> dependencies;
/**
* whether duplicates are allowed. Possibly configured by a different
* instance
*/
private boolean permitDuplicates;
private RealMultibinder(Binder binder, TypeLiteral<T> elementType, String setName, Key<Set<T>> setKey) {
this.binder = checkNotNull(binder, "binder");
this.elementType = checkNotNull(elementType, "elementType");
this.setName = checkNotNull(setName, "setName");
this.setKey = checkNotNull(setKey, "setKey");
this.permitDuplicatesKey = Key.get(Boolean.class, named(toString() + " permits duplicates"));
}
public void configure(Binder binder) {
checkConfiguration(!isInitialized(), "Multibinder was already initialized");
binder.bind(setKey).toProvider(this);
}
@Override
public Multibinder<T> permitDuplicates() {
binder.install(new PermitDuplicatesModule(permitDuplicatesKey));
return this;
}
@Override
public LinkedBindingBuilder<T> addBinding() {
checkConfiguration(!isInitialized(), "Multibinder was already initialized");
return binder.bind(Key.get(elementType, new RealElement(setName)));
}
/**
* Invoked by Guice at Injector-creation time to prepare providers for
* each element in this set. At this time the set's size is known, but its
* contents are only evaluated when get() is invoked.
*/
@Toolable
@Inject
void initialize(Injector injector) {
providers = Lists.newArrayList();
List<Dependency<?>> dependencies = Lists.newArrayList();
for (Binding<?> entry : injector.findBindingsByType(elementType)) {
if (keyMatches(entry.getKey())) {
@SuppressWarnings("unchecked")
// protected by findBindingsByType()
Binding<T> binding = (Binding<T>) entry;
providers.add(binding.getProvider());
dependencies.add(Dependency.get(binding.getKey()));
}
}
this.dependencies = ImmutableSet.copyOf(dependencies);
this.permitDuplicates = permitsDuplicates(injector);
this.binder = null;
}
boolean permitsDuplicates(Injector injector) {
return injector.getBindings().containsKey(permitDuplicatesKey);
}
private boolean keyMatches(Key<?> key) {
return key.getTypeLiteral().equals(elementType) && key.getAnnotation() instanceof Element
&& ((Element) key.getAnnotation()).setName().equals(setName);
}
private boolean isInitialized() {
return binder == null;
}
public Set<T> get() {
checkConfiguration(isInitialized(), "Multibinder is not initialized");
Set<T> result = new LinkedHashSet<T>();
for (Provider<T> provider : providers) {
final T newValue = provider.get();
checkConfiguration(newValue != null, "Set injection failed due to null element");
checkConfiguration(result.add(newValue) || permitDuplicates,
"Set injection failed due to duplicated element \"%s\"", newValue);
}
return Collections.unmodifiableSet(result);
}
String getSetName() {
return setName;
}
Key<Set<T>> getSetKey() {
return setKey;
}
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
@Override
public boolean equals(Object o) {
return o instanceof RealMultibinder<?> && ((RealMultibinder<?>) o).setKey.equals(setKey);
}
@Override
public int hashCode() {
return setKey.hashCode();
}
@Override
public String toString() {
return new StringBuilder().append(setName).append(setName.length() > 0 ? " " : "").append("Multibinder<")
.append(elementType).append(">").toString();
}
}
/**
* We install the permit duplicates configuration as its own binding, all by
* itself. This way, if only one of a multibinder's users remember to call
* permitDuplicates(), they're still permitted.
*/
private static class PermitDuplicatesModule extends AbstractModule {
private final Key<Boolean> key;
PermitDuplicatesModule(Key<Boolean> key) {
this.key = key;
}
@Override
protected void configure() {
bind(key).toInstance(true);
}
@Override
public boolean equals(Object o) {
return o instanceof PermitDuplicatesModule && ((PermitDuplicatesModule) o).key.equals(key);
}
@Override
public int hashCode() {
return getClass().hashCode() ^ key.hashCode();
}
}
static void checkConfiguration(boolean condition, String format, Object... args) {
if (condition) {
return;
}
throw new ConfigurationException(ImmutableSet.of(new Message(String.format(format, args))));
}
static <T> T checkNotNull(T reference, String name) {
if (reference != null) {
return reference;
}
NullPointerException npe = new NullPointerException(name);
throw new ConfigurationException(ImmutableSet.of(new Message(ImmutableList.of(), npe.toString(), npe)));
}
}

View File

@ -1,63 +0,0 @@
/*
* 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 org.jclouds.ohai.config.multibindings;
import java.lang.annotation.Annotation;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author jessewilson@google.com (Jesse Wilson)
*/
class RealElement implements Element {
private static final AtomicInteger nextUniqueId = new AtomicInteger(1);
private final int uniqueId;
private final String setName;
RealElement(String setName) {
uniqueId = nextUniqueId.getAndIncrement();
this.setName = setName;
}
public String setName() {
return setName;
}
public int uniqueId() {
return uniqueId;
}
public Class<? extends Annotation> annotationType() {
return Element.class;
}
@Override
public String toString() {
return "@" + Element.class.getName() + "(setName=" + setName + ",uniqueId=" + uniqueId + ")";
}
@Override
public boolean equals(Object o) {
return o instanceof Element && ((Element) o).setName().equals(setName())
&& ((Element) o).uniqueId() == uniqueId();
}
@Override
public int hashCode() {
return 127 * ("setName".hashCode() ^ setName.hashCode()) + 127 * ("uniqueId".hashCode() ^ uniqueId);
}
}

View File

@ -1,21 +0,0 @@
/*
* 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.
*/
/**
* Taken from r1154 of Guice, as they decided to stop supporting multiple bindings and instead silently throw them away.
*/
package org.jclouds.ohai.config.multibindings;

View File

@ -31,7 +31,6 @@ import org.jclouds.domain.JsonBall;
import org.jclouds.json.Json;
import org.jclouds.json.config.GsonModule;
import org.jclouds.ohai.Automatic;
import org.jclouds.ohai.config.multibindings.MapBinder;
import org.jclouds.rest.annotations.ApiVersion;
import org.testng.annotations.Test;
@ -41,6 +40,7 @@ import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.MapBinder;
import com.google.inject.util.Providers;
/**