1 /* 2 * Copyright (C) 2008 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.inject.multibindings; 18 19 import static com.google.inject.internal.RealMapBinder.newMapRealBinder; 20 import static com.google.inject.internal.RealMapBinder.newRealMapBinder; 21 22 import com.google.inject.Binder; 23 import com.google.inject.TypeLiteral; 24 import com.google.inject.binder.LinkedBindingBuilder; 25 import com.google.inject.internal.RealMapBinder; 26 import java.lang.annotation.Annotation; 27 import java.util.Map; 28 29 /** 30 * An API to bind multiple map entries separately, only to later inject them as a complete map. 31 * MapBinder is intended for use in your application's module: 32 * 33 * <pre><code> 34 * public class SnacksModule extends AbstractModule { 35 * protected void configure() { 36 * MapBinder<String, Snack> mapbinder 37 * = MapBinder.newMapBinder(binder(), String.class, Snack.class); 38 * mapbinder.addBinding("twix").toInstance(new Twix()); 39 * mapbinder.addBinding("snickers").toProvider(SnickersProvider.class); 40 * mapbinder.addBinding("skittles").to(Skittles.class); 41 * } 42 * }</code></pre> 43 * 44 * <p>With this binding, a {@link Map}{@code <String, Snack>} can now be injected: 45 * 46 * <pre><code> 47 * class SnackMachine { 48 * {@literal @}Inject 49 * public SnackMachine(Map<String, Snack> snacks) { ... } 50 * }</code></pre> 51 * 52 * <p>In addition to binding {@code Map<K, V>}, a mapbinder will also bind {@code Map<K, 53 * Provider<V>>} for lazy value provision: 54 * 55 * <pre><code> 56 * class SnackMachine { 57 * {@literal @}Inject 58 * public SnackMachine(Map<String, Provider<Snack>> snackProviders) { ... } 59 * }</code></pre> 60 * 61 * <p>Contributing mapbindings from different modules is supported. For example, it is okay to have 62 * both {@code CandyModule} and {@code ChipsModule} both create their own {@code MapBinder<String, 63 * Snack>}, and to each contribute bindings to the snacks map. When that map is injected, it will 64 * contain entries from both modules. 65 * 66 * <p>The map's iteration order is consistent with the binding order. This is convenient when 67 * multiple elements are contributed by the same module because that module can order its bindings 68 * appropriately. Avoid relying on the iteration order of elements contributed by different modules, 69 * since there is no equivalent mechanism to order modules. 70 * 71 * <p>The map is unmodifiable. Elements can only be added to the map by configuring the MapBinder. 72 * Elements can never be removed from the map. 73 * 74 * <p>Values are resolved at map injection time. If a value is bound to a provider, that provider's 75 * get method will be called each time the map is injected (unless the binding is also scoped, or a 76 * map of providers is injected). 77 * 78 * <p>Annotations are used to create different maps of the same key/value type. Each distinct 79 * annotation gets its own independent map. 80 * 81 * <p><strong>Keys must be distinct.</strong> If the same key is bound more than once, map injection 82 * will fail. However, use {@link #permitDuplicates()} in order to allow duplicate keys; extra 83 * bindings to {@code Map<K, Set<V>>} and {@code Map<K, Set<Provider<V>>} will be added. 84 * 85 * <p><strong>Keys must be non-null.</strong> {@code addBinding(null)} will throw an unchecked 86 * exception. 87 * 88 * <p><strong>Values must be non-null to use map injection.</strong> If any value is null, map 89 * injection will fail (although injecting a map of providers will not). 90 * 91 * @author dpb@google.com (David P. Baker) 92 */ 93 public class MapBinder<K, V> { 94 // This class is non-final due to users mocking this in tests :( 95 96 /** 97 * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a {@link 98 * Map} that is itself bound with no binding annotation. 99 */ newMapBinder( Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType)100 public static <K, V> MapBinder<K, V> newMapBinder( 101 Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType) { 102 return new MapBinder<K, V>( 103 newMapRealBinder(binder.skipSources(MapBinder.class), keyType, valueType)); 104 } 105 106 /** 107 * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a {@link 108 * Map} that is itself bound with no binding annotation. 109 */ newMapBinder( Binder binder, Class<K> keyType, Class<V> valueType)110 public static <K, V> MapBinder<K, V> newMapBinder( 111 Binder binder, Class<K> keyType, Class<V> valueType) { 112 return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType)); 113 } 114 115 /** 116 * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a {@link 117 * Map} that is itself bound with {@code annotation}. 118 */ newMapBinder( Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType, Annotation annotation)119 public static <K, V> MapBinder<K, V> newMapBinder( 120 Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType, Annotation annotation) { 121 return new MapBinder<K, V>( 122 newRealMapBinder(binder.skipSources(MapBinder.class), keyType, valueType, annotation)); 123 } 124 125 /** 126 * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a {@link 127 * Map} that is itself bound with {@code annotation}. 128 */ newMapBinder( Binder binder, Class<K> keyType, Class<V> valueType, Annotation annotation)129 public static <K, V> MapBinder<K, V> newMapBinder( 130 Binder binder, Class<K> keyType, Class<V> valueType, Annotation annotation) { 131 return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotation); 132 } 133 134 /** 135 * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a {@link 136 * Map} that is itself bound with {@code annotationType}. 137 */ newMapBinder( Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType, Class<? extends Annotation> annotationType)138 public static <K, V> MapBinder<K, V> newMapBinder( 139 Binder binder, 140 TypeLiteral<K> keyType, 141 TypeLiteral<V> valueType, 142 Class<? extends Annotation> annotationType) { 143 return new MapBinder<K, V>( 144 newRealMapBinder(binder.skipSources(MapBinder.class), keyType, valueType, annotationType)); 145 } 146 147 /** 148 * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a {@link 149 * Map} that is itself bound with {@code annotationType}. 150 */ newMapBinder( Binder binder, Class<K> keyType, Class<V> valueType, Class<? extends Annotation> annotationType)151 public static <K, V> MapBinder<K, V> newMapBinder( 152 Binder binder, 153 Class<K> keyType, 154 Class<V> valueType, 155 Class<? extends Annotation> annotationType) { 156 return newMapBinder( 157 binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotationType); 158 } 159 160 private final RealMapBinder<K, V> delegate; 161 MapBinder(RealMapBinder<K, V> delegate)162 private MapBinder(RealMapBinder<K, V> delegate) { 163 this.delegate = delegate; 164 } 165 166 /** 167 * Configures the {@code MapBinder} to handle duplicate entries. 168 * 169 * <p>When multiple equal keys are bound, the value that gets included in the map is arbitrary. 170 * 171 * <p>In addition to the {@code Map<K, V>} and {@code Map<K, Provider<V>>} maps that are normally 172 * bound, a {@code Map<K, Set<V>>} and {@code Map<K, Set<Provider<V>>>} are <em>also</em> bound, 173 * which contain all values bound to each key. 174 * 175 * <p>When multiple modules contribute elements to the map, this configuration option impacts all 176 * of them. 177 * 178 * @return this map binder 179 * @since 3.0 180 */ permitDuplicates()181 public MapBinder<K, V> permitDuplicates() { 182 delegate.permitDuplicates(); 183 return this; 184 } 185 186 /** 187 * Returns a binding builder used to add a new entry in the map. Each key must be distinct (and 188 * non-null). Bound providers will be evaluated each time the map is injected. 189 * 190 * <p>It is an error to call this method without also calling one of the {@code to} methods on the 191 * returned binding builder. 192 * 193 * <p>Scoping elements independently is supported. Use the {@code in} method to specify a binding 194 * scope. 195 */ addBinding(K key)196 public LinkedBindingBuilder<V> addBinding(K key) { 197 return delegate.addBinding(key); 198 } 199 200 // Some tests rely on MapBinder implementing equals/hashCode 201 202 @Override equals(Object obj)203 public boolean equals(Object obj) { 204 if (obj instanceof MapBinder) { 205 return delegate.equals(((MapBinder<?, ?>) obj).delegate); 206 } 207 return false; 208 } 209 210 @Override hashCode()211 public int hashCode() { 212 return delegate.hashCode(); 213 } 214 } 215